From 8825b0410a869007817529021a3c59f00dc22958 Mon Sep 17 00:00:00 2001 From: chamikaJ Date: Thu, 17 Apr 2025 18:28:54 +0530 Subject: [PATCH 1/5] init --- .env | 34 + .env.example | 34 + README.md | 138 + docker-compose.yml | 96 +- start.bat | 69 + start.sh | 92 + stop.bat | 7 + stop.sh | 19 + worklenz-backend/.env.example | 67 + worklenz-backend/.env.template | 40 +- worklenz-backend/Dockerfile | 2 +- worklenz-backend/database/1_tables.sql | 1219 +- worklenz-backend/database/2_dml.sql | 402 + worklenz-backend/database/3_system-data.sql | 77 - worklenz-backend/database/3_views.sql | 34 + worklenz-backend/database/4_functions.sql | 6149 +++ worklenz-backend/database/4_views.sql | 34 - worklenz-backend/database/5_database_user.sql | 31 + worklenz-backend/database/5_functions.sql | 5791 --- .../database/6_user-permission.sql | 35 - worklenz-backend/database/indexes.sql | 115 + .../database/migrations}/.gitkeep | 0 .../database/migrations/MIGRATION-NOTE.md | 0 .../database/migrations/README.md | 2 + .../database/text_length_checks.sql | 47 + .../database/{2_triggers.sql => triggers.sql} | 0 worklenz-backend/database/truncate.sql | 75 + .../database/worklenz_db_revision_1.svg | 2533 ++ .../database/worklenz_db_revision_2.svg | 7392 ++++ worklenz-backend/package-lock.json | 1132 +- worklenz-backend/package.json | 24 +- worklenz-backend/release | 2 +- worklenz-backend/src/app.ts | 292 +- worklenz-backend/src/bin/www.ts | 3 +- .../controllers/admin-center-controller.ts | 456 +- .../src/controllers/attachment-controller.ts | 36 +- .../src/controllers/auth-controller.ts | 41 +- .../src/controllers/billing-controller.ts | 288 + .../src/controllers/clients-controller.ts | 2 +- .../controllers/custom-columns-controller.ts | 531 + .../src/controllers/home-page-controller.ts | 2 +- .../src/controllers/index-controller.ts | 2 +- .../project-comments-controller.ts | 2 +- .../controllers/project-members-controller.ts | 67 + .../pt-templates-controller.ts | 33 +- .../src/controllers/projects-controller.ts | 57 +- .../src/controllers/reporting/interfaces.ts | 4 +- .../overview/reporting-overview-base.ts | 146 +- .../reporting/point-options-object.ts | 13 + .../reporting-allocation-controller.ts | 122 +- .../reporting/reporting-controller-base.ts | 20 + .../reporting/reporting-members-controller.ts | 41 +- .../schedule-v2/schedule-controller.ts | 407 + .../schedule/schedule-controller.ts | 77 + .../src/controllers/sub-tasks-controller.ts | 10 +- .../controllers/task-comments-controller.ts | 415 +- .../task-dependencies-controller.ts | 52 + .../task-list-columns-controller.ts | 5 +- .../controllers/task-priorities-controller.ts | 6 +- .../controllers/task-recurring-controller.ts | 108 + .../controllers/task-statuses-controller.ts | 6 +- .../controllers/task-work-log-controller.ts | 21 + .../src/controllers/tasks-controller-base.ts | 4 +- .../src/controllers/tasks-controller-v2.ts | 273 +- .../src/controllers/tasks-controller.ts | 54 +- .../controllers/team-members-controller.ts | 228 +- .../src/controllers/timezones-controller.ts | 6 +- worklenz-backend/src/cron_jobs/helpers.ts | 4 +- worklenz-backend/src/cron_jobs/index.ts | 2 + .../src/cron_jobs/project-digest-job.ts | 2 + .../src/cron_jobs/recurring-tasks.ts | 113 + .../src/interfaces/passport-session.ts | 1 + .../src/interfaces/recurring-tasks.ts | 38 + .../src/interfaces/serialize-callback.ts | 2 +- worklenz-backend/src/keys/PRIVATE_KEY_DEV.pem | 9 + .../src/keys/PRIVATE_KEY_PROD.pem | 9 + .../src/middlewares/session-middleware.ts | 13 +- .../validators/sign-up-validator.ts | 2 + .../validators/task-attachments-validator.ts | 23 +- .../task-comment-attachment-validator.ts | 17 + .../validators/task-comment-body-validator.ts | 6 +- .../validators/task-create-body--validator.ts | 48 + .../validators/task-dependencies-validator.ts | 12 + .../validators/team-members-body-validator.ts | 2 +- worklenz-backend/src/passport/deserialize.ts | 12 +- .../passport-strategies/passport-google.ts | 4 +- .../passport-local-login.ts | 48 +- .../passport-local-signup.ts | 6 +- worklenz-backend/src/passport/serialize.ts | 2 +- .../src/public/148.3562c20f7c79dfbe.js | 1 - .../src/public/150.aea42238d373936d.js | 1 - .../src/public/150.e8fd9de562cbd890.js | 1 + .../src/public/152.89824b7edf9e2b3f.js | 129 - .../src/public/169.3681839fd43f684a.js | 1 - .../src/public/169.3b2c60209f446fbe.js | 1 + .../src/public/226.342b896c33d8d900.js | 1 - .../src/public/226.76ed0ef59ac56130.js | 1 + .../src/public/246.f0a439ab83f2a2c0.js | 1 + .../src/public/265.899f98599d1ef008.js | 1 + .../src/public/265.e0fb54844d977984.js | 1 - .../src/public/269.c4d92a972c5787a6.js | 1 - .../src/public/31.366d3b5b3a40bd28.js | 1 - .../src/public/31.b9d7263dff6a2005.js | 1 + .../src/public/3rdpartylicenses.txt | 25 + .../src/public/422.2cee432955f17cf9.js | 1 + .../src/public/422.7a1aad3ba048190e.js | 1 - .../src/public/450.2aa94132069852f3.js | 1 - .../src/public/453.233ae5ea989450bd.js | 1 + .../src/public/453.850082f04ceb33a4.js | 1 - .../src/public/469.3682bfad6e614ed8.js | 1 + .../src/public/526.31f0f862d4cc0de0.js | 1 - .../src/public/570.496193a4334c6152.js | 1 + .../src/public/570.7a7379f081e3b4a7.js | 1 - .../src/public/586.cc390d02c36f1014.js | 1 + .../src/public/589.54dc9f2604592d56.js | 1 + .../src/public/589.fc04e75bdb5d1681.js | 1 - .../src/public/771.5669afea3e716661.js | 1 + .../src/public/771.c1c131496d7c6ea4.js | 1 - .../src/public/823.6c1e987f275ca088.js | 1 + .../src/public/848.703fbe56ddd4145d.js | 1 + .../src/public/860.0b5404f4cedd350f.js | 129 + .../src/public/89.056b6bfe2710d324.js | 1 - .../src/public/89.ffa2fa0c920899c7.js | 1 + .../src/public/921.06d29d0727f62a99.js | 1 + .../src/public/921.09750d890ab4e8d5.js | 1 - .../src/public/945.80e0a90c72e96010.js | 1 + .../src/public/945.9a0115568762948e.js | 1 - .../src/public/assets/images/google-icon.png | Bin 10275 -> 716 bytes .../src/public/assets/images/wl-logo.png | Bin 0 -> 2006 bytes .../src/public/common.6327831a82c2372e.js | 1 + .../src/public/main.05d8ba4986531fb3.js | 1 + .../src/public/main.91ef34cb24678df1.js | 1 - worklenz-backend/src/public/ngsw.json | 96 +- .../src/public/runtime.6007cef15fea30d1.js | 1 + .../src/public/runtime.9997dc39514a207d.js | 1 - .../src/public/scripts.47557d34e47c126b.js | 1 + .../src/public/styles.68b8da263f6e7075.css | 9 + .../src/public/styles.c78b93a1a5b19d7f.css | 9 - .../src/public/tinymce/CHANGELOG.md | 3449 ++ worklenz-backend/src/public/tinymce/README.md | 73 + .../src/public/tinymce/bower.json | 27 + .../src/public/tinymce/composer.json | 52 + .../src/public/tinymce/icons/default/icons.js | 194 + .../public/tinymce/icons/default/icons.min.js | 1 + .../src/public/tinymce/icons/default/index.js | 7 + .../src/public/tinymce/license.txt | 21 + .../src/public/tinymce/models/dom/index.js | 7 + .../src/public/tinymce/models/dom/model.js | 8040 ++++ .../public/tinymce/models/dom/model.min.js | 4 + .../src/public/tinymce/package.json | 32 + .../public/tinymce/plugins/accordion/index.js | 7 + .../tinymce/plugins/accordion/plugin.js | 1054 + .../tinymce/plugins/accordion/plugin.min.js | 4 + .../public/tinymce/plugins/advlist/index.js | 7 + .../public/tinymce/plugins/advlist/plugin.js | 259 + .../tinymce/plugins/advlist/plugin.min.js | 4 + .../public/tinymce/plugins/anchor/index.js | 7 + .../public/tinymce/plugins/anchor/plugin.js | 214 + .../tinymce/plugins/anchor/plugin.min.js | 4 + .../public/tinymce/plugins/autolink/index.js | 7 + .../public/tinymce/plugins/autolink/plugin.js | 228 + .../tinymce/plugins/autolink/plugin.min.js | 4 + .../tinymce/plugins/autoresize/index.js | 7 + .../tinymce/plugins/autoresize/plugin.js | 192 + .../tinymce/plugins/autoresize/plugin.min.js | 4 + .../public/tinymce/plugins/autosave/index.js | 7 + .../public/tinymce/plugins/autosave/plugin.js | 233 + .../tinymce/plugins/autosave/plugin.min.js | 4 + .../public/tinymce/plugins/charmap/index.js | 7 + .../public/tinymce/plugins/charmap/plugin.js | 1658 + .../tinymce/plugins/charmap/plugin.min.js | 4 + .../src/public/tinymce/plugins/code/index.js | 7 + .../src/public/tinymce/plugins/code/plugin.js | 85 + .../public/tinymce/plugins/code/plugin.min.js | 4 + .../tinymce/plugins/codesample/index.js | 7 + .../tinymce/plugins/codesample/plugin.js | 2463 ++ .../tinymce/plugins/codesample/plugin.min.js | 4 + .../tinymce/plugins/directionality/index.js | 7 + .../tinymce/plugins/directionality/plugin.js | 395 + .../plugins/directionality/plugin.min.js | 4 + .../public/tinymce/plugins/emoticons/index.js | 7 + .../plugins/emoticons/js/emojiimages.js | 1 + .../plugins/emoticons/js/emojiimages.min.js | 3 + .../tinymce/plugins/emoticons/js/emojis.js | 1 + .../plugins/emoticons/js/emojis.min.js | 2 + .../tinymce/plugins/emoticons/plugin.js | 595 + .../tinymce/plugins/emoticons/plugin.min.js | 4 + .../tinymce/plugins/fullscreen/index.js | 7 + .../tinymce/plugins/fullscreen/plugin.js | 1249 + .../tinymce/plugins/fullscreen/plugin.min.js | 4 + .../src/public/tinymce/plugins/help/index.js | 7 + .../tinymce/plugins/help/js/i18n/keynav/ar.js | 90 + .../plugins/help/js/i18n/keynav/bg_BG.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/ca.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/cs.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/da.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/de.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/el.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/en.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/es.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/eu.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/fa.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/fi.js | 90 + .../plugins/help/js/i18n/keynav/fr_FR.js | 90 + .../plugins/help/js/i18n/keynav/he_IL.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/hi.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/hr.js | 90 + .../plugins/help/js/i18n/keynav/hu_HU.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/id.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/it.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/ja.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/kk.js | 90 + .../plugins/help/js/i18n/keynav/ko_KR.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/ms.js | 90 + .../plugins/help/js/i18n/keynav/nb_NO.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/nl.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/pl.js | 90 + .../plugins/help/js/i18n/keynav/pt_BR.js | 90 + .../plugins/help/js/i18n/keynav/pt_PT.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/ro.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/ru.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/sk.js | 90 + .../plugins/help/js/i18n/keynav/sl_SI.js | 90 + .../plugins/help/js/i18n/keynav/sv_SE.js | 90 + .../plugins/help/js/i18n/keynav/th_TH.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/tr.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/uk.js | 90 + .../tinymce/plugins/help/js/i18n/keynav/vi.js | 90 + .../plugins/help/js/i18n/keynav/zh_CN.js | 84 + .../plugins/help/js/i18n/keynav/zh_TW.js | 90 + .../src/public/tinymce/plugins/help/plugin.js | 898 + .../public/tinymce/plugins/help/plugin.min.js | 4 + .../src/public/tinymce/plugins/image/index.js | 7 + .../public/tinymce/plugins/image/plugin.js | 1505 + .../tinymce/plugins/image/plugin.min.js | 4 + .../public/tinymce/plugins/importcss/index.js | 7 + .../tinymce/plugins/importcss/plugin.js | 344 + .../tinymce/plugins/importcss/plugin.min.js | 4 + .../tinymce/plugins/insertdatetime/index.js | 7 + .../tinymce/plugins/insertdatetime/plugin.js | 187 + .../plugins/insertdatetime/plugin.min.js | 4 + .../src/public/tinymce/plugins/link/index.js | 7 + .../src/public/tinymce/plugins/link/plugin.js | 1242 + .../public/tinymce/plugins/link/plugin.min.js | 4 + .../src/public/tinymce/plugins/lists/index.js | 7 + .../public/tinymce/plugins/lists/plugin.js | 2172 ++ .../tinymce/plugins/lists/plugin.min.js | 4 + .../src/public/tinymce/plugins/media/index.js | 7 + .../public/tinymce/plugins/media/plugin.js | 1217 + .../tinymce/plugins/media/plugin.min.js | 4 + .../tinymce/plugins/nonbreaking/index.js | 7 + .../tinymce/plugins/nonbreaking/plugin.js | 123 + .../tinymce/plugins/nonbreaking/plugin.min.js | 4 + .../public/tinymce/plugins/pagebreak/index.js | 7 + .../tinymce/plugins/pagebreak/plugin.js | 117 + .../tinymce/plugins/pagebreak/plugin.min.js | 4 + .../public/tinymce/plugins/preview/index.js | 7 + .../public/tinymce/plugins/preview/plugin.js | 97 + .../tinymce/plugins/preview/plugin.min.js | 4 + .../public/tinymce/plugins/quickbars/index.js | 7 + .../tinymce/plugins/quickbars/plugin.js | 447 + .../tinymce/plugins/quickbars/plugin.min.js | 4 + .../src/public/tinymce/plugins/save/index.js | 7 + .../src/public/tinymce/plugins/save/plugin.js | 118 + .../public/tinymce/plugins/save/plugin.min.js | 4 + .../tinymce/plugins/searchreplace/index.js | 7 + .../tinymce/plugins/searchreplace/plugin.js | 1093 + .../plugins/searchreplace/plugin.min.js | 4 + .../src/public/tinymce/plugins/table/index.js | 7 + .../public/tinymce/plugins/table/plugin.js | 3462 ++ .../tinymce/plugins/table/plugin.min.js | 4 + .../public/tinymce/plugins/template/index.js | 7 + .../public/tinymce/plugins/template/plugin.js | 567 + .../tinymce/plugins/template/plugin.min.js | 4 + .../tinymce/plugins/visualblocks/index.js | 7 + .../tinymce/plugins/visualblocks/plugin.js | 98 + .../plugins/visualblocks/plugin.min.js | 4 + .../tinymce/plugins/visualchars/index.js | 7 + .../tinymce/plugins/visualchars/plugin.js | 560 + .../tinymce/plugins/visualchars/plugin.min.js | 4 + .../public/tinymce/plugins/wordcount/index.js | 7 + .../tinymce/plugins/wordcount/plugin.js | 405 + .../tinymce/plugins/wordcount/plugin.min.js | 4 + .../tinymce/skins/content/dark/content.css | 66 + .../tinymce/skins/content/dark/content.js | 2 + .../skins/content/dark/content.min.css | 1 + .../tinymce/skins/content/default/content.css | 61 + .../tinymce/skins/content/default/content.js | 2 + .../skins/content/default/content.min.css | 1 + .../skins/content/document/content.css | 66 + .../tinymce/skins/content/document/content.js | 2 + .../skins/content/document/content.min.css | 1 + .../skins/content/tinymce-5-dark/content.css | 66 + .../skins/content/tinymce-5-dark/content.js | 2 + .../content/tinymce-5-dark/content.min.css | 1 + .../skins/content/tinymce-5/content.css | 61 + .../skins/content/tinymce-5/content.js | 2 + .../skins/content/tinymce-5/content.min.css | 1 + .../tinymce/skins/content/writer/content.css | 62 + .../tinymce/skins/content/writer/content.js | 2 + .../skins/content/writer/content.min.css | 1 + .../tinymce/skins/ui/oxide-dark/content.css | 766 + .../skins/ui/oxide-dark/content.inline.css | 779 + .../skins/ui/oxide-dark/content.inline.js | 2 + .../ui/oxide-dark/content.inline.min.css | 1 + .../tinymce/skins/ui/oxide-dark/content.js | 2 + .../skins/ui/oxide-dark/content.min.css | 1 + .../tinymce/skins/ui/oxide-dark/skin.css | 3766 ++ .../tinymce/skins/ui/oxide-dark/skin.js | 2 + .../tinymce/skins/ui/oxide-dark/skin.min.css | 1 + .../skins/ui/oxide-dark/skin.shadowdom.css | 30 + .../skins/ui/oxide-dark/skin.shadowdom.js | 2 + .../ui/oxide-dark/skin.shadowdom.min.css | 1 + .../public/tinymce/skins/ui/oxide/content.css | 785 + .../tinymce/skins/ui/oxide/content.inline.css | 779 + .../tinymce/skins/ui/oxide/content.inline.js | 2 + .../skins/ui/oxide/content.inline.min.css | 1 + .../public/tinymce/skins/ui/oxide/content.js | 2 + .../tinymce/skins/ui/oxide/content.min.css | 1 + .../public/tinymce/skins/ui/oxide/skin.css | 3763 ++ .../src/public/tinymce/skins/ui/oxide/skin.js | 2 + .../tinymce/skins/ui/oxide/skin.min.css | 1 + .../tinymce/skins/ui/oxide/skin.shadowdom.css | 30 + .../tinymce/skins/ui/oxide/skin.shadowdom.js | 2 + .../skins/ui/oxide/skin.shadowdom.min.css | 1 + .../skins/ui/tinymce-5-dark/content.css | 766 + .../ui/tinymce-5-dark/content.inline.css | 779 + .../skins/ui/tinymce-5-dark/content.inline.js | 2 + .../ui/tinymce-5-dark/content.inline.min.css | 1 + .../skins/ui/tinymce-5-dark/content.js | 2 + .../skins/ui/tinymce-5-dark/content.min.css | 1 + .../tinymce/skins/ui/tinymce-5-dark/skin.css | 3857 ++ .../tinymce/skins/ui/tinymce-5-dark/skin.js | 2 + .../skins/ui/tinymce-5-dark/skin.min.css | 1 + .../ui/tinymce-5-dark/skin.shadowdom.css | 30 + .../skins/ui/tinymce-5-dark/skin.shadowdom.js | 2 + .../ui/tinymce-5-dark/skin.shadowdom.min.css | 1 + .../tinymce/skins/ui/tinymce-5/content.css | 785 + .../skins/ui/tinymce-5/content.inline.css | 779 + .../skins/ui/tinymce-5/content.inline.js | 2 + .../skins/ui/tinymce-5/content.inline.min.css | 1 + .../tinymce/skins/ui/tinymce-5/content.js | 2 + .../skins/ui/tinymce-5/content.min.css | 1 + .../tinymce/skins/ui/tinymce-5/skin.css | 3857 ++ .../public/tinymce/skins/ui/tinymce-5/skin.js | 2 + .../tinymce/skins/ui/tinymce-5/skin.min.css | 1 + .../skins/ui/tinymce-5/skin.shadowdom.css | 30 + .../skins/ui/tinymce-5/skin.shadowdom.js | 2 + .../skins/ui/tinymce-5/skin.shadowdom.min.css | 1 + .../src/public/tinymce/themes/silver/index.js | 7 + .../src/public/tinymce/themes/silver/theme.js | 30754 +++++++++++++++ .../public/tinymce/themes/silver/theme.min.js | 4 + .../src/public/tinymce/tinymce.d.ts | 3238 ++ .../src/public/tinymce/tinymce.js | 31757 ++++++++++++++++ .../src/public/tinymce/tinymce.min.js | 4 + .../routes/apis/admin-center-api-router.ts | 27 + .../src/routes/apis/billing-api-router.ts | 15 + .../routes/apis/custom-columns-api-router.ts | 14 + .../apis/gannt-apis/schedule-api-router.ts | 1 + .../apis/gannt-apis/schedule-api-v2-router.ts | 22 + worklenz-backend/src/routes/apis/index.ts | 221 +- .../src/routes/apis/projects-api-router.ts | 5 + .../routes/apis/task-comments-api-router.ts | 8 + .../apis/task-dependencies-api-router.ts | 11 + .../routes/apis/task-recurring-api-router.ts | 10 + .../routes/apis/task-work-log-api-router.ts | 1 + .../src/routes/apis/tasks-api-router.ts | 12 +- worklenz-backend/src/routes/auth/index.ts | 2 + .../src/routes/email-templates.ts | 30 +- .../activity-logs/activity-logs.service.ts | 2 +- worklenz-backend/src/shared/constants.ts | 40 + worklenz-backend/src/shared/csp.ts | 108 +- .../src/shared/email-templates.ts | 10 +- worklenz-backend/src/shared/email.ts | 2 +- .../src/shared/paddle-requests.ts | 150 + worklenz-backend/src/shared/paddle-utils.ts | 104 + worklenz-backend/src/shared/s3.ts | 14 +- worklenz-backend/src/shared/slack.ts | 24 + worklenz-backend/src/shared/storage.ts | 387 + worklenz-backend/src/shared/utils.ts | 64 +- .../commands/on-project-health-change.ts | 3 +- .../commands/on-quick-assign-or-remove.ts | 8 +- .../commands/on-task-assignees-change.ts | 125 + .../commands/on-task-billable-change.ts | 21 + .../commands/on-task-description-change.ts | 40 +- .../socket.io/commands/on-task-name-change.ts | 4 +- .../commands/on-task-priority-change.ts | 4 +- .../commands/on-task-recurring-change.ts | 29 + .../commands/on-task-sort-order-change.ts | 11 +- .../commands/on-task-status-change.ts | 22 +- .../on_custom_column_pinned_change.ts | 64 + .../commands/on_custom_column_update.ts | 137 + worklenz-backend/src/socket.io/events.ts | 7 +- worklenz-backend/src/socket.io/index.ts | 12 +- .../src/utils/generate-project-key.ts | 10 +- worklenz-backend/src/views/_hubspot.pug | 4 +- worklenz-backend/src/views/_scripts.pug | 21 + worklenz-backend/src/views/_tawk-to.pug | 11 + worklenz-backend/src/views/admin/layout.pug | 9 +- worklenz-backend/src/views/error/layout.pug | 22 + worklenz-backend/src/views/layout.pug | 3 +- .../worklenz-email-templates/licensing.html | 250 + .../release-note-template.html | 308 + worklenz-frontend/.editorconfig | 16 - worklenz-frontend/.env.development | 17 + worklenz-frontend/.eslintrc.json | 46 - worklenz-frontend/.gitignore | 56 +- worklenz-frontend/.npmrc | 2 - worklenz-frontend/.prettierrc | 24 + worklenz-frontend/Dockerfile | 21 +- worklenz-frontend/README.md | 82 +- worklenz-frontend/angular.json | 157 - worklenz-frontend/{src => }/favicon.ico | Bin worklenz-frontend/index.html | 22 + worklenz-frontend/jest.config.js | 7 + worklenz-frontend/karma.conf.js | 44 - worklenz-frontend/ngsw-config.json | 30 - worklenz-frontend/package-lock.json | 18756 +++------ worklenz-frontend/package.json | 153 +- .../path/to/members-reports-drawer.tsx | 49 + .../path/to/members-reports-time-logs-tab.tsx | 41 + worklenz-frontend/postcss.config.js | 6 + worklenz-frontend/project-report-table.css | 17 + worklenz-frontend/proxy-docker.config.json | 24 - worklenz-frontend/proxy.config.json | 24 - .../images/files => public/file-types}/ai.png | Bin .../files => public/file-types}/avi.png | Bin .../files => public/file-types}/css.png | Bin .../files => public/file-types}/csv.png | Bin .../files => public/file-types}/doc.png | Bin .../files => public/file-types}/exe.png | Bin .../files => public/file-types}/html.png | Bin .../files => public/file-types}/jpg.png | Bin .../images/files => public/file-types}/js.png | Bin .../files => public/file-types}/json.png | Bin .../files => public/file-types}/mp3.png | Bin .../files => public/file-types}/mp4.png | Bin .../files => public/file-types}/pdf.png | Bin .../files => public/file-types}/png.png | Bin .../files => public/file-types}/ppt.png | Bin .../files => public/file-types}/psd.png | Bin .../files => public/file-types}/search.png | Bin .../files => public/file-types}/svg.png | Bin .../files => public/file-types}/txt.png | Bin .../files => public/file-types}/xls.png | Bin .../files => public/file-types}/xml.png | Bin .../files => public/file-types}/zip.png | Bin .../public/locales/en/404-page.json | 4 + .../public/locales/en/account-setup.json | 31 + .../locales/en/admin-center/current-bill.json | 113 + .../locales/en/admin-center/overview.json | 8 + .../locales/en/admin-center/projects.json | 12 + .../locales/en/admin-center/sidebar.json | 8 + .../public/locales/en/admin-center/teams.json | 33 + .../public/locales/en/admin-center/users.json | 9 + .../public/locales/en/all-project-list.json | 23 + .../public/locales/en/auth/auth-common.json | 5 + .../locales/en/auth/forgot-password.json | 12 + .../public/locales/en/auth/login.json | 27 + .../public/locales/en/auth/signup.json | 29 + .../locales/en/auth/verify-reset-email.json | 14 + .../public/locales/en/common.json | 9 + .../locales/en/create-first-project-form.json | 13 + .../public/locales/en/create-first-tasks.json | 7 + worklenz-frontend/public/locales/en/home.json | 46 + .../en/invite-initial-team-members.json | 8 + .../public/locales/en/kanban-board.json | 23 + .../public/locales/en/license-expired.json | 6 + .../public/locales/en/navbar.json | 31 + .../locales/en/organization-name-form.json | 5 + .../public/locales/en/phases-drawer.json | 7 + .../public/locales/en/project-drawer.json | 42 + .../public/locales/en/project-view-files.json | 14 + .../locales/en/project-view-insights.json | 41 + .../locales/en/project-view-members.json | 17 + .../locales/en/project-view-updates.json | 6 + .../project-view/import-task-templates.json | 11 + .../project-view/project-member-drawer.json | 8 + .../en/project-view/project-view-header.json | 13 + .../en/project-view/save-as-template.json | 27 + .../locales/en/reporting-members-drawer.json | 90 + .../public/locales/en/reporting-members.json | 35 + .../locales/en/reporting-overview-drawer.json | 39 + .../public/locales/en/reporting-overview.json | 25 + .../locales/en/reporting-projects-drawer.json | 59 + .../en/reporting-projects-filters.json | 35 + .../public/locales/en/reporting-projects.json | 52 + .../public/locales/en/reporting-sidebar.json | 8 + .../public/locales/en/schedule.json | 39 + .../locales/en/settings/categories.json | 10 + .../locales/en/settings/change-password.json | 15 + .../public/locales/en/settings/clients.json | 22 + .../locales/en/settings/job-titles.json | 20 + .../public/locales/en/settings/labels.json | 11 + .../public/locales/en/settings/language.json | 7 + .../locales/en/settings/notifications.json | 11 + .../public/locales/en/settings/profile.json | 13 + .../en/settings/project-templates.json | 8 + .../public/locales/en/settings/sidebar.json | 14 + .../locales/en/settings/task-templates.json | 9 + .../locales/en/settings/team-members.json | 44 + .../en/task-drawer/task-drawer-info-tab.json | 29 + .../locales/en/task-drawer/task-drawer.json | 78 + .../public/locales/en/task-list-filters.json | 59 + .../public/locales/en/task-list-table.json | 63 + .../locales/en/task-template-drawer.json | 11 + .../en/tasks/task-table-bulk-actions.json | 24 + .../public/locales/en/template-drawer.json | 19 + .../public/locales/en/templateDrawer.json | 23 + .../public/locales/en/time-report.json | 44 + .../public/locales/en/unauthorized.json | 5 + .../public/locales/es/404-page.json | 4 + .../public/locales/es/account-setup.json | 32 + .../locales/es/admin-center/current-bill.json | 105 + .../locales/es/admin-center/overview.json | 8 + .../locales/es/admin-center/projects.json | 12 + .../locales/es/admin-center/sidebar.json | 8 + .../public/locales/es/admin-center/teams.json | 33 + .../public/locales/es/admin-center/users.json | 9 + .../public/locales/es/all-project-list.json | 23 + .../public/locales/es/auth/auth-common.json | 5 + .../locales/es/auth/forgot-password.json | 12 + .../public/locales/es/auth/login.json | 27 + .../public/locales/es/auth/signup.json | 29 + .../locales/es/auth/verify-reset-email.json | 14 + .../public/locales/es/common.json | 9 + .../locales/es/create-first-project-form.json | 13 + .../public/locales/es/create-first-tasks.json | 7 + worklenz-frontend/public/locales/es/home.json | 45 + .../es/invite-initial-team-members.json | 8 + .../public/locales/es/kanban-board.json | 23 + .../public/locales/es/license-expired.json | 6 + .../public/locales/es/navbar.json | 31 + .../locales/es/organization-name-form.json | 5 + .../public/locales/es/phases-drawer.json | 7 + .../public/locales/es/project-drawer.json | 42 + .../public/locales/es/project-view-files.json | 14 + .../locales/es/project-view-insights.json | 41 + .../locales/es/project-view-members.json | 17 + .../locales/es/project-view-updates.json | 6 + .../project-view/import-task-templates.json | 11 + .../project-view/project-member-drawer.json | 8 + .../es/project-view/project-view-header.json | 13 + .../es/project-view/save-as-template.json | 27 + .../locales/es/reporting-members-drawer.json | 90 + .../public/locales/es/reporting-members.json | 35 + .../locales/es/reporting-overview-drawer.json | 39 + .../public/locales/es/reporting-overview.json | 25 + .../locales/es/reporting-projects-drawer.json | 59 + .../es/reporting-projects-filters.json | 35 + .../public/locales/es/reporting-projects.json | 52 + .../public/locales/es/reporting-sidebar.json | 8 + .../public/locales/es/schedule.json | 39 + .../locales/es/settings/categories.json | 10 + .../locales/es/settings/change-password.json | 15 + .../public/locales/es/settings/clients.json | 22 + .../locales/es/settings/job-titles.json | 20 + .../public/locales/es/settings/labels.json | 11 + .../public/locales/es/settings/language.json | 7 + .../locales/es/settings/notifications.json | 10 + .../public/locales/es/settings/profile.json | 13 + .../es/settings/project-templates.json | 8 + .../public/locales/es/settings/sidebar.json | 14 + .../locales/es/settings/task-templates.json | 9 + .../locales/es/settings/team-members.json | 44 + .../es/task-drawer/task-drawer-info-tab.json | 29 + .../locales/es/task-drawer/task-drawer.json | 78 + .../public/locales/es/task-list-filters.json | 55 + .../public/locales/es/task-list-table.json | 63 + .../locales/es/task-template-drawer.json | 11 + .../es/tasks/task-table-bulk-actions.json | 24 + .../public/locales/es/template-drawer.json | 19 + .../public/locales/es/templateDrawer.json | 23 + .../public/locales/es/time-report.json | 44 + .../public/locales/es/unauthorized.json | 5 + .../public/locales/pt/404-page.json | 4 + .../public/locales/pt/account-setup.json | 32 + .../locales/pt/admin-center/current-bill.json | 105 + .../locales/pt/admin-center/overview.json | 8 + .../locales/pt/admin-center/projects.json | 12 + .../locales/pt/admin-center/sidebar.json | 8 + .../public/locales/pt/admin-center/teams.json | 33 + .../public/locales/pt/admin-center/users.json | 9 + .../public/locales/pt/all-project-list.json | 23 + .../public/locales/pt/auth/auth-common.json | 5 + .../locales/pt/auth/forgot-password.json | 12 + .../public/locales/pt/auth/login.json | 27 + .../public/locales/pt/auth/signup.json | 29 + .../locales/pt/auth/verify-reset-email.json | 14 + .../public/locales/pt/common.json | 9 + .../locales/pt/create-first-project-form.json | 13 + .../public/locales/pt/create-first-tasks.json | 7 + worklenz-frontend/public/locales/pt/home.json | 45 + .../pt/invite-initial-team-members.json | 8 + .../public/locales/pt/kanban-board.json | 23 + .../public/locales/pt/license-expired.json | 6 + .../public/locales/pt/navbar.json | 31 + .../locales/pt/organization-name-form.json | 5 + .../public/locales/pt/phases-drawer.json | 7 + .../public/locales/pt/project-drawer.json | 42 + .../public/locales/pt/project-view-files.json | 14 + .../locales/pt/project-view-insights.json | 41 + .../locales/pt/project-view-members.json | 17 + .../locales/pt/project-view-updates.json | 6 + .../project-view/import-task-templates.json | 11 + .../project-view/project-member-drawer.json | 8 + .../pt/project-view/project-view-header.json | 13 + .../pt/project-view/save-as-template.json | 27 + .../locales/pt/reporting-members-drawer.json | 90 + .../public/locales/pt/reporting-members.json | 35 + .../locales/pt/reporting-overview-drawer.json | 39 + .../public/locales/pt/reporting-overview.json | 25 + .../locales/pt/reporting-projects-drawer.json | 59 + .../pt/reporting-projects-filters.json | 35 + .../public/locales/pt/reporting-projects.json | 52 + .../public/locales/pt/reporting-sidebar.json | 8 + .../public/locales/pt/schedule.json | 39 + .../locales/pt/settings/categories.json | 10 + .../locales/pt/settings/change-password.json | 15 + .../public/locales/pt/settings/clients.json | 22 + .../locales/pt/settings/job-titles.json | 20 + .../public/locales/pt/settings/labels.json | 11 + .../public/locales/pt/settings/language.json | 7 + .../locales/pt/settings/notifications.json | 10 + .../public/locales/pt/settings/profile.json | 13 + .../pt/settings/project-templates.json | 8 + .../public/locales/pt/settings/sidebar.json | 14 + .../locales/pt/settings/task-templates.json | 9 + .../locales/pt/settings/team-members.json | 44 + .../pt/task-drawer/task-drawer-info-tab.json | 29 + .../locales/pt/task-drawer/task-drawer.json | 78 + .../public/locales/pt/task-list-filters.json | 56 + .../public/locales/pt/task-list-table.json | 63 + .../locales/pt/task-template-drawer.json | 11 + .../pt/tasks/task-table-bulk-actions.json | 24 + .../public/locales/pt/template-drawer.json | 19 + .../public/locales/pt/templateDrawer.json | 23 + .../public/locales/pt/time-report.json | 44 + .../public/locales/pt/unauthorized.json | 5 + .../scheduler-data/dates-list copy.json | 2268 ++ .../public/scheduler-data/dates-list.json | 198 + .../public/scheduler-data/team-data copy.json | 1182 + .../public/scheduler-data/team-data.json | 268 + worklenz-frontend/scripts/copy-tinymce.js | 39 + worklenz-frontend/src/App.tsx | 48 + .../admin-center/admin-center.api.service.ts | 258 + .../api/admin-center/billing.api.service.ts | 35 + worklenz-frontend/src/api/api-client.ts | 134 + .../attachments/attachments.api.service.ts | 33 + .../src/api/auth/auth.api.service.ts | 59 + .../src/api/clients/clients.api.service.ts | 49 + .../api/home-page/home-page.api.service.ts | 72 + .../notifications.api.service.ts | 34 + .../project-members.api.service.ts | 52 + .../project-templates.api.service.ts | 58 + .../comments/project-comments.api.service.ts | 63 + .../insights/project-insights.api.service.ts | 125 + .../lookups/projectHealth.api.service.ts | 13 + .../lookups/projectStatus.api.service.ts | 13 + .../src/api/projects/projects.api.service.ts | 121 + .../api/projects/projects.v1.api.service.ts | 153 + .../reporting/reporting-export.api.service.ts | 175 + .../reporting-members.api.service.ts | 18 + .../reporting-projects.api.service.ts | 43 + .../api/reporting/reporting.api.service.ts | 301 + .../reporting.timesheet.api.service.ts | 44 + .../src/api/schedule/schedule.api.service.ts | 60 + .../categories/categories.api.service.ts | 54 + .../job-titles/job-titles.api.service.ts | 44 + .../api/settings/labels/labels.api.service.ts | 39 + .../language-timezones-api.service.ts | 18 + .../profile/profile-settings.api.service.ts | 71 + .../task-templates.api.service.ts | 44 + .../labels/labels.api.service.ts | 40 + .../phases/phases.api.service.ts | 72 + .../priority/priority.api.service.ts | 22 + .../status/status.api.service.ts | 77 + .../src/api/tasks/subtasks.api.service.ts | 16 + .../tasks/task-activity-logs.api.service.ts | 15 + .../api/tasks/task-attachments.api.service.ts | 46 + .../api/tasks/task-comments.api.service.ts | 44 + .../tasks/task-dependencies.api.service.ts | 21 + .../task-list-bulk-actions.api.service.ts | 77 + .../api/tasks/task-time-logs.api.service.ts | 32 + .../api/tasks/tasks-custom-columns.service.ts | 69 + .../src/api/tasks/tasks.api.service.ts | 122 + .../team-members/teamMembers.api.service.ts | 99 + .../src/api/teams/teams.api.service.ts | 51 + .../account-setup-routing.module.ts | 16 - .../account-setup/account-setup.module.ts | 49 - .../account-setup.component.html | 161 - .../account-setup.component.scss | 116 - .../account-setup.component.spec.ts | 23 - .../account-setup/account-setup.component.ts | 298 - .../teams-list/teams-list.component.html | 50 - .../teams-list/teams-list.component.scss | 16 - .../teams-list/teams-list.component.spec.ts | 21 - .../teams-list/teams-list.component.ts | 132 - .../admin-center-routing.module.ts | 28 - .../admin-center-service.service.ts | 27 - .../admin-center/admin-center.module.ts | 86 - .../admin-center/layout/layout.component.html | 13 - .../admin-center/layout/layout.component.scss | 3 - .../layout/layout.component.spec.ts | 23 - .../admin-center/layout/layout.component.ts | 10 - .../overview/overview.component.html | 75 - .../overview/overview.component.scss | 42 - .../overview/overview.component.spec.ts | 23 - .../overview/overview.component.ts | 104 - .../sidebar/sidebar.component.html | 11 - .../sidebar/sidebar.component.spec.ts | 23 - .../admin-center/sidebar/sidebar.component.ts | 18 - .../admin-center/teams/teams.component.html | 260 - .../admin-center/teams/teams.component.scss | 11 - .../teams/teams.component.spec.ts | 23 - .../admin-center/teams/teams.component.ts | 293 - .../admin-center/users/users.component.html | 75 - .../admin-center/users/users.component.scss | 45 - .../users/users.component.spec.ts | 23 - .../admin-center/users/users.component.ts | 75 - .../administrator-routing.module.ts | 55 - .../app/administrator/administrator.module.ts | 91 - .../components/avatars/avatars.component.html | 29 - .../avatars/avatars.component.spec.ts | 23 - .../components/avatars/avatars.component.ts | 31 - .../clients-autocomplete.component.html | 21 - .../clients-autocomplete.component.spec.ts | 23 - .../clients-autocomplete.component.ts | 88 - .../convert-to-subtask-modal.component.html | 50 - .../convert-to-subtask-modal.component.scss | 86 - ...convert-to-subtask-modal.component.spec.ts | 25 - .../convert-to-subtask-modal.component.ts | 229 - .../import-tasks-template.component.html | 48 - .../import-tasks-template.component.spec.ts | 23 - .../import-tasks-template.component.ts | 190 - .../job-titles-autocomplete.component.html | 22 - .../job-titles-autocomplete.component.scss | 0 .../job-titles-autocomplete.component.spec.ts | 23 - .../job-titles-autocomplete.component.ts | 100 - .../components/na/na.component.html | 1 - .../components/na/na.component.scss | 0 .../components/na/na.component.spec.ts | 21 - .../components/na/na.component.ts | 15 - .../personal-overview.component.html | 53 - .../personal-overview.component.scss | 0 .../personal-overview.component.spec.ts | 23 - .../personal-overview.component.ts | 93 - ...ect-categories-autocomplete.component.html | 32 - ...ect-categories-autocomplete.component.scss | 0 ...-categories-autocomplete.component.spec.ts | 21 - ...oject-categories-autocomplete.component.ts | 141 - ...roject-folders-autocomplete.component.html | 21 - ...roject-folders-autocomplete.component.scss | 0 ...ect-folders-autocomplete.component.spec.ts | 21 - .../project-folders-autocomplete.component.ts | 91 - .../project-form-modal.component.html | 280 - .../project-form-modal.component.scss | 36 - .../project-form-modal.component.spec.ts | 23 - .../project-form-modal.component.ts | 594 - .../project-members-form.component.html | 58 - .../project-members-form.component.scss | 0 .../project-members-form.component.spec.ts | 23 - .../project-members-form.component.ts | 162 - ...ject-template-create-drawer.component.html | 95 - ...ject-template-create-drawer.component.scss | 18 - ...t-template-create-drawer.component.spec.ts | 21 - ...roject-template-create-drawer.component.ts | 143 - .../custom-template-list.component.html | 28 - .../custom-template-list.component.scss | 31 - .../custom-template-list.component.spec.ts | 21 - .../custom-template-list.component.ts | 74 - ...ject-template-import-drawer.component.html | 37 - ...ject-template-import-drawer.component.scss | 0 ...t-template-import-drawer.component.spec.ts | 21 - ...roject-template-import-drawer.component.ts | 138 - .../worklenz-template-list.component.html | 93 - .../worklenz-template-list.component.scss | 33 - .../worklenz-template-list.component.spec.ts | 21 - .../worklenz-template-list.component.ts | 84 - .../project-updates-drawer.component.html | 14 - .../project-updates-drawer.component.scss | 24 - .../project-updates-drawer.component.spec.ts | 21 - .../project-updates-drawer.component.ts | 73 - .../project-updates-input.component.html | 1 - .../project-updates-input.component.scss | 1 - .../project-updates-input.component.spec.ts | 21 - .../project-updates-input.component.ts | 31 - .../project-updates-list.component.html | 51 - .../project-updates-list.component.scss | 10 - .../project-updates-list.component.spec.ts | 21 - .../project-updates-list.component.ts | 270 - .../projects-autocomplete.component.html | 26 - .../projects-autocomplete.component.scss | 0 .../projects-autocomplete.component.spec.ts | 23 - .../projects-autocomplete.component.ts | 95 - .../resource-gantt.component.html | 54 - .../resource-gantt.component.scss | 110 - .../resource-gantt.component.spec.ts | 23 - .../resource-gantt.component.ts | 119 - .../status-form/status-form.component.html | 55 - .../status-form/status-form.component.scss | 9 - .../status-form/status-form.component.spec.ts | 23 - .../status-form/status-form.component.ts | 199 - .../task-priority-label.component.html | 8 - .../task-priority-label.component.scss | 18 - .../task-priority-label.component.spec.ts | 23 - .../task-priority-label.component.ts | 15 - .../task-template-drawer.component.html | 39 - .../task-template-drawer.component.scss | 0 .../task-template-drawer.component.spec.ts | 23 - .../task-template-drawer.component.ts | 158 - .../components/task-timer/interfaces.ts | 4 - .../task-timer/task-timer.component.html | 18 - .../task-timer/task-timer.component.scss | 16 - .../task-timer/task-timer.component.spec.ts | 23 - .../task-timer/task-timer.component.ts | 193 - .../task-timer/task-timer.service.ts | 52 - .../components/task-view/interfaces.ts | 15 - .../task-view-activity-log.component.html | 101 - .../task-view-activity-log.component.scss | 0 .../task-view-activity-log.component.spec.ts | 21 - .../task-view-activity-log.component.ts | 69 - .../task-view-assignees.component.html | 58 - .../task-view-assignees.component.scss | 17 - .../task-view-assignees.component.spec.ts | 23 - .../task-view-assignees.component.ts | 135 - ...task-view-attachments-thumb.component.html | 113 - ...task-view-attachments-thumb.component.scss | 45 - ...k-view-attachments-thumb.component.spec.ts | 23 - .../task-view-attachments-thumb.component.ts | 136 - .../task-view-attachments.component.html | 28 - .../task-view-attachments.component.scss | 11 - .../task-view-attachments.component.spec.ts | 23 - .../task-view-attachments.component.ts | 163 - .../task-view-comments-input.component.html | 32 - .../task-view-comments-input.component.scss | 10 - ...task-view-comments-input.component.spec.ts | 23 - .../task-view-comments-input.component.ts | 179 - .../task-view-comments.component.html | 21 - .../task-view-comments.component.scss | 13 - .../task-view-comments.component.spec.ts | 23 - .../task-view-comments.component.ts | 102 - .../task-view-description.component.html | 19 - .../task-view-description.component.scss | 36 - .../task-view-description.component.spec.ts | 23 - .../task-view-description.component.ts | 98 - .../task-view-due-date.component.html | 41 - .../task-view-due-date.component.scss | 28 - .../task-view-due-date.component.spec.ts | 23 - .../task-view-due-date.component.ts | 91 - .../task-view-estimation.component.html | 24 - .../task-view-estimation.component.scss | 0 .../task-view-estimation.component.spec.ts | 23 - .../task-view-estimation.component.ts | 32 - .../task-view-info.component.html | 72 - .../task-view-info.component.scss | 19 - .../task-view-info.component.spec.ts | 23 - .../task-view-info.component.ts | 53 - .../task-view-labels.component.html | 39 - .../task-view-labels.component.scss | 3 - .../task-view-labels.component.spec.ts | 23 - .../task-view-labels.component.ts | 137 - .../task-view-name.component.html | 46 - .../task-view-name.component.scss | 52 - .../task-view-name.component.spec.ts | 23 - .../task-view-name.component.ts | 141 - .../task-view-notify-to-user.component.html | 56 - .../task-view-notify-to-user.component.scss | 17 - ...task-view-notify-to-user.component.spec.ts | 21 - .../task-view-notify-to-user.component.ts | 154 - .../task-view-phase.component.html | 22 - .../task-view-phase.component.scss | 0 .../task-view-phase.component.spec.ts | 21 - .../task-view-phase.component.ts | 55 - .../task-view-priority.component.html | 23 - .../task-view-priority.component.scss | 0 .../task-view-priority.component.spec.ts | 23 - .../task-view-priority.component.ts | 62 - .../task-view-sub-tasks.component.html | 90 - .../task-view-sub-tasks.component.scss | 4 - .../task-view-sub-tasks.component.spec.ts | 23 - .../task-view-sub-tasks.component.ts | 216 - .../task-view-time-log.component.html | 164 - .../task-view-time-log.component.scss | 73 - .../task-view-time-log.component.spec.ts | 23 - .../task-view-time-log.component.ts | 452 - .../task-view/task-view.component.html | 67 - .../task-view/task-view.component.scss | 0 .../task-view/task-view.component.spec.ts | 23 - .../task-view/task-view.component.ts | 334 - .../components/task-view/task-view.module.ts | 159 - .../task-view/task-view.service.spec.ts | 16 - .../components/task-view/task-view.service.ts | 170 - .../tasks-group-view.component.html | 16 - .../tasks-group-view.component.scss | 49 - .../tasks-group-view.component.spec.ts | 23 - .../tasks-group-view.component.ts | 141 - .../tasks-progress-bar.component.html | 25 - .../tasks-progress-bar.component.scss | 51 - .../tasks-progress-bar.component.spec.ts | 21 - .../tasks-progress-bar.component.ts | 35 - .../with-percentage-mark.pipe.ts | 11 - .../team-members-autocomplete.component.html | 58 - .../team-members-autocomplete.component.scss | 0 ...eam-members-autocomplete.component.spec.ts | 23 - .../team-members-autocomplete.component.ts | 211 - .../team-members-form.component.html | 99 - .../team-members-form.component.scss | 0 .../team-members-form.component.spec.ts | 23 - .../team-members-form.component.ts | 248 - .../toggle-menu-button.component.html | 9 - .../toggle-menu-button.component.scss | 0 .../toggle-menu-button.component.spec.ts | 23 - .../toggle-menu-button.component.ts | 25 - .../layout/alerts/alerts.component.html | 22 - .../layout/alerts/alerts.component.scss | 0 .../layout/alerts/alerts.component.spec.ts | 23 - .../layout/alerts/alerts.component.ts | 21 - .../layout/header/header.component.html | 194 - .../layout/header/header.component.scss | 207 - .../layout/header/header.component.spec.ts | 23 - .../layout/header/header.component.ts | 178 - .../layout/layout.component.html | 37 - .../layout/layout.component.scss | 37 - .../layout/layout.component.spec.ts | 25 - .../administrator/layout/layout.component.ts | 217 - .../licensing-alerts.component.html | 20 - .../licensing-alerts.component.scss | 0 .../licensing-alerts.component.spec.ts | 21 - .../licensing-alerts.component.ts | 68 - .../notification-template.component.html | 27 - .../notification-template.component.scss | 0 .../notification-template.component.spec.ts | 21 - .../notification-template.component.ts | 75 - .../notifications-drawer.component.html | 96 - .../notifications-drawer.component.scss | 3 - .../notifications-drawer.component.spec.ts | 23 - .../notifications-drawer.component.ts | 433 - .../tag-background.pipe.ts | 13 - .../layout/notifications-drawer/types.ts | 7 - .../task-card/task-card.component.html | 43 - .../task-card/task-card.component.scss | 0 .../task-card/task-card.component.spec.ts | 21 - .../task-card/task-card.component.ts | 15 - .../task-creation-assignees.component.html | 71 - .../task-creation-assignees.component.scss | 17 - .../task-creation-assignees.component.spec.ts | 21 - .../task-creation-assignees.component.ts | 160 - .../task-end-date.component.html | 16 - .../task-end-date.component.scss | 36 - .../task-end-date.component.spec.ts | 21 - .../task-end-date/task-end-date.component.ts | 65 - .../task-labels/task-labels.component.html | 15 - .../task-labels/task-labels.component.scss | 4 - .../task-labels/task-labels.component.spec.ts | 21 - .../task-labels/task-labels.component.ts | 94 - .../task-members/task-members.component.html | 64 - .../task-members/task-members.component.scss | 33 - .../task-members.component.spec.ts | 21 - .../task-members/task-members.component.ts | 176 - .../task-name/task-name.component.html | 19 - .../task-name/task-name.component.scss | 8 - .../task-name/task-name.component.spec.ts | 21 - .../task-name/task-name.component.ts | 79 - .../task-priority.component.html | 13 - .../task-priority.component.scss | 13 - .../task-priority.component.spec.ts | 21 - .../task-priority/task-priority.component.ts | 60 - .../task-progress.component.html | 9 - .../task-progress.component.scss | 0 .../task-progress.component.spec.ts | 21 - .../task-progress/task-progress.component.ts | 65 - .../task-subtask-count.component.html | 17 - .../task-subtask-count.component.scss | 4 - .../task-subtask-count.component.spec.ts | 21 - .../task-subtask-count.component.ts | 79 - .../kanban-board/kanban-board.component.html | 306 - .../kanban-board/kanban-board.component.scss | 401 - .../kanban-board.component.spec.ts | 21 - .../kanban-board/kanban-board.component.ts | 1068 - .../kanban-board/models/board.model.ts | 6 - .../kanban-board/models/column.model.ts | 14 - .../pipes/validate-min-date.pipe.ts | 17 - .../kanban-hash-map-service.service.ts | 69 - .../kanban-view-v2-routing.module.ts | 15 - .../kanban-view-v2/kanban-view-v2.module.ts | 121 - .../kanban-view-v2/kanban-view-v2.service.ts | 100 - .../gantt-chart-v2-routing.module.ts | 11 - .../roadmap-v2/gantt-chart-v2.module.ts | 85 - .../add-task-input.component.html | 21 - .../add-task-input.component.scss | 25 - .../add-task-input.component.spec.ts | 21 - .../add-task-input.component.ts | 160 - .../add-task-row/add-task-row.component.html | 3 - .../add-task-row/add-task-row.component.scss | 14 - .../add-task-row.component.spec.ts | 21 - .../add-task-row/add-task-row.component.ts | 149 - .../end-date/end-date.component.html | 16 - .../end-date/end-date.component.scss | 41 - .../end-date/end-date.component.spec.ts | 21 - .../components/end-date/end-date.component.ts | 84 - .../components/filters/filters.component.html | 20 - .../components/filters/filters.component.scss | 0 .../filters/filters.component.spec.ts | 21 - .../components/filters/filters.component.ts | 56 - .../start-date/start-date.component.html | 16 - .../start-date/start-date.component.scss | 33 - .../start-date/start-date.component.spec.ts | 21 - .../start-date/start-date.component.ts | 72 - .../task-bar/task-bar.component.html | 21 - .../task-bar/task-bar.component.scss | 59 - .../task-bar/task-bar.component.spec.ts | 21 - .../components/task-bar/task-bar.component.ts | 289 - .../task-name/task-name.component.html | 25 - .../task-name/task-name.component.scss | 41 - .../task-name/task-name.component.spec.ts | 21 - .../task-name/task-name.component.ts | 96 - .../directives/drag-move.directive.ts | 138 - .../project-roadmap-v2-custom.component.html | 207 - .../project-roadmap-v2-custom.component.scss | 253 - ...roject-roadmap-v2-custom.component.spec.ts | 21 - .../project-roadmap-v2-custom.component.ts | 355 - .../services/roadmap-v2-hashmap.service.ts | 208 - .../services/roadmap-v2-service.service.ts | 414 - .../modules/task-list-v2/interfaces.ts | 100 - .../task-list-v2/pipes/end-name-check.pipe.ts | 11 - .../task-list-columns-toggle.component.html | 25 - .../task-list-columns-toggle.component.scss | 10 - ...task-list-columns-toggle.component.spec.ts | 23 - .../task-list-columns-toggle.component.ts | 62 - .../task-list-filters.component.html | 169 - .../task-list-filters.component.scss | 27 - .../task-list-filters.component.spec.ts | 23 - .../task-list-filters.component.ts | 288 - .../task-list-hash-map.service.ts | 208 - .../task-list-add-task-input.component.html | 21 - .../task-list-add-task-input.component.scss | 14 - ...task-list-add-task-input.component.spec.ts | 23 - .../task-list-add-task-input.component.ts | 197 - .../task-list-bulk-actions.component.html | 214 - .../task-list-bulk-actions.component.scss | 63 - .../task-list-bulk-actions.component.spec.ts | 23 - .../task-list-bulk-actions.component.ts | 294 - .../interfaces/convert-subtask-request.ts | 6 - .../subtask-convert-service.service.ts | 19 - .../task-list-context-menu.component.html | 56 - .../task-list-context-menu.component.scss | 0 .../task-list-context-menu.component.spec.ts | 23 - .../task-list-context-menu.component.ts | 225 - .../task-list-group-settings.component.html | 76 - .../task-list-group-settings.component.scss | 57 - ...task-list-group-settings.component.spec.ts | 23 - .../task-list-group-settings.component.ts | 236 - .../task-list-header.component.html | 100 - .../task-list-header.component.scss | 100 - .../task-list-header.component.spec.ts | 23 - .../task-list-header.component.ts | 143 - .../task-list-phase-duration.component.html | 25 - .../task-list-phase-duration.component.scss | 4 - ...task-list-phase-duration.component.spec.ts | 21 - .../task-list-phase-duration.component.ts | 65 - ...-list-phase-settings-drawer.component.html | 97 - ...-list-phase-settings-drawer.component.scss | 63 - ...st-phase-settings-drawer.component.spec.ts | 21 - ...sk-list-phase-settings-drawer.component.ts | 256 - .../pipes/ellipsis-tooltip-title.pipe.ts | 13 - .../pipes/sub-tasks-arrow-color.pipe.ts | 11 - .../pipes/sub-tasks-arrow-icon.pipe.ts | 10 - .../pipes/truncate-if-long.pipe.ts | 11 - .../pipes/validate-max-date.pipe.ts | 16 - .../pipes/validate-min-date.pipe.ts | 16 - .../task-list-description.component.html | 1 - .../task-list-description.component.scss | 0 .../task-list-description.component.spec.ts | 23 - .../task-list-description.component.ts | 44 - .../task-list-end-date.component.html | 17 - .../task-list-end-date.component.scss | 30 - .../task-list-end-date.component.spec.ts | 23 - .../task-list-end-date.component.ts | 92 - .../task-list-labels.component.html | 49 - .../task-list-labels.component.scss | 36 - .../task-list-labels.component.spec.ts | 23 - .../task-list-labels.component.ts | 172 - .../task-list-members.component.html | 61 - .../task-list-members.component.scss | 17 - .../task-list-members.component.spec.ts | 23 - .../task-list-members.component.ts | 211 - .../task-list-phase.component.html | 20 - .../task-list-phase.component.scss | 0 .../task-list-phase.component.spec.ts | 21 - .../task-list-phase.component.ts | 132 - .../task-list-priority.component.html | 15 - .../task-list-priority.component.scss | 5 - .../task-list-priority.component.spec.ts | 23 - .../task-list-priority.component.ts | 132 - .../task-list-row.component.html | 222 - .../task-list-row.component.scss | 381 - .../task-list-row.component.spec.ts | 23 - .../task-list-row/task-list-row.component.ts | 299 - .../task-list-start-date.component.html | 17 - .../task-list-start-date.component.scss | 25 - .../task-list-start-date.component.spec.ts | 23 - .../task-list-start-date.component.ts | 77 - .../task-list-status.component.html | 14 - .../task-list-status.component.scss | 5 - .../task-list-status.component.spec.ts | 23 - .../task-list-status.component.ts | 143 - .../task-list-timer.component.html | 53 - .../task-list-timer.component.scss | 0 .../task-list-timer.component.spec.ts | 23 - .../task-list-timer.component.ts | 67 - .../task-progress.component.html | 10 - .../task-progress.component.scss | 0 .../task-progress.component.spec.ts | 23 - .../task-progress/task-progress.component.ts | 68 - .../task-list-table.component.html | 192 - .../task-list-table.component.scss | 395 - .../task-list-table.component.spec.ts | 23 - .../task-list-table.component.ts | 722 - .../task-list-v2-routing.module.ts | 14 - .../task-list-v2/task-list-v2.module.ts | 195 - .../task-list-v2/task-list-v2.service.ts | 418 - .../activity-log/activity-log.component.html | 20 - .../activity-log/activity-log.component.scss | 0 .../activity-log.component.spec.ts | 23 - .../activity-log/activity-log.component.ts | 58 - .../dashboard/dashboard.component.html | 58 - .../dashboard/dashboard.component.scss | 37 - .../dashboard/dashboard.component.spec.ts | 23 - .../dashboard/dashboard.component.ts | 106 - .../my-projects/my-projects.component.html | 56 - .../my-projects/my-projects.component.scss | 87 - .../my-projects/my-projects.component.spec.ts | 23 - .../my-projects/my-projects.component.ts | 83 - .../task-done/task-done.component.html | 5 - .../task-done/task-done.component.scss | 16 - .../task-done/task-done.component.spec.ts | 21 - .../task-done/task-done.component.ts | 39 - .../task-due-date.component.html | 17 - .../task-due-date.component.scss | 30 - .../task-due-date.component.spec.ts | 21 - .../task-due-date/task-due-date.component.ts | 148 - .../task-name/task-name.component.html | 27 - .../task-name/task-name.component.scss | 14 - .../task-name/task-name.component.spec.ts | 21 - .../task-name/task-name.component.ts | 54 - .../task-project/task-project.component.html | 8 - .../task-project/task-project.component.scss | 4 - .../task-project.component.spec.ts | 21 - .../task-project/task-project.component.ts | 12 - .../task-status/task-status.component.html | 13 - .../task-status/task-status.component.scss | 3 - .../task-status/task-status.component.spec.ts | 21 - .../task-status/task-status.component.ts | 71 - .../task-add-container.component.html | 95 - .../task-add-container.component.scss | 24 - .../task-add-container.component.spec.ts | 21 - .../task-add-container.component.ts | 252 - .../tasks-table/tasks-table.component.html | 54 - .../tasks-table/tasks-table.component.scss | 46 - .../tasks-table/tasks-table.component.spec.ts | 21 - .../tasks-table/tasks-table.component.ts | 223 - .../my-tasks/my-tasks.component.html | 277 - .../my-tasks/my-tasks.component.scss | 239 - .../my-tasks/my-tasks.component.spec.ts | 23 - .../dashboard/my-tasks/my-tasks.component.ts | 118 - .../personal-tasks.component.html | 37 - .../personal-tasks.component.scss | 83 - .../personal-tasks.component.spec.ts | 21 - .../personal-tasks.component.ts | 79 - .../my-dashboard/homepage-service.service.ts | 71 - .../my-dashboard/homepage.enum.ts | 12 - .../administrator/my-dashboard/intefaces.ts | 25 - .../my-dashboard-routing.module.ts | 14 - .../my-dashboard/my-dashboard.module.ts | 124 - .../personal-todo-list.component.html | 132 - .../personal-todo-list.component.scss | 18 - .../personal-todo-list.component.spec.ts | 23 - .../personal-todo-list.component.ts | 201 - .../projects-tasks.component.html | 85 - .../projects-tasks.component.scss | 0 .../projects-tasks.component.spec.ts | 23 - .../projects-tasks.component.ts | 118 - .../all-tasks-attachments.component.html | 99 - .../all-tasks-attachments.component.scss | 9 - .../all-tasks-attachments.component.spec.ts | 23 - .../all-tasks-attachments.component.ts | 139 - .../context-menu/context-menu.component.html | 28 - .../context-menu/context-menu.component.scss | 0 .../context-menu.component.spec.ts | 21 - .../context-menu/context-menu.component.ts | 149 - .../end-date/end-date.component.html | 17 - .../end-date/end-date.component.scss | 36 - .../end-date/end-date.component.spec.ts | 21 - .../components/end-date/end-date.component.ts | 90 - .../member-task-add-container.component.html | 21 - .../member-task-add-container.component.scss | 14 - ...ember-task-add-container.component.spec.ts | 21 - .../member-task-add-container.component.ts | 189 - .../member-tasks-drawer.component.html | 104 - .../member-tasks-drawer.component.scss | 40 - .../member-tasks-drawer.component.spec.ts | 21 - .../member-tasks-drawer.component.ts | 309 - .../overview-tab/overview-tab.component.html | 169 - .../overview-tab/overview-tab.component.scss | 15 - .../overview-tab.component.spec.ts | 21 - .../overview-tab/overview-tab.component.ts | 191 - .../components/phase/phase.component.html | 20 - .../components/phase/phase.component.scss | 0 .../components/phase/phase.component.spec.ts | 21 - .../components/phase/phase.component.ts | 114 - .../priority/priority.component.html | 15 - .../priority/priority.component.scss | 5 - .../priority/priority.component.spec.ts | 21 - .../components/priority/priority.component.ts | 113 - .../start-date/start-date.component.html | 17 - .../start-date/start-date.component.scss | 28 - .../start-date/start-date.component.spec.ts | 21 - .../start-date/start-date.component.ts | 78 - .../components/status/status.component.html | 14 - .../components/status/status.component.scss | 5 - .../status/status.component.spec.ts | 21 - .../components/status/status.component.ts | 122 - .../task-list-header.component.html | 36 - .../task-list-header.component.scss | 60 - .../task-list-header.component.spec.ts | 21 - .../task-list-header.component.ts | 79 - .../task-list-row.component.html | 104 - .../task-list-row.component.scss | 232 - .../task-list-row.component.spec.ts | 21 - .../task-list-row/task-list-row.component.ts | 188 - .../task-name/task-name.component.html | 15 - .../task-name/task-name.component.scss | 47 - .../task-name/task-name.component.spec.ts | 21 - .../task-name/task-name.component.ts | 88 - .../services/wl-tasks-hash-map.service.ts | 173 - .../services/wl-tasks.service.ts | 264 - .../workload-gaant-chart-v2.component.html | 248 - .../workload-gaant-chart-v2.component.scss | 316 - .../workload-gaant-chart-v2.component.spec.ts | 21 - .../workload-gaant-chart-v2.component.ts | 338 - .../project-stats.component.html | 95 - .../project-stats.component.scss | 20 - .../project-stats.component.spec.ts | 23 - .../project-stats/project-stats.component.ts | 71 - .../project-insights.component.html | 17 - .../project-insights.component.scss | 25 - .../project-insights.component.spec.ts | 23 - .../project-insights.component.ts | 73 - .../last-updated-tasks.component.html | 39 - .../last-updated-tasks.component.scss | 5 - .../last-updated-tasks.component.spec.ts | 23 - .../last-updated-tasks.component.ts | 96 - .../priority-breakdown.component.html | 14 - .../priority-breakdown.component.scss | 0 .../priority-breakdown.component.spec.ts | 23 - .../priority-breakdown.component.ts | 133 - .../project-deadline.component.html | 101 - .../project-deadline.component.scss | 20 - .../project-deadline.component.spec.ts | 23 - .../project-deadline.component.ts | 96 - .../status-overview.component.html | 35 - .../status-overview.component.scss | 7 - .../status-overview.component.spec.ts | 23 - .../status-overview.component.ts | 120 - .../member-stats/member-stats.component.html | 54 - .../member-stats/member-stats.component.scss | 20 - .../member-stats.component.spec.ts | 23 - .../member-stats/member-stats.component.ts | 64 - .../member-tasks/member-tasks.component.html | 104 - .../member-tasks/member-tasks.component.scss | 136 - .../member-tasks.component.spec.ts | 23 - .../member-tasks/member-tasks.component.ts | 155 - ...ct-insights-member-overview.component.html | 4 - ...ct-insights-member-overview.component.scss | 4 - ...insights-member-overview.component.spec.ts | 23 - ...ject-insights-member-overview.component.ts | 59 - .../project-overview.component.html | 31 - .../project-overview.component.scss | 4 - .../project-overview.component.spec.ts | 23 - .../project-overview.component.ts | 153 - .../task-insights.component.html | 228 - .../task-insights.component.scss | 9 - .../task-insights.component.spec.ts | 23 - .../task-insights/task-insights.component.ts | 232 - .../project-members.component.html | 111 - .../project-members.component.scss | 4 - .../project-members.component.spec.ts | 23 - .../project-members.component.ts | 147 - .../project-updates.component.html | 9 - .../project-updates.component.scss | 5 - .../project-updates.component.spec.ts | 21 - .../project-updates.component.ts | 27 - .../project-sharing-functions.ts | 44 - .../project-sharing/template.html | 49 - .../project-view-extra.component.html | 45 - .../project-view-extra.component.scss | 6 - .../project-view-extra.component.spec.ts | 23 - .../project-view-extra.component.ts | 171 - .../project-view/project-view.component.html | 208 - .../project-view/project-view.component.scss | 38 - .../project-view.component.spec.ts | 23 - .../project-view/project-view.component.ts | 500 - .../projects/projects-routing.module.ts | 18 - .../administrator/projects/projects.module.ts | 250 - .../projects/projects.service.spec.ts | 16 - .../projects/projects.service.ts | 21 - .../project-filter-by-tooltip.pipe.spec.ts | 8 - .../pipes/project-filter-by-tooltip.pipe.ts | 10 - ...projects-folder-form-drawer.component.html | 45 - ...projects-folder-form-drawer.component.scss | 0 ...jects-folder-form-drawer.component.spec.ts | 21 - .../projects-folder-form-drawer.component.ts | 133 - .../projects-folder-form-drawer.service.ts | 20 - .../projects-folder-view.component.html | 1 - .../projects-folder-view.component.scss | 0 .../projects-folder-view.component.spec.ts | 21 - .../projects-folder-view.component.ts | 10 - .../projects-list-view.component.html | 1 - .../projects-list-view.component.scss | 0 .../projects-list-view.component.spec.ts | 21 - .../projects-list-view.component.ts | 10 - .../projects/projects/projects.component.html | 276 - .../projects/projects/projects.component.scss | 21 - .../projects/projects.component.spec.ts | 25 - .../projects/projects/projects.component.ts | 353 - .../estimated-vs-actual-chart.component.html | 13 - .../estimated-vs-actual-chart.component.scss | 19 - ...stimated-vs-actual-chart.component.spec.ts | 21 - .../estimated-vs-actual-chart.component.ts | 85 - .../member-logs-breakdown.component.html | 1 - .../member-logs-breakdown.component.scss | 0 .../member-logs-breakdown.component.spec.ts | 21 - .../member-logs-breakdown.component.ts | 10 - .../project-category.component.html | 42 - .../project-category.component.scss | 43 - .../project-category.component.spec.ts | 21 - .../project-category.component.ts | 187 - .../project-health.component.html | 11 - .../project-health.component.scss | 0 .../project-health.component.spec.ts | 21 - .../project-health.component.ts | 56 - .../project-logs-breakdown.component.html | 100 - .../project-logs-breakdown.component.scss | 3 - .../project-logs-breakdown.component.spec.ts | 21 - .../project-logs-breakdown.component.ts | 208 - .../project-start-end-dates.component.html | 20 - .../project-start-end-dates.component.scss | 0 .../project-start-end-dates.component.spec.ts | 21 - .../project-start-end-dates.component.ts | 73 - .../project-status.component.html | 10 - .../project-status.component.scss | 10 - .../project-status.component.spec.ts | 21 - .../project-status.component.ts | 61 - .../rpt-header/rpt-header.component.html | 65 - .../rpt-header/rpt-header.component.scss | 0 .../rpt-header/rpt-header.component.spec.ts | 21 - .../rpt-header/rpt-header.component.ts | 122 - .../rpt-layout/rpt-layout.component.html | 51 - .../rpt-layout/rpt-layout.component.scss | 114 - .../rpt-layout/rpt-layout.component.spec.ts | 21 - .../rpt-layout/rpt-layout.component.ts | 73 - .../rpt-drawer-title.component.html | 5 - .../rpt-drawer-title.component.scss | 8 - .../rpt-drawer-title.component.spec.ts | 21 - .../rpt-drawer-title.component.ts | 16 - .../rpt-flat-task-list.component.html | 61 - .../rpt-flat-task-list.component.scss | 4 - .../rpt-flat-task-list.component.spec.ts | 21 - .../rpt-flat-task-list.component.ts | 194 - .../rpt-flat-tasks-list.module.ts | 44 - .../rpt-grouped-task-list.component.html | 73 - .../rpt-grouped-task-list.component.scss | 4 - .../rpt-grouped-task-list.component.spec.ts | 21 - .../rpt-grouped-task-list.component.ts | 132 - .../rpt-grouped-task-list.module.ts | 46 - .../rpt-member-projects-list.component.html | 50 - .../rpt-member-projects-list.component.scss | 0 ...rpt-member-projects-list.component.spec.ts | 21 - .../rpt-member-projects-list.component.ts | 81 - .../rpt-projects-list.component.html | 168 - .../rpt-projects-list.component.scss | 27 - .../rpt-projects-list.component.spec.ts | 21 - .../rpt-projects-list.component.ts | 242 - .../rpt-projects-list.module.ts | 63 - .../drawers/reporting-drawers.service.ts | 132 - .../rpt-member-drawer-overview.component.html | 164 - .../rpt-member-drawer-overview.component.scss | 14 - ...t-member-drawer-overview.component.spec.ts | 21 - .../rpt-member-drawer-overview.component.ts | 194 - .../rpt-member-drawer-projects.component.html | 6 - .../rpt-member-drawer-projects.component.scss | 0 ...t-member-drawer-projects.component.spec.ts | 21 - .../rpt-member-drawer-projects.component.ts | 20 - .../rpt-member-drawer-tasks.component.html | 5 - .../rpt-member-drawer-tasks.component.scss | 0 .../rpt-member-drawer-tasks.component.spec.ts | 21 - .../rpt-member-drawer-tasks.component.ts | 12 - .../rpt-member-drawer.component.html | 69 - .../rpt-member-drawer.component.scss | 0 .../rpt-member-drawer.component.spec.ts | 21 - .../rpt-member-drawer.component.ts | 75 - .../rpt-member-drawer.module.ts | 70 - .../rpt-project-drawer-members.component.html | 45 - .../rpt-project-drawer-members.component.scss | 0 ...t-project-drawer-members.component.spec.ts | 21 - .../rpt-project-drawer-members.component.ts | 65 - ...rpt-project-drawer-overview.component.html | 204 - ...rpt-project-drawer-overview.component.scss | 25 - ...-project-drawer-overview.component.spec.ts | 21 - .../rpt-project-drawer-overview.component.ts | 193 - .../rpt-project-drawer-tasks.component.html | 3 - .../rpt-project-drawer-tasks.component.scss | 0 ...rpt-project-drawer-tasks.component.spec.ts | 21 - .../rpt-project-drawer-tasks.component.ts | 11 - .../rpt-project-drawer.component.html | 63 - .../rpt-project-drawer.component.scss | 0 .../rpt-project-drawer.component.spec.ts | 21 - .../rpt-project-drawer.component.ts | 75 - .../rpt-project-drawer.module.ts | 71 - .../activity-logs.component.html | 131 - .../activity-logs.component.scss | 25 - .../activity-logs.component.spec.ts | 21 - .../activity-logs/activity-logs.component.ts | 128 - .../duration-filter.component.html | 34 - .../duration-filter.component.scss | 0 .../duration-filter.component.spec.ts | 21 - .../duration-filter.component.ts | 66 - ...ngle-member-drawer-overview.component.html | 181 - ...ngle-member-drawer-overview.component.scss | 16 - ...e-member-drawer-overview.component.spec.ts | 21 - ...single-member-drawer-overview.component.ts | 283 - .../service/log-header.service.ts | 34 - .../single-member-time-logs.component.html | 38 - .../single-member-time-logs.component.scss | 18 - .../single-member-time-logs.component.spec.ts | 21 - .../single-member-time-logs.component.ts | 139 - .../rpt-single-member-drawer.component.html | 79 - .../rpt-single-member-drawer.component.scss | 0 ...rpt-single-member-drawer.component.spec.ts | 21 - .../rpt-single-member-drawer.component.ts | 268 - .../rpt-single-member-drawer.module.ts | 79 - ...ngle-member-projects-drawer.component.html | 96 - ...ngle-member-projects-drawer.component.scss | 8 - ...e-member-projects-drawer.component.spec.ts | 21 - ...single-member-projects-drawer.component.ts | 84 - .../rpt-single-member-stat.component.html | 84 - .../rpt-single-member-stat.component.scss | 11 - .../rpt-single-member-stat.component.spec.ts | 21 - .../rpt-single-member-stat.component.ts | 146 - .../rpt-task-view-drawer.component.html | 6 - .../rpt-task-view-drawer.component.scss | 0 .../rpt-task-view-drawer.component.spec.ts | 21 - .../rpt-task-view-drawer.component.ts | 82 - .../rpt-task-view-drawer.module.ts | 19 - .../rpt-tasks-drawer.component.html | 49 - .../rpt-tasks-drawer.component.scss | 0 .../rpt-tasks-drawer.component.spec.ts | 21 - .../rpt-tasks-drawer.component.ts | 61 - .../rpt-tasks-drawer.module.ts | 37 - .../rpt-team-drawer-members.component.html | 36 - .../rpt-team-drawer-members.component.scss | 0 .../rpt-team-drawer-members.component.spec.ts | 21 - .../rpt-team-drawer-members.component.ts | 71 - .../rpt-team-drawer-projects.component.html | 6 - .../rpt-team-drawer-projects.component.scss | 0 ...rpt-team-drawer-projects.component.spec.ts | 21 - .../rpt-team-drawer-projects.component.ts | 16 - .../rpt-team-drawer.component.html | 59 - .../rpt-team-drawer.component.scss | 0 .../rpt-team-drawer.component.spec.ts | 21 - .../rpt-team-drawer.component.ts | 89 - .../rpt-team-drawer/rpt-team-drawer.module.ts | 63 - .../rpt-team-overview.component.html | 174 - .../rpt-team-overview.component.scss | 9 - .../rpt-team-overview.component.spec.ts | 21 - .../rpt-team-overview.component.ts | 181 - .../app/administrator/reporting/interfaces.ts | 422 - .../rpt-allocation-routing.module.ts | 14 - .../rpt-allocation/rpt-allocation.module.ts | 52 - .../rpt-allocation.component.html | 160 - .../rpt-allocation.component.scss | 95 - .../rpt-allocation.component.spec.ts | 21 - .../rpt-allocation.component.ts | 301 - .../rpt-members/rpt-members-routing.module.ts | 14 - .../modules/rpt-members/rpt-members.module.ts | 77 - .../rpt-members/rpt-members.component.html | 137 - .../rpt-members/rpt-members.component.scss | 0 .../rpt-members/rpt-members.component.spec.ts | 21 - .../rpt-members/rpt-members.component.ts | 381 - .../rpt-overview-routing.module.ts | 14 - .../rpt-overview/rpt-overview.module.ts | 81 - .../rpt-overview-cards.component.html | 54 - .../rpt-overview-cards.component.scss | 3 - .../rpt-overview-cards.component.spec.ts | 21 - .../rpt-overview-cards.component.ts | 48 - .../rpt-overview/rpt-overview.component.html | 38 - .../rpt-overview/rpt-overview.component.scss | 0 .../rpt-overview.component.spec.ts | 21 - .../rpt-overview/rpt-overview.component.ts | 66 - .../rpt-projects-routing.module.ts | 14 - .../rpt-projects/rpt-projects.module.ts | 97 - ...rojects-estimated-vs-actual.component.html | 11 - ...rojects-estimated-vs-actual.component.scss | 16 - ...ects-estimated-vs-actual.component.spec.ts | 21 - ...-projects-estimated-vs-actual.component.ts | 86 - .../rpt-projects/rpt-projects.component.html | 297 - .../rpt-projects/rpt-projects.component.scss | 27 - .../rpt-projects.component.spec.ts | 21 - .../rpt-projects/rpt-projects.component.ts | 400 - ...ime-estimation-vs-actual-routing.module.ts | 17 - .../rpt-time-estimation-vs-actual.module.ts | 53 - ...timation-vs-actual-projects.component.html | 137 - ...timation-vs-actual-projects.component.scss | 11 - ...ation-vs-actual-projects.component.spec.ts | 21 - ...estimation-vs-actual-projects.component.ts | 431 - .../rpt-time-members-routing.module.ts | 15 - .../rpt-time-members.module.ts | 43 - .../time-members/time-members.component.html | 127 - .../time-members/time-members.component.scss | 6 - .../time-members.component.spec.ts | 21 - .../time-members/time-members.component.ts | 417 - .../rpt-time-projects-routing.module.ts | 15 - .../rpt-time-projects.module.ts | 52 - .../time-projects.component.html | 129 - .../time-projects.component.scss | 6 - .../time-projects.component.spec.ts | 21 - .../time-projects/time-projects.component.ts | 420 - .../reporting/pipes/with-count.pipe.spec.ts | 8 - .../reporting/pipes/with-count.pipe.ts | 10 - .../reporting/reporting-api.service.ts | 179 - .../reporting/reporting-routing.module.ts | 58 - .../reporting/reporting-service.service.ts | 36 - .../reporting/reporting.module.ts | 127 - .../reporting/reporting.service.ts | 122 - .../project-schedule.component.html | 164 - .../project-schedule.component.scss | 220 - .../project-schedule.component.spec.ts | 23 - .../project-schedule.component.ts | 116 - .../schedule/schedule-routing.module.ts | 14 - .../add-member-allocation.component.html | 3 - .../add-member-allocation.component.scss | 14 - .../add-member-allocation.component.spec.ts | 21 - .../add-member-allocation.component.ts | 105 - .../context-menu/context-menu.component.html | 8 - .../context-menu/context-menu.component.scss | 0 .../context-menu.component.spec.ts | 21 - .../context-menu/context-menu.component.ts | 67 - .../end-date/end-date.component.html | 17 - .../end-date/end-date.component.scss | 36 - .../end-date/end-date.component.spec.ts | 21 - .../components/end-date/end-date.component.ts | 90 - .../member-indicator.component.html | 13 - .../member-indicator.component.scss | 54 - .../member-indicator.component.spec.ts | 21 - .../member-indicator.component.ts | 159 - .../member-task-add-container.component.html | 21 - .../member-task-add-container.component.scss | 14 - ...ember-task-add-container.component.spec.ts | 21 - .../member-task-add-container.component.ts | 193 - .../components/phase/phase.component.html | 20 - .../components/phase/phase.component.scss | 0 .../components/phase/phase.component.spec.ts | 21 - .../components/phase/phase.component.ts | 114 - .../priority/priority.component.html | 15 - .../priority/priority.component.scss | 5 - .../priority/priority.component.spec.ts | 21 - .../components/priority/priority.component.ts | 114 - .../project-indicator.component.html | 8 - .../project-indicator.component.scss | 10 - .../project-indicator.component.spec.ts | 21 - .../project-indicator.component.ts | 33 - ...project-member-tasks-drawer.component.html | 92 - ...project-member-tasks-drawer.component.scss | 40 - ...ject-member-tasks-drawer.component.spec.ts | 21 - .../project-member-tasks-drawer.component.ts | 282 - .../start-date/start-date.component.html | 17 - .../start-date/start-date.component.scss | 28 - .../start-date/start-date.component.spec.ts | 21 - .../start-date/start-date.component.ts | 79 - .../components/status/status.component.html | 14 - .../components/status/status.component.scss | 5 - .../status/status.component.spec.ts | 21 - .../components/status/status.component.ts | 122 - .../task-add-input.component.html | 21 - .../task-add-input.component.scss | 25 - .../task-add-input.component.spec.ts | 21 - .../task-add-input.component.ts | 156 - .../task-add-row/task-add-row.component.html | 3 - .../task-add-row/task-add-row.component.scss | 14 - .../task-add-row.component.spec.ts | 21 - .../task-add-row/task-add-row.component.ts | 76 - .../task-list-header.component.html | 36 - .../task-list-header.component.scss | 60 - .../task-list-header.component.spec.ts | 21 - .../task-list-header.component.ts | 53 - .../task-list-row.component.html | 83 - .../task-list-row.component.scss | 232 - .../task-list-row.component.spec.ts | 21 - .../task-list-row/task-list-row.component.ts | 193 - .../task-name/task-name.component.html | 15 - .../task-name/task-name.component.scss | 47 - .../task-name/task-name.component.spec.ts | 21 - .../task-name/task-name.component.ts | 85 - .../tasks-context-menu.component.html | 28 - .../tasks-context-menu.component.scss | 0 .../tasks-context-menu.component.spec.ts | 21 - .../tasks-context-menu.component.ts | 151 - .../projects-schedule.component.html | 149 - .../projects-schedule.component.scss | 261 - .../projects-schedule.component.spec.ts | 21 - .../projects-schedule.component.ts | 225 - .../project-schedule-service.service.ts | 136 - ...le-member-tasks-hashmap-service.service.ts | 172 - .../schedule-member-tasks-service.service.ts | 264 - .../service/scheduler-common.service.ts | 35 - .../schedule-view.component.html | 36 - .../schedule-view.component.scss | 3 - .../schedule-view.component.spec.ts | 23 - .../schedule-view/schedule-view.component.ts | 64 - .../administrator/schedule/schedule.module.ts | 120 - .../team-schedule.component.html | 155 - .../team-schedule.component.scss | 220 - .../team-schedule.component.spec.ts | 23 - .../team-schedule/team-schedule.component.ts | 113 - .../categories/categories-routing.module.ts | 16 - .../settings/categories/categories.module.ts | 41 - .../categories/categories.component.html | 68 - .../categories/categories.component.scss | 0 .../categories/categories.component.spec.ts | 21 - .../categories/categories.component.ts | 95 - .../change-password.component.html | 68 - .../change-password.component.scss | 3 - .../change-password.component.spec.ts | 23 - .../change-password.component.ts | 68 - .../clients/clients-routing.module.ts | 14 - .../settings/clients/clients.module.ts | 58 - .../clients/clients/clients.component.html | 93 - .../clients/clients/clients.component.scss | 0 .../clients/clients/clients.component.spec.ts | 25 - .../clients/clients/clients.component.ts | 181 - .../job-titles/job-titles-routing.module.ts | 14 - .../settings/job-titles/job-titles.module.ts | 57 - .../job-titles/job-titles.component.html | 86 - .../job-titles/job-titles.component.scss | 0 .../job-titles/job-titles.component.spec.ts | 25 - .../job-titles/job-titles.component.ts | 183 - .../settings/labels/labels.component.html | 67 - .../settings/labels/labels.component.scss | 0 .../settings/labels/labels.component.spec.ts | 23 - .../settings/labels/labels.component.ts | 96 - .../language-and-region.component.html | 35 - .../language-and-region.component.scss | 0 .../language-and-region.component.spec.ts | 23 - .../language-and-region.component.ts | 70 - .../notification-settings-routing.module.ts | 14 - .../notification-settings.module.ts | 28 - .../notification-settings.component.html | 67 - .../notification-settings.component.scss | 8 - .../notification-settings.component.spec.ts | 23 - .../notification-settings.component.ts | 79 - .../settings/profile/profile.component.html | 59 - .../settings/profile/profile.component.scss | 15 - .../profile/profile.component.spec.ts | 23 - .../settings/profile/profile.component.ts | 133 - .../add-task-input.component.html | 21 - .../add-task-input.component.scss | 0 .../add-task-input.component.spec.ts | 21 - .../add-task-input.component.ts | 173 - .../context-menu/context-menu.component.html | 29 - .../context-menu/context-menu.component.scss | 0 .../context-menu.component.spec.ts | 21 - .../context-menu/context-menu.component.ts | 126 - .../group-filter/group-filter.component.html | 64 - .../group-filter/group-filter.component.scss | 0 .../group-filter.component.spec.ts | 21 - .../group-filter/group-filter.component.ts | 109 - .../phase-settings-drawer.component.html | 78 - .../phase-settings-drawer.component.scss | 0 .../phase-settings-drawer.component.spec.ts | 21 - .../phase-settings-drawer.component.ts | 197 - .../task-description.component.html | 34 - .../task-description.component.scss | 36 - .../task-description.component.spec.ts | 21 - .../task-description.component.ts | 88 - .../task-end-date.component.html | 17 - .../task-end-date.component.scss | 0 .../task-end-date.component.spec.ts | 21 - .../task-end-date/task-end-date.component.ts | 74 - .../task-estimation.component.html | 41 - .../task-estimation.component.scss | 0 .../task-estimation.component.spec.ts | 21 - .../task-estimation.component.ts | 76 - .../task-labels/task-labels.component.html | 49 - .../task-labels/task-labels.component.scss | 36 - .../task-labels/task-labels.component.spec.ts | 21 - .../row/task-labels/task-labels.component.ts | 167 - .../row/task-phase/task-phase.component.html | 20 - .../row/task-phase/task-phase.component.scss | 0 .../task-phase/task-phase.component.spec.ts | 21 - .../row/task-phase/task-phase.component.ts | 130 - .../task-priority.component.html | 15 - .../task-priority.component.scss | 0 .../task-priority.component.spec.ts | 21 - .../task-priority/task-priority.component.ts | 127 - .../task-start-date.component.html | 18 - .../task-start-date.component.scss | 0 .../task-start-date.component.spec.ts | 21 - .../task-start-date.component.ts | 63 - .../task-status/task-status.component.html | 14 - .../task-status/task-status.component.scss | 3 - .../task-status/task-status.component.spec.ts | 21 - .../row/task-status/task-status.component.ts | 127 - .../status-settings-drawer.component.html | 41 - .../status-settings-drawer.component.scss | 0 .../status-settings-drawer.component.spec.ts | 21 - .../status-settings-drawer.component.ts | 149 - .../task-list-group-settings.component.html | 58 - .../task-list-group-settings.component.scss | 42 - ...task-list-group-settings.component.spec.ts | 21 - .../task-list-group-settings.component.ts | 215 - .../task-list-header.component.html | 60 - .../task-list-header.component.scss | 84 - .../task-list-header.component.spec.ts | 21 - .../task-list-header.component.ts | 83 - .../template-name.component.html | 10 - .../template-name.component.scss | 27 - .../template-name.component.spec.ts | 21 - .../template-name/template-name.component.ts | 87 - .../project-template-edit-view/interfaces.ts | 111 - .../pipes/ellipsis-tooltip-title.pipe.ts | 13 - .../pipes/end-name-check.pipe.ts | 11 - .../pipes/sub-tasks-arrow-color.pipe.ts | 11 - .../pipes/sub-tasks-arrow-icon.pipe.ts | 10 - .../pipes/truncate-if-long.pipe.ts | 11 - .../project-template-edit-view.component.html | 147 - .../project-template-edit-view.component.scss | 403 - ...oject-template-edit-view.component.spec.ts | 21 - .../project-template-edit-view.component.ts | 488 - .../services/pt-task-list-hash-map.service.ts | 205 - .../services/pt-task-list.service.ts | 308 - .../task-list-row.component.html | 120 - .../task-list-row.component.scss | 360 - .../task-list-row.component.spec.ts | 21 - .../task-list-row/task-list-row.component.ts | 194 - .../project-templates.component.html | 37 - .../project-templates.component.scss | 0 .../project-templates.component.spec.ts | 21 - .../project-templates.component.ts | 63 - .../settings/settings-routing.module.ts | 64 - .../administrator/settings/settings.module.ts | 182 - .../settings/settings.service.spec.ts | 16 - .../settings/settings.service.ts | 19 - .../settings/settings/settings.component.html | 23 - .../settings/settings/settings.component.scss | 3 - .../settings/settings.component.spec.ts | 25 - .../settings/settings/settings.component.ts | 57 - .../task-templates.component.html | 43 - .../task-templates.component.scss | 0 .../task-templates.component.spec.ts | 23 - .../task-templates.component.ts | 86 - .../team-members/team-members.component.html | 123 - .../team-members/team-members.component.scss | 0 .../team-members.component.spec.ts | 23 - .../team-members/team-members.component.ts | 168 - .../settings/teams/teams.component.html | 90 - .../settings/teams/teams.component.scss | 4 - .../settings/teams/teams.component.spec.ts | 23 - .../settings/teams/teams.component.ts | 94 - .../src/app/app-routing.module.ts | 34 - worklenz-frontend/src/app/app.component.html | 1 - worklenz-frontend/src/app/app.component.scss | 0 .../src/app/app.component.spec.ts | 35 - worklenz-frontend/src/app/app.component.ts | 44 - worklenz-frontend/src/app/app.module.ts | 68 - .../src/app/auth/auth-routing.module.ts | 34 - worklenz-frontend/src/app/auth/auth.module.ts | 56 - .../src/app/auth/layout/layout.component.html | 19 - .../src/app/auth/layout/layout.component.scss | 25 - .../app/auth/layout/layout.component.spec.ts | 25 - .../src/app/auth/layout/layout.component.ts | 42 - .../src/app/auth/login/login.component.html | 62 - .../src/app/auth/login/login.component.scss | 11 - .../app/auth/login/login.component.spec.ts | 25 - .../src/app/auth/login/login.component.ts | 150 - .../reset-password.component.html | 55 - .../reset-password.component.scss | 0 .../reset-password.component.spec.ts | 25 - .../reset-password.component.ts | 78 - .../src/app/auth/signup/signup.component.html | 79 - .../src/app/auth/signup/signup.component.scss | 0 .../app/auth/signup/signup.component.spec.ts | 25 - .../src/app/auth/signup/signup.component.ts | 170 - .../auth/team-name/team-name.component.html | 19 - .../auth/team-name/team-name.component.scss | 0 .../team-name/team-name.component.spec.ts | 25 - .../app/auth/team-name/team-name.component.ts | 84 - .../verify-reset-email.component.html | 51 - .../verify-reset-email.component.scss | 0 .../verify-reset-email.component.spec.ts | 23 - .../verify-reset-email.component.ts | 74 - .../authenticate/authenticate.component.html | 1 - .../authenticate/authenticate.component.scss | 0 .../authenticate.component.spec.ts | 25 - .../authenticate/authenticate.component.ts | 57 - .../infinite-scroll-trigger.directive.spec.ts | 9 - .../infinite-scroll-trigger.directive.ts | 100 - .../app/directives/lazy-for.directive.spec.ts | 8 - .../src/app/directives/lazy-for.directive.ts | 194 - .../not-authorized.component.html | 5 - .../not-authorized.component.scss | 0 .../not-authorized.component.spec.ts | 23 - .../not-authorized.component.ts | 19 - .../errors/not-found/not-found.component.html | 5 - .../errors/not-found/not-found.component.scss | 0 .../not-found/not-found.component.spec.ts | 23 - .../errors/not-found/not-found.component.ts | 19 - .../src/app/guards/auth.guard.spec.ts | 16 - .../src/app/guards/auth.guard.ts | 30 - .../src/app/guards/login-check.guard.spec.ts | 16 - .../src/app/guards/login-check.guard.ts | 29 - .../guards/non-google-account.guard.spec.ts | 16 - .../app/guards/non-google-account.guard.ts | 28 - .../src/app/guards/team-member.guard.spec.ts | 16 - .../src/app/guards/team-member.guard.ts | 31 - .../src/app/guards/team-name.guard.spec.ts | 16 - .../src/app/guards/team-name.guard.ts | 29 - .../team-owner-or-admin-guard.service.ts | 30 - .../guards/team-owner-or-admin.guard.spec.ts | 16 - .../src/app/guards/team-owner.guard.spec.ts | 16 - .../src/app/guards/team-owner.guard.ts | 30 - .../src/app/guards/team-url.guard.spec.ts | 16 - .../src/app/guards/team-url.guard.ts | 19 - .../app/interceptors/http.interceptor.spec.ts | 16 - .../src/app/interceptors/http.interceptor.ts | 80 - .../src/app/interfaces/account-center.ts | 193 - .../app/interfaces/allocation-view-model.ts | 17 - .../app/interfaces/api-models/activity-log.ts | 5 - .../api-models/activity-logs-get-response.ts | 59 - .../api-models/authorize-response.ts | 8 - .../api-models/bulk-assign-request.ts | 12 - .../api-models/bulk-delete-tasks-request.ts | 2 - .../api-models/bulk-tasks-archive-request.ts | 4 - .../api-models/bulk-tasks-delete-request.ts | 3 - .../api-models/bulk-tasks-delete-response.ts | 7 - .../api-models/bulk-tasks-labels-request.ts | 7 - .../bulk-tasks-phase-change-request.ts | 4 - .../bulk-tasks-priority-change-request.ts | 4 - .../bulk-tasks-status-change-request.ts | 4 - .../api-models/client-view-model.ts | 5 - .../api-models/clients-view-model.ts | 6 - .../src/app/interfaces/api-models/gantt.ts | 57 - .../interfaces/api-models/inline-member.ts | 8 - .../api-models/job-titles-view-model.ts | 6 - .../interfaces/api-models/local-session.ts | 28 - .../my-dashboard-all-tasks-view-model.ts | 33 - .../organization-team-get-request.ts | 7 - .../organization-users-get-request.ts | 6 - .../project-comment-create-request.ts | 10 - .../api-models/project-create-request.ts | 6 - .../interfaces/api-models/project-insights.ts | 54 - .../api-models/project-members-view-model.ts | 18 - .../api-models/project-tasks-view-model.ts | 89 - .../interfaces/api-models/project-template.ts | 45 - .../api-models/project-view-model.ts | 43 - .../api-models/projects-get-response.ts | 19 - .../api-models/projects-view-model.ts | 6 - .../app/interfaces/api-models/reporting.ts | 24 - .../api-models/reset-password-request.ts | 3 - .../api-models/task-attachment-view-model.ts | 16 - .../interfaces/api-models/task-attachment.ts | 8 - .../api-models/task-comment-view-model.ts | 8 - .../task-comments-create-request.ts.ts | 5 - .../api-models/task-create-request.ts | 25 - .../api-models/task-get-response.ts | 23 - .../interfaces/api-models/task-list-config.ts | 18 - .../task-priorities-get-response.ts | 5 - .../api-models/task-status-get-response.ts | 6 - .../api-models/task-template-get-response.ts | 7 - .../api-models/task-templates-get-response.ts | 5 - .../api-models/team-activate-response.ts | 3 - .../api-models/team-get-response.ts | 7 - .../api-models/team-invitation-view-model.ts | 6 - .../api-models/team-member-create-request.ts | 7 - .../api-models/team-members-get-response.ts | 78 - .../api-models/team-members-view-model.ts | 6 - .../interfaces/api-models/team-view-model.ts | 9 - .../api-models/user-login-request.ts | 6 - .../api-models/user-login-response.ts | 4 - .../api-models/user-sign-up-response.ts | 4 - .../api-models/verify-reset-email.ts | 5 - .../src/app/interfaces/client.ts | 7 - .../src/app/interfaces/gantt-chart.ts | 69 - .../src/app/interfaces/invitation-response.ts | 5 - .../src/app/interfaces/job-title.ts | 5 - .../src/app/interfaces/my-tasks.ts | 13 - .../src/app/interfaces/nav-item-type.ts | 4 - .../src/app/interfaces/nav-item.ts | 8 - .../src/app/interfaces/notification.ts | 14 - .../app/interfaces/pagination-component.ts | 13 - .../app/interfaces/password-strength-check.ts | 76 - .../interfaces/password-validity-result.ts | 6 - .../src/app/interfaces/personal-overview.ts | 13 - .../src/app/interfaces/project-comments.ts | 11 - .../src/app/interfaces/project-folder.ts | 11 - .../src/app/interfaces/project-health.ts | 8 - .../src/app/interfaces/project-manager.ts | 6 - .../src/app/interfaces/project-template.ts | 17 - .../project-wise-resources-view-model.ts | 56 - .../src/app/interfaces/project.ts | 44 - .../reporting-allocation-settings.ts | 5 - .../src/app/interfaces/reporting.ts | 219 - .../src/app/interfaces/roadmap.ts | 36 - worklenz-frontend/src/app/interfaces/role.ts | 7 - .../src/app/interfaces/schedular.ts | 92 - .../src/app/interfaces/selectable-category.ts | 5 - .../src/app/interfaces/selectable-project.ts | 5 - .../src/app/interfaces/selectable-team.ts | 5 - .../interfaces/settings-navigation-item.ts | 5 - .../src/app/interfaces/sub-task.ts | 18 - .../task-assignee-update-response.ts | 13 - .../src/app/interfaces/task-assignee.ts | 6 - .../src/app/interfaces/task-comment.ts | 9 - .../app/interfaces/task-form-view-model.ts | 57 - .../src/app/interfaces/task-list-column.ts | 8 - .../task-list-estimation-change-response.ts | 8 - .../task-list-status-change-response.ts | 14 - .../src/app/interfaces/task-priority.ts | 6 - .../src/app/interfaces/task-status.ts | 23 - worklenz-frontend/src/app/interfaces/task.ts | 48 - worklenz-frontend/src/app/interfaces/team.ts | 21 - .../interfaces/timer-start-event-response.ts | 5 - .../interfaces/timer-stop-event-response.ts | 4 - .../src/app/interfaces/todo-list-item.ts | 10 - worklenz-frontend/src/app/interfaces/user.ts | 6 - .../app/interfaces/worklenz-notification.ts | 14 - .../src/app/interfaces/workload.ts | 91 - .../src/app/pipes/bind-na.pipe.spec.ts | 8 - .../src/app/pipes/bind-na.pipe.ts | 11 - .../src/app/pipes/date-formatter.pipe.ts | 35 - .../src/app/pipes/ellipsis.pipe.spec.ts | 8 - .../src/app/pipes/ellipsis.pipe.ts | 15 - .../app/pipes/first-char-upper.pipe.spec.ts | 8 - .../src/app/pipes/first-char-upper.pipe.ts | 12 - .../src/app/pipes/from-now.pipe.spec.ts | 8 - .../src/app/pipes/from-now.pipe.ts | 13 - .../src/app/pipes/min-date-validator.pipe.ts | 17 - .../src/app/pipes/safe-string.pipe.spec.ts | 8 - .../src/app/pipes/safe-string.pipe.ts | 16 - .../src/app/pipes/search-by-name.pipe.spec.ts | 8 - .../src/app/pipes/search-by-name.pipe.ts | 16 - .../pipes/task-comment-mention.pipe.spec.ts | 8 - .../app/pipes/task-comment-mention.pipe.ts | 29 - .../src/app/pipes/to-now.pipe.spec.ts | 8 - .../src/app/pipes/to-now.pipe.ts | 13 - .../src/app/pipes/with-alpha.pipe.spec.ts | 8 - .../src/app/pipes/with-alpha.pipe.ts | 13 - .../src/app/pipes/wl-safe-array.pipe.spec.ts | 8 - .../src/app/pipes/wl-safe-array.pipe.ts | 11 - .../src/app/routes/account-setup-routes.tsx | 9 + .../src/app/routes/admin-center-routes.tsx | 32 + .../src/app/routes/auth-routes.tsx | 51 + worklenz-frontend/src/app/routes/index.tsx | 230 + .../src/app/routes/main-routes.tsx | 64 + .../src/app/routes/not-found-route.tsx | 10 + .../src/app/routes/protected-routes.tsx | 21 + .../src/app/routes/reporting-routes.tsx | 28 + .../src/app/routes/root-routes.tsx | 10 + .../src/app/routes/settings-routes.tsx | 32 + .../api/access-controls-api.service.ts | 8 - .../api/account-center-api.service.ts | 90 - .../api/activity-logs.service.spec.ts | 16 - .../app/services/api/activity-logs.service.ts | 22 - .../src/app/services/api/api-service-base.ts | 24 - .../services/api/attachments-api.service.ts | 47 - .../src/app/services/api/auth-api.service.ts | 57 - .../app/services/api/clients-api.service.ts | 43 - .../services/api/gantt-api.service.spec.ts | 16 - .../src/app/services/api/gantt-api.service.ts | 32 - .../app/services/api/home-page-api.service.ts | 55 - .../services/api/job-titles-api.service.ts | 44 - .../src/app/services/api/logs-api.service.ts | 22 - .../services/api/notifications-api.service.ts | 37 - .../services/api/personal-overview.service.ts | 36 - .../api/profile-settings-api.service.ts | 49 - .../api/project-categories-api.service.ts | 44 - .../api/project-categories.service.spec.ts | 16 - .../api/project-comments-api.service.ts | 50 - .../api/project-folders-api.service.spec.ts | 16 - .../api/project-folders-api.service.ts | 39 - .../api/project-healths-api.service.ts | 23 - .../services/api/project-insights.service.ts | 72 - .../api/project-managers-api.service.ts | 24 - .../api/project-members-api.service.ts | 38 - .../api/project-roadmap-api.service.ts | 31 - .../api/project-statuses-api.service.ts | 22 - .../api/project-template-api.service.ts | 61 - .../api/project-workload-api.service.ts | 46 - .../app/services/api/projects-api.service.ts | 126 - .../app/services/api/pt-labels-api.service.ts | 25 - .../services/api/pt-priorities-api.service.ts | 26 - .../services/api/pt-statuses-api.service.ts | 49 - .../api/pt-task-phases-api.service.ts | 55 - .../app/services/api/pt-tasks-api.service.ts | 41 - .../services/api/reporting-api-v0.service.ts | 187 - .../api/reporting-export-api.service.ts | 154 - .../api/resource-allocation.service.ts | 27 - .../app/services/api/schedule-api.service.ts | 54 - .../api/shared-projects-api.service.ts | 35 - .../app/services/api/sub-tasks-api.service.ts | 34 - .../services/api/task-comments-api.service.ts | 35 - .../services/api/task-labels-api.service.ts | 41 - .../services/api/task-phases-api.service.ts | 60 - .../services/api/task-priorities.service.ts | 28 - .../services/api/task-statuses-api.service.ts | 62 - .../services/api/task-templates.service.ts | 56 - .../src/app/services/api/tasks-api.service.ts | 190 - .../services/api/tasks-log-time.service.ts | 43 - .../services/api/team-members-api.service.ts | 76 - .../src/app/services/api/teams-api.service.ts | 57 - .../services/api/test-gannt-api.service.ts | 31 - .../app/services/api/timezones-api.service.ts | 27 - .../app/services/api/todo-list-api.service.ts | 46 - .../src/app/services/api/users.service.ts | 24 - .../src/app/services/app.service.spec.ts | 16 - .../src/app/services/app.service.ts | 63 - .../src/app/services/auth.service.spec.ts | 16 - .../src/app/services/auth.service.ts | 85 - .../src/app/services/menu-service.spec.ts | 16 - .../src/app/services/menu.service.ts | 36 - .../notification-settings.service.spec.ts | 16 - .../services/notification-settings.service.ts | 30 - .../services/project-form-service.service.ts | 27 - .../app/services/project-phases.service.ts | 48 - .../app/services/project-template.service.ts | 48 - .../app/services/project-updates.service.ts | 36 - .../src/app/services/socket.service.ts | 35 - .../src/app/services/utils.service.spec.ts | 16 - .../src/app/services/utils.service.ts | 78 - .../src/app/services/worklenz.analytics.ts | 66 - .../session-expired.component.html | 22 - .../session-expired.component.scss | 0 .../session-expired.component.spec.ts | 25 - .../session-expired.component.ts | 33 - worklenz-frontend/src/app/shared/constants.ts | 120 - worklenz-frontend/src/app/shared/events.ts | 40 - .../src/app/shared/session-helper.ts | 25 - worklenz-frontend/src/app/shared/utils.ts | 120 - .../app/shared/worklenz-analytics-events.ts | 157 - worklenz-frontend/src/app/store.ts | 162 + worklenz-frontend/src/assets/.gitkeep | 0 .../src/assets/css/prebuilt-editor.css | 16 - .../src/assets/icons/file-icon.png | Bin 0 -> 12544 bytes .../src/assets/icons/icon-128x128.png | Bin 8396 -> 0 bytes .../src/assets/icons/icon-144x144.png | Bin 9344 -> 0 bytes .../src/assets/icons/icon-152x152.png | Bin 9823 -> 0 bytes .../src/assets/icons/icon-192x192.png | Bin 12077 -> 0 bytes .../src/assets/icons/icon-384x384.png | Bin 26068 -> 0 bytes .../src/assets/icons/icon-512x512.png | Bin 29712 -> 0 bytes .../src/assets/icons/icon-96x96.png | Bin 6682 -> 0 bytes .../insightsIcons}/block-user.png | Bin .../insightsIcons}/clipboard.png | Bin .../insightsIcons}/clock-green.png | Bin .../{images => icons/insightsIcons}/group.png | Bin .../insightsIcons}/insights-check.png | Bin .../insightsIcons}/warning.png | Bin worklenz-frontend/src/assets/images/404.svg | 325 - .../src/assets/images/chevron-down-solid.svg | 4 - .../src/assets/images/clock-red.png | Bin 4922 -> 0 bytes .../src/assets/images/clock-yellow.png | Bin 46382 -> 0 bytes .../src/assets/images/confetti (1).png | Bin 20398 -> 0 bytes .../src/assets/images/confetti.png | Bin 43336 -> 0 bytes .../src/assets/images/empty-box.png | Bin 33801 -> 0 bytes .../src/assets/images/empty-box.webp | Bin 19842 -> 0 bytes .../src/assets/images/folder.svg | 5 - .../src/assets/images/google-icon.png | Bin 10275 -> 716 bytes .../src/assets/images/google_sign_in.png | Bin 4308 -> 0 bytes .../src/assets/images/graph-report.png | Bin 38597 -> 0 bytes .../src/assets/images/group_1.png | Bin 49239 -> 0 bytes .../src/assets/images/insights-calendar.png | Bin 19138 -> 0 bytes .../src/assets/images/logo-dark-mode.png | Bin 0 -> 2969 bytes .../src/assets/images/logo-lg.png | Bin 230748 -> 0 bytes .../src/assets/images/logo-sm.png | Bin 4803 -> 0 bytes worklenz-frontend/src/assets/images/logo.png | Bin 28062 -> 2006 bytes .../assets/images/magnifying-glass-solid.svg | 5 - .../src/assets/images/noimage.png | Bin 15076 -> 0 bytes .../src/assets/images/not-found-img.png | Bin 0 -> 34446 bytes .../src/assets/images/open-icon.svg | 5 - .../src/assets/images/progress.png | Bin 24159 -> 0 bytes .../src/assets/images/project-management.png | Bin 19755 -> 0 bytes .../src/assets/images/team-members.svg | 11 - .../src/assets/images/to-do-list.png | Bin 29396 -> 0 bytes worklenz-frontend/src/assets/images/user.png | Bin 22004 -> 0 bytes .../src/components/AuthPageHeader.tsx | 27 + .../src/components/CustomAvatar.tsx | 25 + .../src/components/CustomSearchbar.tsx | 37 + .../src/components/CustomTableTitle.tsx | 19 + .../src/components/EmptyListPlaceholder.tsx | 30 + .../src/components/ErrorBoundary.tsx | 73 + .../src/components/PinRouteToNavbarButton.tsx | 62 + .../src/components/PreferenceSelector.tsx | 28 + worklenz-frontend/src/components/TawkTo.tsx | 50 + .../account-setup/admin-center-common.css | 19 + .../components/account-setup/members-step.tsx | 182 + .../account-setup/organization-step.tsx | 64 + .../components/account-setup/project-step.tsx | 155 + .../components/account-setup/tasks-step.tsx | 132 + .../add-members-dropdown.css | 13 + .../add-members-dropdown.tsx | 137 + .../add-members-dropdown.css | 5 + .../add-members-dropdown.tsx | 132 + .../account-storage/account-storage.tsx | 95 + .../billing/billing-tables/charges-table.tsx | 99 + .../billing/billing-tables/invoices-table.tsx | 105 + .../admin-center/billing/current-bill.css | 12 + .../admin-center/billing/current-bill.tsx | 141 + .../current-plan-details.tsx | 437 + .../redeem-code-drawer/redeem-code-drawer.tsx | 103 + .../upgrade-plans-lkr/upgrade-plans-lkr.css | 3 + .../upgrade-plans-lkr/upgrade-plans-lkr.tsx | 235 + .../drawers/upgrade-plans/upgrade-plans.tsx | 523 + .../configuration/configuration.tsx | 222 + .../organization-admins-table.tsx | 60 + .../organization-name/organization-name.tsx | 122 + .../organization-owner/organization-owner.tsx | 120 + .../teams/add-team-drawer/add-team-drawer.tsx | 85 + .../teams/settings-drawer/settings-drawer.css | 4 + .../teams/settings-drawer/settings-drawer.tsx | 198 + .../teams/teams-table/teams-table.tsx | 146 + .../src/components/avatars/avatars.tsx | 46 + .../board-assignee-selector.tsx | 222 + .../ChangeCategoryDropdown.css | 13 + .../ChangeCategoryDropdown.tsx | 110 + .../common-members-section.tsx | 297 + .../common-phase-section.tsx | 294 + .../common-priority-section.tsx | 295 + .../CommonStatusSection.css | 16 + .../CommonStatusSection.tsx | 295 + .../components/board/custom-avatar-group.tsx | 30 + .../board/custom-due-date-picker.tsx | 101 + .../board/kanban-group/kanban-group.css | 16 + .../board/kanban-group/kanban-group.tsx | 330 + .../board/subTaskCard/SubTaskCard.tsx | 158 + .../components/board/taskCard/TaskCard.css | 58 + .../components/board/taskCard/TaskCard.tsx | 318 + .../board/taskCreateCard/TaskCreateCard.css | 3 + .../board/taskCreateCard/TaskCreateCard.tsx | 255 + .../priority-task-create-card.tsx | 255 + .../calendars/homeCalendar/HomeCalendar.tsx | 28 + .../calendars/homeCalendar/homeCalendar.css | 7 + .../components/collapsible/collapsible.tsx | 26 + .../invite-team-members.tsx | 180 + .../project-status-icon.tsx | 21 + .../common/single-avatar/single-avatar.tsx | 26 + .../template-drawer/template-drawer.css | 94 + .../template-drawer/template-drawer.tsx | 379 + .../home-tasks-status-dropdown.css | 19 + .../home-tasks-status-dropdown.tsx | 117 + .../taskDatePicker/home-tasks-date-picker.tsx | 110 + .../notification/invitation-item.tsx | 117 + .../notification/notfication-drawer.tsx | 303 + .../notification/notification-button.tsx | 41 + .../notification/notification-item.css | 398 + .../notification/notification-item.tsx | 127 + .../notification/notification-template.tsx | 95 + .../push-notification-template.css | 7 + .../push-notification-template.tsx | 99 + .../components/project-list/TableColumns.css | 27 + .../components/project-list/TableColumns.tsx | 132 + .../project-list-actions.tsx | 93 + .../project-list-category.tsx | 41 + .../project-rate-cell.tsx | 59 + .../progress-list-progress.tsx | 11 + .../project-list-updated.tsx | 12 + .../project-name/project-name-cell.tsx | 69 + .../create-status-button.tsx | 32 + .../create-status-drawer.css | 36 + .../create-status-drawer.tsx | 110 + .../delete-status-drawer.tsx | 146 + .../group-by-filter-dropdown.tsx | 86 + .../labels-filter-dropdown.tsx | 166 + .../members-filter-dropdown.tsx | 179 + .../priority-filter-dropdown.tsx | 133 + .../filter-dropdowns/search-dropdown.tsx | 106 + .../show-fields-filter-dropdown.tsx | 81 + .../filter-dropdowns/sort-filter-dropdown.tsx | 139 + .../project-create-button.tsx | 162 + .../project-basic-info/project-basic-info.tsx | 45 + .../project-category-section.tsx | 159 + .../project-client-section.tsx | 124 + .../project-drawer/project-drawer.tsx | 489 + .../project-health-section.tsx | 35 + .../project-status-section.tsx | 39 + .../project-manager-dropdown.css | 31 + .../project-manager-dropdown.tsx | 119 + .../project-member-invite-drawer.tsx | 244 + .../projects/project-stats-card.tsx | 58 + .../reporting-overview-members-tab.tsx | 31 + .../reporting-overview-members-table.tsx | 127 + .../reports-overview-category-graph.tsx | 106 + .../reports-overview-project-health-graph.tsx | 105 + .../reports-overview-status-graph.tsx | 106 + .../overview-tab/reports-overview-tab.tsx | 46 + .../overview-team-info-drawer-tabs.tsx | 36 + .../overview-team-info-drawer.tsx | 63 + .../projects-tab/projects-reports-table.css | 57 + .../reporting-overview-projects-tab.tsx | 30 + .../reporting-overview-projects-table.tsx | 331 + .../components/reporting/time-wise-filter.tsx | 188 + .../save-project-as-template.tsx | 197 + .../WithStartAndEndDates.tsx | 66 + .../src/components/schedule-old/team/Team.css | 8 + .../src/components/schedule-old/team/Team.tsx | 467 + .../grant-chart/day-allocation-cell.tsx | 110 + .../schedule/grant-chart/grantt-chart.tsx | 286 + .../grant-chart/grantt-members-table.tsx | 147 + .../grant-chart/project-timeline-bar.tsx | 166 + .../WithStartAndEndDates.tsx | 72 + .../schedule/grant-chart/timeline.tsx | 108 + .../settings/edit-team-name-modal.tsx | 85 + .../settings/update-member-drawer.tsx | 272 + .../suspense-fallback/suspense-fallback.tsx | 46 + .../activity-log/task-drawer-activity-log.tsx | 195 + .../info-tab/attachments/attachments-grid.tsx | 62 + .../attachments/attachments-preview.css | 229 + .../attachments/attachments-preview.tsx | 305 + .../attachments/attachments-upload.css | 39 + .../attachments/attachments-upload.tsx | 100 + .../info-tab/comments/task-comments.css | 288 + .../info-tab/comments/task-comments.tsx | 359 + .../comments/task-view-comment-edit.tsx | 99 + .../shared/info-tab/dependencies-table.css | 9 + .../shared/info-tab/dependencies-table.tsx | 216 + .../shared/info-tab/description-editor.tsx | 198 + .../task-drawer-assignee-selector.tsx | 198 + .../task-drawer-billable.tsx | 30 + .../task-drawer-due-date.tsx | 133 + .../task-drawer-estimation.tsx | 84 + .../task-drawer-key/task-drawer-key.tsx | 19 + .../task-drawer-labels/task-drawer-labels.tsx | 223 + .../task-drawer-phase-selector.tsx | 52 + .../priority-dropdown.css | 19 + .../task-drawer-priority-selector.tsx | 115 + .../shared/info-tab/info-tab-footer.tsx | 472 + .../info-tab/notify-member-selector.tsx | 230 + .../shared/info-tab/subtask-table.css | 19 + .../shared/info-tab/subtask-table.tsx | 279 + .../shared/info-tab/task-details-form.tsx | 135 + .../shared/info-tab/task-drawer-info-tab.tsx | 295 + .../shared/time-log/TaskDrawerTimeLog.tsx | 162 + .../shared/time-log/task-drawer-time-log.tsx | 147 + .../shared/time-log/time-log-form.tsx | 268 + .../shared/time-log/time-log-item.css} | 0 .../shared/time-log/time-log-item.tsx | 109 + .../shared/time-log/time-log-list.tsx | 25 + .../task-drawer-header/task-drawer-header.css | 9 + .../task-drawer-header/task-drawer-header.tsx | 146 + .../task-drawer-status-dropdown.css | 19 + .../task-drawer-status-dropdown.tsx | 102 + .../components/task-drawer/task-drawer.css | 32 + .../components/task-drawer/task-drawer.tsx | 221 + .../assigneeSelector/AssigneeSelector.tsx | 168 + .../convert-to-subtask-drawer.tsx | 252 + .../labelsSelector/color-changed-label.tsx | 50 + .../labelsSelector/custom-color-label.tsx | 27 + .../labelsSelector/custom-number-label.tsx | 26 + .../labelsSelector/labels-selector.tsx | 170 + .../phaseDropdown/PhaseDropdown.tsx | 66 + .../phaseDropdown/phaseDropdown.css | 19 + .../priorityDropdown/priority-dropdown.css | 19 + .../priorityDropdown/priority-dropdown.tsx | 110 + .../status-dropdown/status-dropdown.css | 19 + .../status-dropdown/status-dropdown.tsx | 77 + .../task-row-due-time.tsx | 23 + .../task-row-description.tsx | 19 + .../task-row/task-row-name/task-row-name.tsx | 125 + .../task-row-progress/task-row-progress.css | 27 + .../task-row-progress/task-row-progress.tsx | 31 + .../task-row-time-tracking.tsx | 69 + .../task-templates/import-task-template.tsx | 160 + .../task-templates/task-template-drawer.tsx | 179 + .../assignee-selector/assignee-selector.tsx | 223 + .../labelsSelector/CustomColordLabel.tsx | 32 + .../labelsSelector/CustomNumberLabel.tsx | 28 + .../labelsSelector/LabelsSelector.tsx | 180 + .../labelsSelector/color-changed-label.tsx | 51 + .../phase-dropdown/phase-dropdown.css | 31 + .../phase-dropdown/phase-dropdown.tsx | 147 + .../priorityDropdown/PriorityDropdown.tsx | 129 + .../priorityDropdown/priorityDropdown.css | 19 + .../statusDropdown/StatusDropdown.tsx | 114 + .../statusDropdown/statusDropdown.css | 19 + .../components/AssigneesDropdown.tsx | 91 + .../components/LabelsDropdown.tsx | 108 + .../task-list-bulk-actions-bar.css | 74 + .../task-list-bulk-actions-bar.tsx | 576 + .../taskListCommon/task-timer/task-timer.tsx | 153 + .../src/environments/environment.prod.ts | 3 - .../src/environments/environment.ts | 16 - .../account-setup/account-setup.slice.ts | 67 + .../src/features/actionSetup/buttonSlice.ts | 22 + .../admin-center/admin-center.slice.ts | 83 + .../admin-center/billing/billing.slice.ts | 27 + .../src/features/auth/authSlice.ts | 156 + .../src/features/board/board-slice.ts | 872 + .../src/features/board/create-card.slice.ts | 35 + .../src/features/date/dateSlice.ts | 24 + .../group-by-filter-dropdown-slice.ts | 22 + .../src/features/home-page/home-page.slice.ts | 156 + .../src/features/i18n/language-selector.tsx | 56 + .../src/features/i18n/localesSlice.ts | 75 + .../src/features/navbar/help/HelpButton.css | 3 + .../src/features/navbar/help/HelpButton.tsx | 23 + .../features/navbar/invite/InviteButton.tsx | 32 + .../navbar/mobileMenu/MobileMenuButton.tsx | 99 + .../features/navbar/mobileMenu/mobileMenu.css | 38 + .../src/features/navbar/navRoutes.ts | 33 + .../src/features/navbar/navbar-logo.tsx | 45 + .../src/features/navbar/navbar.tsx | 186 + .../src/features/navbar/notificationSlice.ts | 117 + .../navbar/switchTeam/SwitchTeamButton.tsx | 137 + .../features/navbar/switchTeam/switchTeam.css | 26 + .../navbar/upgradePlan/UpgradePlanButton.tsx | 33 + .../navbar/user-profile/profile-button.css | 3 + .../navbar/user-profile/profile-button.tsx | 117 + .../navbar/user-profile/profile-dropdown.css | 39 + .../features/project/project-drawer.slice.ts | 63 + .../src/features/project/project.slice.ts | 221 + .../bulkActions/BulkTasksActionContainer.tsx | 117 + .../projects/bulkActions/bulkActionSlice.ts | 31 + .../insights/project-insights.slice.ts | 42 + .../projectCategoriesSlice.ts | 159 + .../projectHealth/projectHealthSlice.ts | 55 + .../projectStatuses/projectStatusesSlice.ts | 55 + .../projects/priority/prioritySlice.ts | 53 + .../src/features/projects/projectsSlice.ts | 252 + .../members/projectMembersSlice.ts | 150 + .../singleProject/phase/ConfigPhaseButton.tsx | 35 + .../singleProject/phase/PhaseDrawer.tsx | 209 + .../singleProject/phase/PhaseHeader.tsx | 30 + .../singleProject/phase/PhaseOptionItem.tsx | 136 + .../singleProject/phase/phases.slice.ts | 143 + .../task-list-custom-columns-slice.ts | 118 + .../taskListColumns/taskColumnsSlice.ts | 194 + .../singleProject/updates/updatesSlice.ts | 18 + .../projects/status/DeleteStatusSlice.ts | 41 + .../features/projects/status/StatusSlice.ts | 42 + .../update-project/update-project-drawer.tsx | 269 + .../activity-log-tab/activity-log-card.tsx | 104 + .../members-reports-activity-logs-tab.tsx | 73 + .../members-reports-drawer-tabs.tsx | 62 + .../members-reports-drawer.tsx | 166 + .../overviewTab/MembersReportsOverviewTab.tsx | 58 + .../MembersReportsPriorityGraph.tsx | 83 + .../MembersReportsProjectGraph.tsx | 83 + .../overviewTab/MembersReportsStatusGraph.tsx | 84 + ...members-overview-projects-stats-drawer.tsx | 85 + .../members-overview-projects-stats-table.tsx | 187 + .../members-overview-tasks-stats-drawer.tsx | 74 + .../members-overview-tasks-stats-table.tsx | 173 + .../overviewTab/members-reports-stat-card.tsx | 114 + .../taskTab/MembersReportsTasksTab.tsx | 107 + .../taskTab/MembersReportsTasksTable.tsx | 144 + .../taskTab/ProjectFilter.tsx | 34 + .../time-log-tab/billable-filter.tsx | 84 + .../members-reports-time-logs-tab.tsx | 89 + .../time-log-tab/time-log-card.tsx | 61 + .../membersReports/membersReportsSlice.ts | 143 + .../projectReports/project-reports-slice.ts | 271 + .../project-reports-table-column-slice.ts | 45 + .../ProjectReportsDrawer.tsx | 73 + .../ProjectReportsDrawerTabs.tsx | 38 + .../membersTab/ProjectReportsMembersTab.tsx | 57 + .../membersTab/ProjectReportsMembersTable.tsx | 115 + .../ProjectReportsMembersTaskDrawer.tsx | 72 + .../ProjectReportsMembersTaskTable.tsx | 145 + .../ProjectReportsDueDateGraph.tsx | 84 + .../overviewTab/ProjectReportsOverviewTab.tsx | 92 + .../ProjectReportsPriorityGraph.tsx | 91 + .../overviewTab/ProjectReportsStatCard.tsx | 74 + .../overviewTab/ProjectReportsStatusGraph.tsx | 83 + .../tasksTab/ProjectReportsTaskTable.tsx | 199 + .../tasksTab/ProjectReportsTasksTab.tsx | 90 + .../tasksTab/group-by-filter.tsx | 42 + .../src/features/reporting/reporting.slice.ts | 66 + .../time-reports-overview.slice.ts | 203 + .../src/features/roadmap/roadmap-slice.ts | 228 + .../schedule-old/ProjectTimelineModal.tsx | 57 + .../features/schedule-old/ScheduleDrawer.tsx | 44 + .../schedule-old/ScheduleSettingsDrawer.tsx | 94 + .../features/schedule-old/scheduleSlice.ts | 45 + .../schedule/ProjectTimelineModal.tsx | 137 + .../src/features/schedule/ScheduleDrawer.tsx | 45 + .../schedule/ScheduleSettingsDrawer.tsx | 99 + .../src/features/schedule/scheduleSlice.ts | 223 + .../categories/CustomColorsCategoryTag.tsx | 16 + .../settings/categories/categoriesSlice.ts | 30 + .../categories/color-changed-category.tsx | 51 + .../settings/client/CreateClientDrawer.tsx | 70 + .../settings/client/UpdateClientDrawer.tsx | 89 + .../features/settings/client/clientSlice.ts | 126 + .../settings/job/CreateJobTitlesDrawer.tsx | 70 + .../settings/job/UpdateJobTitlesDrawer.tsx | 89 + .../src/features/settings/job/jobSlice.ts | 55 + .../src/features/settings/label/labelSlice.ts | 46 + .../features/settings/member/memberSlice.ts | 53 + .../taskTemplates/TaskTemplateDrawer.json | 36 + .../taskTemplates/taskTemplateSlice.ts | 29 + .../features/task-drawer/task-drawer.slice.ts | 137 + .../features/taskAttributes/taskLabelSlice.ts | 113 + .../taskAttributes/taskMemberSlice.ts | 85 + .../taskAttributes/taskPrioritySlice.ts | 105 + .../taskAttributes/taskStatusSlice.ts | 130 + .../src/features/tasks/tasks.slice.ts | 1139 + .../team-members/team-members.slice.ts | 142 + .../src/features/teams/teamSlice.ts | 141 + .../src/features/theme/ThemeSelector.tsx | 28 + .../src/features/theme/ThemeWrapper.tsx | 77 + .../src/features/theme/themeSlice.ts | 88 + .../timeReport/projects/ProjectTimeLog.json | 109 + .../projects/ProjectTimeLogDrawer.css | 3 + .../projects/ProjectTimeLogDrawer.tsx | 128 + .../timeReport/projects/timeLogSlice.ts | 34 + .../src/features/user/userSlice.ts | 33 + worklenz-frontend/src/hooks/useAlert.ts | 30 + worklenz-frontend/src/hooks/useAppDispatch.ts | 4 + worklenz-frontend/src/hooks/useAppSelector.ts | 4 + worklenz-frontend/src/hooks/useAuth.ts | 9 + .../src/hooks/useDoumentTItle.ts | 7 + worklenz-frontend/src/hooks/useDragCursor.ts | 25 + .../src/hooks/useIsProjectManager.ts | 16 + .../src/hooks/useIsomorphicLayoutEffect.ts | 6 + .../src/hooks/useMixpanelTracking.tsx | 63 + worklenz-frontend/src/hooks/useResponsive.ts | 10 + .../src/hooks/useSelectedProject.ts | 17 + .../src/hooks/useSocketService.ts | 16 + .../src/hooks/useTabSearchParam.ts | 11 + .../src/hooks/useTaskDrawerUrlSync.ts | 91 + worklenz-frontend/src/hooks/useTaskTimer.ts | 153 + worklenz-frontend/src/i18n.ts | 19 + worklenz-frontend/src/index.css | 147 + worklenz-frontend/src/index.html | 20 - worklenz-frontend/src/index.tsx | 46 + worklenz-frontend/src/layouts/AuthLayout.tsx | 44 + .../src/layouts/AuthenticatedLayout.tsx | 11 + worklenz-frontend/src/layouts/MainLayout.tsx | 79 + .../src/layouts/ReportingLayout.tsx | 121 + .../src/layouts/SettingsLayout.tsx | 56 + .../src/layouts/admin-center-layout.tsx | 63 + .../src/lib/project/project-constants.ts | 26 + .../src/lib/project/project-view-constants.ts | 70 + .../src/lib/reporting/reporting-constants.ts | 71 + .../src/lib/settings/settings-constants.ts | 138 + worklenz-frontend/src/main.ts | 12 - worklenz-frontend/src/manifest.webmanifest | 59 - .../src/pages/404-page/404-page.tsx | 34 + .../src/pages/account-setup/account-setup.css | 150 + .../src/pages/account-setup/account-setup.tsx | 301 + .../admin-center/admin-center-constants.ts | 60 + .../pages/admin-center/billing/billing.tsx | 32 + .../pages/admin-center/overview/overview.tsx | 90 + .../pages/admin-center/projects/projects.css | 16 + .../pages/admin-center/projects/projects.tsx | 227 + .../pages/admin-center/sidebar/sidebar.css | 3 + .../pages/admin-center/sidebar/sidebar.tsx | 55 + .../src/pages/admin-center/teams/teams.css | 7 + .../src/pages/admin-center/teams/teams.tsx | 132 + .../src/pages/admin-center/users/users.tsx | 142 + .../src/pages/auth/authenticating.tsx | 81 + .../src/pages/auth/forgot-password-page.tsx | 158 + .../src/pages/auth/logging-out.tsx | 41 + .../src/pages/auth/login-page.tsx | 256 + .../src/pages/auth/signup-page.tsx | 366 + .../src/pages/auth/verify-reset-email.tsx | 176 + .../src/pages/home/greeting-with-time.tsx | 30 + .../src/pages/home/home-page.tsx | 90 + .../add-favourite-project-button.tsx | 41 + .../recent-and-favourite-project-list.tsx | 160 + .../home/task-list/add-task-inline-form.tsx | 266 + .../pages/home/task-list/calendar-view.tsx | 48 + .../src/pages/home/task-list/list-view.tsx | 55 + .../src/pages/home/task-list/tasks-list.css | 8 + .../src/pages/home/task-list/tasks-list.tsx | 293 + .../pages/home/todo-list/todo-done-button.tsx | 41 + .../src/pages/home/todo-list/todo-list.tsx | 171 + .../pages/license-expired/license-expired.tsx | 46 + .../src/pages/projects/project-list.css | 27 + .../src/pages/projects/project-list.tsx | 267 + .../projects/project-view-1/board/card.tsx | 35 + .../projects/project-view-1/board/column.tsx | 45 + .../board/project-view-board.tsx | 141 + .../roadmap/project-view-roadmap.css | 116 + .../roadmap/project-view-roadmap.tsx | 35 + .../roadmap/roadmap-grant-chart.tsx | 91 + .../roadmap/roadmap-table/roadmap-table.tsx | 189 + .../roadmap-table/roadmap-task-cell.tsx | 112 + .../project-view-1/roadmap/time-filter.tsx | 74 + .../project-view-1/task-list/table-v2.tsx | 142 + .../task-list-columns/task-list-columns.tsx | 239 + .../task-list/task-list-custom.css | 44 + .../task-list/task-list-custom.tsx | 272 + .../task-list-header/task-list-header.tsx | 116 + .../task-list-instant-task-input.tsx | 160 + .../task-list-table-old.tsx | 471 + .../task-list-table-old/task-list-table.css | 19 + .../task-list-table-wrapper.css | 15 + .../task-list-table-wrapper.tsx | 190 + .../project-view-1/task-list/task-list.tsx | 65 + .../taskList/ProjectViewTaskList.tsx | 56 + .../statusTables/StatusGroupTables.tsx | 67 + .../taskListFilters/GroupByFilterDropdown.tsx | 69 + .../taskListFilters/LabelsFilterDropdown.tsx | 134 + .../taskListFilters/MembersFilterDropdown.tsx | 140 + .../PriorityFilterDropdown.tsx | 73 + .../taskListFilters/SearchDropdown.tsx | 54 + .../ShowFieldsFilterDropdown.tsx | 96 + .../taskListFilters/SortFilterDropdown.tsx | 110 + .../taskListFilters/TaskListFilters.tsx | 73 + .../taskList/taskListTable/TaskListTable.tsx | 425 + .../taskListTable/TaskListTableWrapper.tsx | 216 + .../taskListTable/columns/columnList.ts | 90 + .../contextMenu/TaskContextMenu.tsx | 90 + .../taskListTableCells/TaskCell.tsx | 121 + .../taskListTableCells/TaskProgress.css | 22 + .../taskListTableCells/TaskProgress.tsx | 29 + .../taskListTableCells/TimeTracker.tsx | 66 + .../taskListTableCells/mockTimeLogs.ts | 42 + .../taskListTableRows/AddSubTaskListRow.tsx | 37 + .../taskListTableRows/AddTaskListRow.tsx | 37 + .../taskListTable/taskListTableWrapper.css | 15 + .../updates/project-view-updates.css | 19 + .../updates/project-view-updates.tsx | 263 + .../workload/ProjectViewWorkload.tsx | 7 + .../workload/projectViewWorkload.css} | 0 .../board-create-section-card.tsx | 160 + .../board-section-card-header.tsx | 377 + .../board-section-card/board-section-card.tsx | 220 + .../board-section/board-section-container.tsx | 116 + .../board-create-sub-task-card.tsx | 172 + .../board-sub-task-card.tsx | 62 + .../board-view-create-task-card.tsx | 248 + .../board-task-card/board-view-task-card.tsx | 413 + .../projectView/board/project-view-board.tsx | 431 + .../projectView/files/project-view-files.tsx | 271 + .../insights-members/insights-members.tsx | 27 + .../tables/assigned-tasks-list.tsx | 141 + .../tables/tasks-by-members.tsx | 161 + .../graphs/priority-overview.tsx | 119 + .../graphs/status-overview.tsx | 107 + .../insights-overview/insights-overview.tsx | 60 + .../tables/last-updated-tasks.tsx | 128 + .../tables/project-deadline.tsx | 137 + .../insights-tasks/insights-tasks.tsx | 100 + .../tables/over-logged-tasks-table.tsx | 137 + .../tables/overdue-tasks-table.tsx | 113 + .../tables/task-completed-early-table.tsx | 117 + .../tables/task-completed-late-table.tsx | 118 + .../insights/member-stats/member-stats.tsx | 64 + .../insights/project-stats/project-stats.tsx | 96 + .../insights/project-view-insights.tsx | 179 + .../members/project-view-members.tsx | 295 + .../projectView/project-view-header.tsx | 309 + .../projects/projectView/project-view.css | 6 + .../projects/projectView/project-view.tsx | 202 + .../taskList/groupTables/TaskGroupList.tsx | 245 + .../priorityTables/PriorityGroupTables.tsx | 44 + .../taskList/project-view-task-list.tsx | 68 + .../task-list-filters/task-list-filters.tsx | 85 + .../context-menu/task-context-menu.tsx | 321 + .../custom-column-label-cell.css | 19 + .../custom-column-label-cell.tsx | 127 + .../custom-column-selection-cell.css | 19 + .../custom-column-selection-cell.tsx | 135 + .../custom-column-header.tsx | 54 + .../add-custom-column-button.tsx | 34 + .../custom-column-modal.tsx | 497 + .../formula-type-column.tsx | 103 + .../key-type-column/key-type-column.tsx | 28 + .../label-type-column/label-type-column.tsx | 142 + .../formatted-type-number-column.tsx | 45 + .../number-type-column/number-type-column.tsx | 61 + .../percentage-type-number-column.tsx | 45 + .../unformatted-type-number-column.tsx | 18 + .../with-label-type-number-column.tsx | 85 + .../selection-type-column.tsx | 154 + .../task-group-wrapper/task-group-wrapper.tsx | 671 + .../task-list-completed-date-cell.tsx | 14 + .../task-list-created-date-cell.tsx | 13 + .../task-list-description-cell.tsx | 21 + .../task-list-due-date-cell.tsx | 54 + .../task-list-due-time-cell.tsx | 24 + .../task-list-estimation-cell.tsx | 12 + .../task-list-labels-cell.tsx | 26 + .../task-list-last-updated-cell.tsx | 13 + .../task-list-members-cell.tsx | 21 + .../task-list-progress-cell.css | 22 + .../task-list-progress-cell.tsx | 23 + .../task-list-reporter-cell.tsx | 8 + .../task-list-start-date-cell.tsx | 49 + .../task-list-task-cell.css | 13 + .../task-list-task-cell.tsx | 245 + .../task-list-task-id-cell.tsx | 12 + .../task-list-time-tracker-cell.tsx | 26 + .../add-sub-task-list-row.tsx | 37 + .../add-task-list-row.tsx | 156 + .../task-list-table-wrapper.css | 15 + .../task-list-table-wrapper.tsx | 260 + .../task-list-table/task-list-table.tsx | 1679 + .../task-list-time-tracker-cell.tsx | 1 + .../updates/ProjectViewUpdates.tsx | 122 + .../members-reports-table.tsx | 131 + .../tablesCells/memberCell/MemberCell.tsx | 28 + .../tasksProgressCell/TasksProgressCell.tsx | 79 + .../members-reports/members-reports.tsx | 86 + .../overview-reports/overview-reports.tsx | 58 + .../overview-reports/overview-stat-card.tsx | 32 + .../overview-reports/overview-stats.tsx | 132 + .../overview-table/overview-reports-table.tsx | 99 + .../page-header/custom-page-header.tsx | 20 + .../project-categories-filter-dropdown.tsx | 143 + .../project-health-filter-dropdown.tsx | 112 + .../project-managers-filter-dropdown.tsx | 111 + .../project-reports-filters.tsx | 40 + .../project-status-filter-dropdown.tsx | 106 + .../project-table-show-fields-dropdown.tsx | 57 + .../projects-reports-table.css | 57 + .../projects-reports-table.tsx | 318 + .../estimated-vs-actual-cell.tsx | 93 + .../last-activity-cell/last-activity-cell.tsx | 18 + .../project-category-cell.css | 19 + .../project-category-cell.tsx | 206 + .../table-cells/project-cell/project-cell.tsx | 29 + .../project-client-cell.tsx | 16 + .../project-dates-cell/project-dates-cell.tsx | 114 + .../project-days-left-and-overdue-cell.tsx | 48 + .../project-health-cell.css | 19 + .../project-health-cell.tsx | 117 + .../project-manager-cell.tsx | 26 + .../project-status-cell.tsx | 96 + .../project-team-cell/project-team-cell.tsx | 20 + .../project-update-cell.tsx | 20 + .../tasks-progress-cell.tsx | 79 + .../projects-reports/projects-reports.tsx | 59 + .../sidebar/reporting-collapsed-button.tsx | 106 + .../reporting/sidebar/reporting-sider.tsx | 67 + .../estimated-vs-actual-time-sheet.tsx | 301 + .../members-time-sheet/members-time-sheet.tsx | 203 + .../project-time-sheet-chart.tsx | 240 + .../time-sheet-table/time-sheet-table.css | 110 + .../time-sheet-table/time-sheet-table.tsx | 154 + .../estimated-vs-actual-time-reports.tsx | 70 + .../timeReports/members-time-reports.tsx | 50 + .../timeReports/overview-time-reports.tsx | 67 + .../timeReports/page-header/billable.tsx | 49 + .../timeReports/page-header/categories.tsx | 137 + .../timeReports/page-header/projects.tsx | 113 + .../timeReports/page-header/team.tsx | 115 + .../page-header/time-report-page-header.tsx | 36 + .../timeReports/projects-time-reports.tsx | 52 + .../TimeReportingRightHeader.tsx | 50 + .../src/pages/schedule-old/schedule.tsx | 89 + .../src/pages/schedule/grant-chart.tsx | 923 + .../src/pages/schedule/schedule.tsx | 91 + .../categories/categories-settings.tsx | 140 + .../change-password/change-password.tsx | 131 + .../pages/settings/clients/client-drawer.tsx | 85 + .../settings/clients/clients-settings.tsx | 196 + .../settings/job-titles/job-titles-drawer.tsx | 99 + .../job-titles/job-titles-settings.tsx | 205 + .../pages/settings/labels/labels-settings.tsx | 140 + .../language-and-region-settings.test.ts} | 0 .../language-and-region-settings.tsx | 152 + .../notifications/notifications-settings.tsx | 162 + .../settings/profile/profile-settings.css | 11 + .../settings/profile/profile-settings.tsx | 254 + .../project-templates-settings.tsx | 103 + .../ProjectTemplateEditView.tsx | 104 + .../settings/sidebar/settings-sidebar.tsx | 70 + .../task-templates-settings.css | 24 + .../task-templates-settings.tsx | 135 + .../team-members/team-members-settings.tsx | 353 + .../pages/settings/teams/teams-settings.tsx | 101 + .../src/pages/unauthorized/unauthorized.tsx | 28 + worklenz-frontend/src/react-app-env.d.ts | 1 + worklenz-frontend/src/reportWebVitals.ts | 15 + .../src/res/js/jquery-3.6.1.min.js | 2 - .../src/res/js/jquery.mentiony.js | 812 - worklenz-frontend/src/res/js/smooth-scroll.js | 306 - .../src/res/scss/antd-variables.scss | 41 - worklenz-frontend/src/res/scss/antd.scss | 22275 ----------- worklenz-frontend/src/res/scss/gantt.scss | 407 - .../src/scss/editable-table.scss | 73 - worklenz-frontend/src/scss/scrollbar.scss | 29 - worklenz-frontend/src/scss/task-view.scss | 54 - .../src/services/alerts/alertMiddleware.ts | 25 + .../src/services/alerts/alertService.ts | 67 + .../src/services/alerts/alertSlice.ts | 43 + .../src/services/auth/auth.service.ts | 69 + .../src/services/socket/socket.service.ts | 154 + .../services/task-list/taskList.service.ts | 40 + worklenz-frontend/src/shared/constants.ts | 315 + .../src/{app => }/shared/socket-events.ts | 8 +- .../src/shared/worklenz-analytics-events.ts | 167 + worklenz-frontend/src/socket/config.ts | 8 + .../src/socket/socketContext.tsx | 149 + worklenz-frontend/src/styles.scss | 1094 - worklenz-frontend/src/styles/colors.ts | 23 + .../src/styles/customOverrides.css | 105 + worklenz-frontend/src/test.ts | 11 - worklenz-frontend/src/theme.less | 153 - .../types/admin-center/admin-center.types.ts | 231 + .../src/types/admin-center/country.types.ts | 5 + .../src/types/admin-center/team.types.ts | 9 + worklenz-frontend/src/types/alert.types.ts | 21 + .../taskPrioritiesGetResponse.types.ts | 5 + .../src/types/auth/local-session.types.ts | 31 + .../src/types/auth/login.types.ts | 33 + .../auth/signup.types.ts} | 0 .../types/auth/verify-reset-email.types.ts | 6 + .../avatarAttachment.types.ts} | 0 .../src/types/categories.types.ts | 5 + worklenz-frontend/src/types/client.types.ts | 16 + .../common.types.ts} | 0 .../src/types/dependencies.types.ts | 6 + .../src/types/home/home-page.types.ts | 26 + .../src/types/home/my-tasks.types.ts | 13 + .../src/types/home/tasks.types.ts | 33 + worklenz-frontend/src/types/job.types.ts | 10 + .../task-label.ts => types/label.type.ts} | 0 worklenz-frontend/src/types/member.types.ts | 10 + worklenz-frontend/src/types/mixpanel.types.ts | 15 + .../src/types/notification.types.ts | 1 + .../notifications/notifications.types.ts | 25 + worklenz-frontend/src/types/phase.types.ts | 12 + .../types/project-list/project-list.types.ts | 48 + .../project-templates.types.ts | 60 + worklenz-frontend/src/types/project.types.ts | 51 + .../types/project/project-insights.types.ts | 81 + .../src/types/project/project.types.ts | 47 + .../project/projectAccessLevel.types.ts} | 0 .../project/projectCategory.types.ts} | 0 .../types/project/projectComments.types.ts | 26 + .../project/projectFilterConfig.types.ts} | 0 .../src/types/project/projectHealth.types.ts | 8 + .../types/project/projectInsights.types.ts | 55 + .../src/types/project/projectManager.types.ts | 6 + .../project/projectMember.types.ts} | 0 .../project/projectStatus.types.ts} | 0 .../project/projectTasksViewModel.types.ts | 97 + .../types/project/projectTemplate.types.ts | 17 + .../types/project/projectViewModel.types.ts | 44 + .../types/project/projectsViewModel.types.ts | 11 + .../src/types/projectMember.types.ts | 20 + .../reporting/reporting-allocation.types.ts | 19 + .../reporting/reporting-filters.types.ts | 15 + .../src/types/reporting/reporting.types.ts | 439 + .../src/types/schedule/schedule-v2.types.ts | 65 + .../src/types/schedule/schedule.types.ts | 27 + .../settings/notifications.types.ts} | 0 .../settings/profile.types.ts} | 0 .../types/settings/task-templates.types.ts | 14 + .../settings/timezone.types.ts} | 0 worklenz-frontend/src/types/socket.types.ts | 12 + worklenz-frontend/src/types/status.types.ts | 29 + worklenz-frontend/src/types/task.types.ts | 36 + .../src/types/tasks/bulk-action-bar.types.ts | 43 + .../src/types/tasks/subTask.types.ts | 18 + .../tasks/task-activity-logs-get-request.ts | 61 + .../tasks/task-assignee-update-response.ts | 13 + .../types/tasks/task-attachment-view-model.ts | 24 + .../src/types/tasks/task-comments.types.ts | 42 + .../types/tasks/task-create-request.types.ts | 25 + .../src/types/tasks/task-dependency.types.ts | 13 + .../types/tasks/task-list-priority.types.ts | 7 + .../src/types/tasks/task-list-status.types.ts | 16 + .../tasks/task-log-view.types.ts} | 0 .../tasks}/task-phase-change-response.ts | 0 .../tasks}/task-status-create-request.ts | 2 +- .../tasks/task-status-update-model.types.ts} | 0 .../src/types/tasks/task.types.ts | 107 + .../src/types/tasks/taskLabel.types.ts | 14 + .../src/types/tasks/taskList.types.ts | 110 + .../src/types/tasks/taskListFilters.types.ts | 50 + .../tasks/taskPhase.types.ts} | 0 .../src/types/tasks/taskPriority.types.ts | 12 + .../src/types/tasks/taskStatus.types.ts | 24 + .../tasks/taskStatusCategory.types.ts} | 0 .../tasks/taskStatusGetResponse.types.ts | 6 + .../types/teamMembers/inlineMember.types.ts | 8 + .../teamMembers/team-member-create-request.ts | 7 + .../teamMembers/teamMember.types.ts} | 0 .../teamMembersGetResponse.types.ts | 77 + .../teamMembers/teamMembersViewModel.types.ts | 6 + .../src/types/teams/team.type.ts | 47 + .../src/types/timeSheet/project.types.ts | 5 + worklenz-frontend/src/types/todo.types.ts | 5 + worklenz-frontend/src/types/updates.types.ts | 6 + worklenz-frontend/src/types/user.types.ts | 6 + worklenz-frontend/src/typings.d.ts | 1 - .../src/utils/calculate-time-difference.ts | 41 + .../src/utils/calculate-time-gap.ts | 10 + .../src/utils/check-task-dependency-status.ts | 13 + worklenz-frontend/src/utils/colorUtils.ts | 3 + .../src/utils/current-date-string.ts | 12 + worklenz-frontend/src/utils/dateUtils.ts | 32 + .../src/utils/durationDateFormat.ts | 25 + worklenz-frontend/src/utils/errorLogger.ts | 153 + worklenz-frontend/src/utils/fetchData.ts | 13 + worklenz-frontend/src/utils/file-utils.ts | 7 + .../src/utils/format-date-time-with-locale.ts | 12 + .../src/utils/get-initial-theme.ts | 10 + .../src/utils/getPriorityColors.ts | 21 + worklenz-frontend/src/utils/getStatusColor.ts | 21 + worklenz-frontend/src/utils/greetingString.ts | 35 + worklenz-frontend/src/utils/language-utils.ts | 37 + .../src/utils/localStorageFunctions.ts | 17 + worklenz-frontend/src/utils/mixpanelInit.ts | 11 + .../src/utils/project-list-utils.ts | 24 + worklenz-frontend/src/utils/projectUtils.ts | 11 + worklenz-frontend/src/utils/sanitizeInput.ts | 35 + worklenz-frontend/src/utils/schedule.ts | 3 + worklenz-frontend/src/utils/session-helper.ts | 33 + .../src/utils/simpleDateFormat.ts | 24 + .../src/utils/sort-team-members.ts | 34 + worklenz-frontend/src/utils/test-utils.ts | 12 + worklenz-frontend/src/utils/themeWiseColor.ts | 6 + worklenz-frontend/src/utils/timeUtils.ts | 13 + .../src/utils/timeZoneCurrencyMap.ts | 7 + worklenz-frontend/src/utils/toCamelCase.ts | 3 + worklenz-frontend/src/utils/toQueryString.ts | 9 + worklenz-frontend/src/utils/validateEmail.ts | 12 + worklenz-frontend/src/vite-env.d.ts | 10 + worklenz-frontend/tailwind.config.js | 8 + worklenz-frontend/tsconfig.app.json | 14 - worklenz-frontend/tsconfig.json | 75 +- worklenz-frontend/tsconfig.spec.json | 17 - worklenz-frontend/vite.config.ts | 77 + 2837 files changed, 241385 insertions(+), 127578 deletions(-) create mode 100644 .env create mode 100644 .env.example create mode 100644 start.bat create mode 100644 start.sh create mode 100644 stop.bat create mode 100644 stop.sh create mode 100644 worklenz-backend/.env.example create mode 100644 worklenz-backend/database/2_dml.sql delete mode 100644 worklenz-backend/database/3_system-data.sql create mode 100644 worklenz-backend/database/3_views.sql create mode 100644 worklenz-backend/database/4_functions.sql delete mode 100644 worklenz-backend/database/4_views.sql create mode 100644 worklenz-backend/database/5_database_user.sql delete mode 100644 worklenz-backend/database/5_functions.sql delete mode 100644 worklenz-backend/database/6_user-permission.sql create mode 100644 worklenz-backend/database/indexes.sql rename {worklenz-frontend/src/app/DTOs => worklenz-backend/database/migrations}/.gitkeep (100%) rename worklenz-frontend/src/app/administrator/admin-center/sidebar/sidebar.component.scss => worklenz-backend/database/migrations/MIGRATION-NOTE.md (100%) create mode 100644 worklenz-backend/database/migrations/README.md create mode 100644 worklenz-backend/database/text_length_checks.sql rename worklenz-backend/database/{2_triggers.sql => triggers.sql} (100%) create mode 100644 worklenz-backend/database/truncate.sql create mode 100644 worklenz-backend/database/worklenz_db_revision_1.svg create mode 100644 worklenz-backend/database/worklenz_db_revision_2.svg create mode 100644 worklenz-backend/src/controllers/billing-controller.ts create mode 100644 worklenz-backend/src/controllers/custom-columns-controller.ts create mode 100644 worklenz-backend/src/controllers/reporting/point-options-object.ts create mode 100644 worklenz-backend/src/controllers/schedule-v2/schedule-controller.ts create mode 100644 worklenz-backend/src/controllers/task-dependencies-controller.ts create mode 100644 worklenz-backend/src/controllers/task-recurring-controller.ts create mode 100644 worklenz-backend/src/cron_jobs/recurring-tasks.ts create mode 100644 worklenz-backend/src/interfaces/recurring-tasks.ts create mode 100644 worklenz-backend/src/keys/PRIVATE_KEY_DEV.pem create mode 100644 worklenz-backend/src/keys/PRIVATE_KEY_PROD.pem create mode 100644 worklenz-backend/src/middlewares/validators/task-comment-attachment-validator.ts create mode 100644 worklenz-backend/src/middlewares/validators/task-create-body--validator.ts create mode 100644 worklenz-backend/src/middlewares/validators/task-dependencies-validator.ts delete mode 100644 worklenz-backend/src/public/148.3562c20f7c79dfbe.js delete mode 100644 worklenz-backend/src/public/150.aea42238d373936d.js create mode 100644 worklenz-backend/src/public/150.e8fd9de562cbd890.js delete mode 100644 worklenz-backend/src/public/152.89824b7edf9e2b3f.js delete mode 100644 worklenz-backend/src/public/169.3681839fd43f684a.js create mode 100644 worklenz-backend/src/public/169.3b2c60209f446fbe.js delete mode 100644 worklenz-backend/src/public/226.342b896c33d8d900.js create mode 100644 worklenz-backend/src/public/226.76ed0ef59ac56130.js create mode 100644 worklenz-backend/src/public/246.f0a439ab83f2a2c0.js create mode 100644 worklenz-backend/src/public/265.899f98599d1ef008.js delete mode 100644 worklenz-backend/src/public/265.e0fb54844d977984.js delete mode 100644 worklenz-backend/src/public/269.c4d92a972c5787a6.js delete mode 100644 worklenz-backend/src/public/31.366d3b5b3a40bd28.js create mode 100644 worklenz-backend/src/public/31.b9d7263dff6a2005.js create mode 100644 worklenz-backend/src/public/422.2cee432955f17cf9.js delete mode 100644 worklenz-backend/src/public/422.7a1aad3ba048190e.js delete mode 100644 worklenz-backend/src/public/450.2aa94132069852f3.js create mode 100644 worklenz-backend/src/public/453.233ae5ea989450bd.js delete mode 100644 worklenz-backend/src/public/453.850082f04ceb33a4.js create mode 100644 worklenz-backend/src/public/469.3682bfad6e614ed8.js delete mode 100644 worklenz-backend/src/public/526.31f0f862d4cc0de0.js create mode 100644 worklenz-backend/src/public/570.496193a4334c6152.js delete mode 100644 worklenz-backend/src/public/570.7a7379f081e3b4a7.js create mode 100644 worklenz-backend/src/public/586.cc390d02c36f1014.js create mode 100644 worklenz-backend/src/public/589.54dc9f2604592d56.js delete mode 100644 worklenz-backend/src/public/589.fc04e75bdb5d1681.js create mode 100644 worklenz-backend/src/public/771.5669afea3e716661.js delete mode 100644 worklenz-backend/src/public/771.c1c131496d7c6ea4.js create mode 100644 worklenz-backend/src/public/823.6c1e987f275ca088.js create mode 100644 worklenz-backend/src/public/848.703fbe56ddd4145d.js create mode 100644 worklenz-backend/src/public/860.0b5404f4cedd350f.js delete mode 100644 worklenz-backend/src/public/89.056b6bfe2710d324.js create mode 100644 worklenz-backend/src/public/89.ffa2fa0c920899c7.js create mode 100644 worklenz-backend/src/public/921.06d29d0727f62a99.js delete mode 100644 worklenz-backend/src/public/921.09750d890ab4e8d5.js create mode 100644 worklenz-backend/src/public/945.80e0a90c72e96010.js delete mode 100644 worklenz-backend/src/public/945.9a0115568762948e.js create mode 100644 worklenz-backend/src/public/assets/images/wl-logo.png create mode 100644 worklenz-backend/src/public/common.6327831a82c2372e.js create mode 100644 worklenz-backend/src/public/main.05d8ba4986531fb3.js delete mode 100644 worklenz-backend/src/public/main.91ef34cb24678df1.js create mode 100644 worklenz-backend/src/public/runtime.6007cef15fea30d1.js delete mode 100644 worklenz-backend/src/public/runtime.9997dc39514a207d.js create mode 100644 worklenz-backend/src/public/scripts.47557d34e47c126b.js create mode 100644 worklenz-backend/src/public/styles.68b8da263f6e7075.css delete mode 100644 worklenz-backend/src/public/styles.c78b93a1a5b19d7f.css create mode 100644 worklenz-backend/src/public/tinymce/CHANGELOG.md create mode 100644 worklenz-backend/src/public/tinymce/README.md create mode 100644 worklenz-backend/src/public/tinymce/bower.json create mode 100644 worklenz-backend/src/public/tinymce/composer.json create mode 100644 worklenz-backend/src/public/tinymce/icons/default/icons.js create mode 100644 worklenz-backend/src/public/tinymce/icons/default/icons.min.js create mode 100644 worklenz-backend/src/public/tinymce/icons/default/index.js create mode 100644 worklenz-backend/src/public/tinymce/license.txt create mode 100644 worklenz-backend/src/public/tinymce/models/dom/index.js create mode 100644 worklenz-backend/src/public/tinymce/models/dom/model.js create mode 100644 worklenz-backend/src/public/tinymce/models/dom/model.min.js create mode 100644 worklenz-backend/src/public/tinymce/package.json create mode 100644 worklenz-backend/src/public/tinymce/plugins/accordion/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/accordion/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/accordion/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/advlist/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/advlist/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/advlist/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/anchor/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/anchor/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/anchor/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/autolink/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/autolink/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/autolink/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/autoresize/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/autoresize/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/autoresize/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/autosave/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/autosave/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/autosave/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/charmap/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/charmap/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/charmap/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/code/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/code/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/code/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/codesample/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/codesample/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/codesample/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/directionality/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/directionality/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/directionality/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/emoticons/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/emoticons/js/emojiimages.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/emoticons/js/emojiimages.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/emoticons/js/emojis.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/emoticons/js/emojis.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/emoticons/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/emoticons/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/fullscreen/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/fullscreen/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/fullscreen/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/ar.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/bg_BG.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/ca.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/cs.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/da.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/de.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/el.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/en.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/es.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/eu.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/fa.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/fi.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/fr_FR.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/he_IL.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/hi.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/hr.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/hu_HU.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/id.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/it.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/ja.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/kk.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/ko_KR.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/ms.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/nb_NO.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/nl.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/pl.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/pt_BR.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/pt_PT.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/ro.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/ru.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/sk.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/sl_SI.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/sv_SE.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/th_TH.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/tr.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/uk.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/vi.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/zh_CN.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/js/i18n/keynav/zh_TW.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/help/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/image/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/image/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/image/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/importcss/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/importcss/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/importcss/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/insertdatetime/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/insertdatetime/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/insertdatetime/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/link/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/link/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/link/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/lists/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/lists/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/lists/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/media/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/media/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/media/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/nonbreaking/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/nonbreaking/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/nonbreaking/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/pagebreak/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/pagebreak/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/pagebreak/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/preview/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/preview/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/preview/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/quickbars/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/quickbars/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/quickbars/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/save/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/save/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/save/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/searchreplace/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/searchreplace/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/searchreplace/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/table/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/table/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/table/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/template/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/template/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/template/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/visualblocks/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/visualblocks/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/visualblocks/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/visualchars/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/visualchars/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/visualchars/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/wordcount/index.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/wordcount/plugin.js create mode 100644 worklenz-backend/src/public/tinymce/plugins/wordcount/plugin.min.js create mode 100644 worklenz-backend/src/public/tinymce/skins/content/dark/content.css create mode 100644 worklenz-backend/src/public/tinymce/skins/content/dark/content.js create mode 100644 worklenz-backend/src/public/tinymce/skins/content/dark/content.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/content/default/content.css create mode 100644 worklenz-backend/src/public/tinymce/skins/content/default/content.js create mode 100644 worklenz-backend/src/public/tinymce/skins/content/default/content.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/content/document/content.css create mode 100644 worklenz-backend/src/public/tinymce/skins/content/document/content.js create mode 100644 worklenz-backend/src/public/tinymce/skins/content/document/content.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/content/tinymce-5-dark/content.css create mode 100644 worklenz-backend/src/public/tinymce/skins/content/tinymce-5-dark/content.js create mode 100644 worklenz-backend/src/public/tinymce/skins/content/tinymce-5-dark/content.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/content/tinymce-5/content.css create mode 100644 worklenz-backend/src/public/tinymce/skins/content/tinymce-5/content.js create mode 100644 worklenz-backend/src/public/tinymce/skins/content/tinymce-5/content.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/content/writer/content.css create mode 100644 worklenz-backend/src/public/tinymce/skins/content/writer/content.js create mode 100644 worklenz-backend/src/public/tinymce/skins/content/writer/content.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide-dark/content.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide-dark/content.inline.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide-dark/content.inline.js create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide-dark/content.inline.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide-dark/content.js create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide-dark/content.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide-dark/skin.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide-dark/skin.js create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide-dark/skin.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide-dark/skin.shadowdom.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide-dark/skin.shadowdom.js create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide-dark/skin.shadowdom.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide/content.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide/content.inline.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide/content.inline.js create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide/content.inline.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide/content.js create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide/content.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide/skin.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide/skin.js create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide/skin.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide/skin.shadowdom.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide/skin.shadowdom.js create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/oxide/skin.shadowdom.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5-dark/content.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5-dark/content.inline.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5-dark/content.inline.js create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5-dark/content.inline.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5-dark/content.js create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5-dark/content.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5-dark/skin.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5-dark/skin.js create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5-dark/skin.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.js create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5/content.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5/content.inline.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5/content.inline.js create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5/content.inline.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5/content.js create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5/content.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5/skin.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5/skin.js create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5/skin.min.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5/skin.shadowdom.css create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5/skin.shadowdom.js create mode 100644 worklenz-backend/src/public/tinymce/skins/ui/tinymce-5/skin.shadowdom.min.css create mode 100644 worklenz-backend/src/public/tinymce/themes/silver/index.js create mode 100644 worklenz-backend/src/public/tinymce/themes/silver/theme.js create mode 100644 worklenz-backend/src/public/tinymce/themes/silver/theme.min.js create mode 100644 worklenz-backend/src/public/tinymce/tinymce.d.ts create mode 100644 worklenz-backend/src/public/tinymce/tinymce.js create mode 100644 worklenz-backend/src/public/tinymce/tinymce.min.js create mode 100644 worklenz-backend/src/routes/apis/billing-api-router.ts create mode 100644 worklenz-backend/src/routes/apis/custom-columns-api-router.ts create mode 100644 worklenz-backend/src/routes/apis/gannt-apis/schedule-api-v2-router.ts create mode 100644 worklenz-backend/src/routes/apis/task-dependencies-api-router.ts create mode 100644 worklenz-backend/src/routes/apis/task-recurring-api-router.ts create mode 100644 worklenz-backend/src/shared/paddle-requests.ts create mode 100644 worklenz-backend/src/shared/paddle-utils.ts create mode 100644 worklenz-backend/src/shared/storage.ts create mode 100644 worklenz-backend/src/socket.io/commands/on-task-assignees-change.ts create mode 100644 worklenz-backend/src/socket.io/commands/on-task-billable-change.ts create mode 100644 worklenz-backend/src/socket.io/commands/on-task-recurring-change.ts create mode 100644 worklenz-backend/src/socket.io/commands/on_custom_column_pinned_change.ts create mode 100644 worklenz-backend/src/socket.io/commands/on_custom_column_update.ts create mode 100644 worklenz-backend/src/views/_tawk-to.pug create mode 100644 worklenz-backend/worklenz-email-templates/licensing.html create mode 100644 worklenz-backend/worklenz-email-templates/release-note-template.html delete mode 100644 worklenz-frontend/.editorconfig create mode 100644 worklenz-frontend/.env.development delete mode 100644 worklenz-frontend/.eslintrc.json delete mode 100644 worklenz-frontend/.npmrc create mode 100644 worklenz-frontend/.prettierrc delete mode 100644 worklenz-frontend/angular.json rename worklenz-frontend/{src => }/favicon.ico (100%) create mode 100644 worklenz-frontend/index.html create mode 100644 worklenz-frontend/jest.config.js delete mode 100644 worklenz-frontend/karma.conf.js delete mode 100644 worklenz-frontend/ngsw-config.json create mode 100644 worklenz-frontend/path/to/members-reports-drawer.tsx create mode 100644 worklenz-frontend/path/to/members-reports-time-logs-tab.tsx create mode 100644 worklenz-frontend/postcss.config.js create mode 100644 worklenz-frontend/project-report-table.css delete mode 100644 worklenz-frontend/proxy-docker.config.json delete mode 100644 worklenz-frontend/proxy.config.json rename worklenz-frontend/{src/assets/images/files => public/file-types}/ai.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/avi.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/css.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/csv.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/doc.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/exe.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/html.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/jpg.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/js.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/json.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/mp3.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/mp4.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/pdf.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/png.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/ppt.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/psd.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/search.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/svg.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/txt.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/xls.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/xml.png (100%) rename worklenz-frontend/{src/assets/images/files => public/file-types}/zip.png (100%) create mode 100644 worklenz-frontend/public/locales/en/404-page.json create mode 100644 worklenz-frontend/public/locales/en/account-setup.json create mode 100644 worklenz-frontend/public/locales/en/admin-center/current-bill.json create mode 100644 worklenz-frontend/public/locales/en/admin-center/overview.json create mode 100644 worklenz-frontend/public/locales/en/admin-center/projects.json create mode 100644 worklenz-frontend/public/locales/en/admin-center/sidebar.json create mode 100644 worklenz-frontend/public/locales/en/admin-center/teams.json create mode 100644 worklenz-frontend/public/locales/en/admin-center/users.json create mode 100644 worklenz-frontend/public/locales/en/all-project-list.json create mode 100644 worklenz-frontend/public/locales/en/auth/auth-common.json create mode 100644 worklenz-frontend/public/locales/en/auth/forgot-password.json create mode 100644 worklenz-frontend/public/locales/en/auth/login.json create mode 100644 worklenz-frontend/public/locales/en/auth/signup.json create mode 100644 worklenz-frontend/public/locales/en/auth/verify-reset-email.json create mode 100644 worklenz-frontend/public/locales/en/common.json create mode 100644 worklenz-frontend/public/locales/en/create-first-project-form.json create mode 100644 worklenz-frontend/public/locales/en/create-first-tasks.json create mode 100644 worklenz-frontend/public/locales/en/home.json create mode 100644 worklenz-frontend/public/locales/en/invite-initial-team-members.json create mode 100644 worklenz-frontend/public/locales/en/kanban-board.json create mode 100644 worklenz-frontend/public/locales/en/license-expired.json create mode 100644 worklenz-frontend/public/locales/en/navbar.json create mode 100644 worklenz-frontend/public/locales/en/organization-name-form.json create mode 100644 worklenz-frontend/public/locales/en/phases-drawer.json create mode 100644 worklenz-frontend/public/locales/en/project-drawer.json create mode 100644 worklenz-frontend/public/locales/en/project-view-files.json create mode 100644 worklenz-frontend/public/locales/en/project-view-insights.json create mode 100644 worklenz-frontend/public/locales/en/project-view-members.json create mode 100644 worklenz-frontend/public/locales/en/project-view-updates.json create mode 100644 worklenz-frontend/public/locales/en/project-view/import-task-templates.json create mode 100644 worklenz-frontend/public/locales/en/project-view/project-member-drawer.json create mode 100644 worklenz-frontend/public/locales/en/project-view/project-view-header.json create mode 100644 worklenz-frontend/public/locales/en/project-view/save-as-template.json create mode 100644 worklenz-frontend/public/locales/en/reporting-members-drawer.json create mode 100644 worklenz-frontend/public/locales/en/reporting-members.json create mode 100644 worklenz-frontend/public/locales/en/reporting-overview-drawer.json create mode 100644 worklenz-frontend/public/locales/en/reporting-overview.json create mode 100644 worklenz-frontend/public/locales/en/reporting-projects-drawer.json create mode 100644 worklenz-frontend/public/locales/en/reporting-projects-filters.json create mode 100644 worklenz-frontend/public/locales/en/reporting-projects.json create mode 100644 worklenz-frontend/public/locales/en/reporting-sidebar.json create mode 100644 worklenz-frontend/public/locales/en/schedule.json create mode 100644 worklenz-frontend/public/locales/en/settings/categories.json create mode 100644 worklenz-frontend/public/locales/en/settings/change-password.json create mode 100644 worklenz-frontend/public/locales/en/settings/clients.json create mode 100644 worklenz-frontend/public/locales/en/settings/job-titles.json create mode 100644 worklenz-frontend/public/locales/en/settings/labels.json create mode 100644 worklenz-frontend/public/locales/en/settings/language.json create mode 100644 worklenz-frontend/public/locales/en/settings/notifications.json create mode 100644 worklenz-frontend/public/locales/en/settings/profile.json create mode 100644 worklenz-frontend/public/locales/en/settings/project-templates.json create mode 100644 worklenz-frontend/public/locales/en/settings/sidebar.json create mode 100644 worklenz-frontend/public/locales/en/settings/task-templates.json create mode 100644 worklenz-frontend/public/locales/en/settings/team-members.json create mode 100644 worklenz-frontend/public/locales/en/task-drawer/task-drawer-info-tab.json create mode 100644 worklenz-frontend/public/locales/en/task-drawer/task-drawer.json create mode 100644 worklenz-frontend/public/locales/en/task-list-filters.json create mode 100644 worklenz-frontend/public/locales/en/task-list-table.json create mode 100644 worklenz-frontend/public/locales/en/task-template-drawer.json create mode 100644 worklenz-frontend/public/locales/en/tasks/task-table-bulk-actions.json create mode 100644 worklenz-frontend/public/locales/en/template-drawer.json create mode 100644 worklenz-frontend/public/locales/en/templateDrawer.json create mode 100644 worklenz-frontend/public/locales/en/time-report.json create mode 100644 worklenz-frontend/public/locales/en/unauthorized.json create mode 100644 worklenz-frontend/public/locales/es/404-page.json create mode 100644 worklenz-frontend/public/locales/es/account-setup.json create mode 100644 worklenz-frontend/public/locales/es/admin-center/current-bill.json create mode 100644 worklenz-frontend/public/locales/es/admin-center/overview.json create mode 100644 worklenz-frontend/public/locales/es/admin-center/projects.json create mode 100644 worklenz-frontend/public/locales/es/admin-center/sidebar.json create mode 100644 worklenz-frontend/public/locales/es/admin-center/teams.json create mode 100644 worklenz-frontend/public/locales/es/admin-center/users.json create mode 100644 worklenz-frontend/public/locales/es/all-project-list.json create mode 100644 worklenz-frontend/public/locales/es/auth/auth-common.json create mode 100644 worklenz-frontend/public/locales/es/auth/forgot-password.json create mode 100644 worklenz-frontend/public/locales/es/auth/login.json create mode 100644 worklenz-frontend/public/locales/es/auth/signup.json create mode 100644 worklenz-frontend/public/locales/es/auth/verify-reset-email.json create mode 100644 worklenz-frontend/public/locales/es/common.json create mode 100644 worklenz-frontend/public/locales/es/create-first-project-form.json create mode 100644 worklenz-frontend/public/locales/es/create-first-tasks.json create mode 100644 worklenz-frontend/public/locales/es/home.json create mode 100644 worklenz-frontend/public/locales/es/invite-initial-team-members.json create mode 100644 worklenz-frontend/public/locales/es/kanban-board.json create mode 100644 worklenz-frontend/public/locales/es/license-expired.json create mode 100644 worklenz-frontend/public/locales/es/navbar.json create mode 100644 worklenz-frontend/public/locales/es/organization-name-form.json create mode 100644 worklenz-frontend/public/locales/es/phases-drawer.json create mode 100644 worklenz-frontend/public/locales/es/project-drawer.json create mode 100644 worklenz-frontend/public/locales/es/project-view-files.json create mode 100644 worklenz-frontend/public/locales/es/project-view-insights.json create mode 100644 worklenz-frontend/public/locales/es/project-view-members.json create mode 100644 worklenz-frontend/public/locales/es/project-view-updates.json create mode 100644 worklenz-frontend/public/locales/es/project-view/import-task-templates.json create mode 100644 worklenz-frontend/public/locales/es/project-view/project-member-drawer.json create mode 100644 worklenz-frontend/public/locales/es/project-view/project-view-header.json create mode 100644 worklenz-frontend/public/locales/es/project-view/save-as-template.json create mode 100644 worklenz-frontend/public/locales/es/reporting-members-drawer.json create mode 100644 worklenz-frontend/public/locales/es/reporting-members.json create mode 100644 worklenz-frontend/public/locales/es/reporting-overview-drawer.json create mode 100644 worklenz-frontend/public/locales/es/reporting-overview.json create mode 100644 worklenz-frontend/public/locales/es/reporting-projects-drawer.json create mode 100644 worklenz-frontend/public/locales/es/reporting-projects-filters.json create mode 100644 worklenz-frontend/public/locales/es/reporting-projects.json create mode 100644 worklenz-frontend/public/locales/es/reporting-sidebar.json create mode 100644 worklenz-frontend/public/locales/es/schedule.json create mode 100644 worklenz-frontend/public/locales/es/settings/categories.json create mode 100644 worklenz-frontend/public/locales/es/settings/change-password.json create mode 100644 worklenz-frontend/public/locales/es/settings/clients.json create mode 100644 worklenz-frontend/public/locales/es/settings/job-titles.json create mode 100644 worklenz-frontend/public/locales/es/settings/labels.json create mode 100644 worklenz-frontend/public/locales/es/settings/language.json create mode 100644 worklenz-frontend/public/locales/es/settings/notifications.json create mode 100644 worklenz-frontend/public/locales/es/settings/profile.json create mode 100644 worklenz-frontend/public/locales/es/settings/project-templates.json create mode 100644 worklenz-frontend/public/locales/es/settings/sidebar.json create mode 100644 worklenz-frontend/public/locales/es/settings/task-templates.json create mode 100644 worklenz-frontend/public/locales/es/settings/team-members.json create mode 100644 worklenz-frontend/public/locales/es/task-drawer/task-drawer-info-tab.json create mode 100644 worklenz-frontend/public/locales/es/task-drawer/task-drawer.json create mode 100644 worklenz-frontend/public/locales/es/task-list-filters.json create mode 100644 worklenz-frontend/public/locales/es/task-list-table.json create mode 100644 worklenz-frontend/public/locales/es/task-template-drawer.json create mode 100644 worklenz-frontend/public/locales/es/tasks/task-table-bulk-actions.json create mode 100644 worklenz-frontend/public/locales/es/template-drawer.json create mode 100644 worklenz-frontend/public/locales/es/templateDrawer.json create mode 100644 worklenz-frontend/public/locales/es/time-report.json create mode 100644 worklenz-frontend/public/locales/es/unauthorized.json create mode 100644 worklenz-frontend/public/locales/pt/404-page.json create mode 100644 worklenz-frontend/public/locales/pt/account-setup.json create mode 100644 worklenz-frontend/public/locales/pt/admin-center/current-bill.json create mode 100644 worklenz-frontend/public/locales/pt/admin-center/overview.json create mode 100644 worklenz-frontend/public/locales/pt/admin-center/projects.json create mode 100644 worklenz-frontend/public/locales/pt/admin-center/sidebar.json create mode 100644 worklenz-frontend/public/locales/pt/admin-center/teams.json create mode 100644 worklenz-frontend/public/locales/pt/admin-center/users.json create mode 100644 worklenz-frontend/public/locales/pt/all-project-list.json create mode 100644 worklenz-frontend/public/locales/pt/auth/auth-common.json create mode 100644 worklenz-frontend/public/locales/pt/auth/forgot-password.json create mode 100644 worklenz-frontend/public/locales/pt/auth/login.json create mode 100644 worklenz-frontend/public/locales/pt/auth/signup.json create mode 100644 worklenz-frontend/public/locales/pt/auth/verify-reset-email.json create mode 100644 worklenz-frontend/public/locales/pt/common.json create mode 100644 worklenz-frontend/public/locales/pt/create-first-project-form.json create mode 100644 worklenz-frontend/public/locales/pt/create-first-tasks.json create mode 100644 worklenz-frontend/public/locales/pt/home.json create mode 100644 worklenz-frontend/public/locales/pt/invite-initial-team-members.json create mode 100644 worklenz-frontend/public/locales/pt/kanban-board.json create mode 100644 worklenz-frontend/public/locales/pt/license-expired.json create mode 100644 worklenz-frontend/public/locales/pt/navbar.json create mode 100644 worklenz-frontend/public/locales/pt/organization-name-form.json create mode 100644 worklenz-frontend/public/locales/pt/phases-drawer.json create mode 100644 worklenz-frontend/public/locales/pt/project-drawer.json create mode 100644 worklenz-frontend/public/locales/pt/project-view-files.json create mode 100644 worklenz-frontend/public/locales/pt/project-view-insights.json create mode 100644 worklenz-frontend/public/locales/pt/project-view-members.json create mode 100644 worklenz-frontend/public/locales/pt/project-view-updates.json create mode 100644 worklenz-frontend/public/locales/pt/project-view/import-task-templates.json create mode 100644 worklenz-frontend/public/locales/pt/project-view/project-member-drawer.json create mode 100644 worklenz-frontend/public/locales/pt/project-view/project-view-header.json create mode 100644 worklenz-frontend/public/locales/pt/project-view/save-as-template.json create mode 100644 worklenz-frontend/public/locales/pt/reporting-members-drawer.json create mode 100644 worklenz-frontend/public/locales/pt/reporting-members.json create mode 100644 worklenz-frontend/public/locales/pt/reporting-overview-drawer.json create mode 100644 worklenz-frontend/public/locales/pt/reporting-overview.json create mode 100644 worklenz-frontend/public/locales/pt/reporting-projects-drawer.json create mode 100644 worklenz-frontend/public/locales/pt/reporting-projects-filters.json create mode 100644 worklenz-frontend/public/locales/pt/reporting-projects.json create mode 100644 worklenz-frontend/public/locales/pt/reporting-sidebar.json create mode 100644 worklenz-frontend/public/locales/pt/schedule.json create mode 100644 worklenz-frontend/public/locales/pt/settings/categories.json create mode 100644 worklenz-frontend/public/locales/pt/settings/change-password.json create mode 100644 worklenz-frontend/public/locales/pt/settings/clients.json create mode 100644 worklenz-frontend/public/locales/pt/settings/job-titles.json create mode 100644 worklenz-frontend/public/locales/pt/settings/labels.json create mode 100644 worklenz-frontend/public/locales/pt/settings/language.json create mode 100644 worklenz-frontend/public/locales/pt/settings/notifications.json create mode 100644 worklenz-frontend/public/locales/pt/settings/profile.json create mode 100644 worklenz-frontend/public/locales/pt/settings/project-templates.json create mode 100644 worklenz-frontend/public/locales/pt/settings/sidebar.json create mode 100644 worklenz-frontend/public/locales/pt/settings/task-templates.json create mode 100644 worklenz-frontend/public/locales/pt/settings/team-members.json create mode 100644 worklenz-frontend/public/locales/pt/task-drawer/task-drawer-info-tab.json create mode 100644 worklenz-frontend/public/locales/pt/task-drawer/task-drawer.json create mode 100644 worklenz-frontend/public/locales/pt/task-list-filters.json create mode 100644 worklenz-frontend/public/locales/pt/task-list-table.json create mode 100644 worklenz-frontend/public/locales/pt/task-template-drawer.json create mode 100644 worklenz-frontend/public/locales/pt/tasks/task-table-bulk-actions.json create mode 100644 worklenz-frontend/public/locales/pt/template-drawer.json create mode 100644 worklenz-frontend/public/locales/pt/templateDrawer.json create mode 100644 worklenz-frontend/public/locales/pt/time-report.json create mode 100644 worklenz-frontend/public/locales/pt/unauthorized.json create mode 100644 worklenz-frontend/public/scheduler-data/dates-list copy.json create mode 100644 worklenz-frontend/public/scheduler-data/dates-list.json create mode 100644 worklenz-frontend/public/scheduler-data/team-data copy.json create mode 100644 worklenz-frontend/public/scheduler-data/team-data.json create mode 100644 worklenz-frontend/scripts/copy-tinymce.js create mode 100644 worklenz-frontend/src/App.tsx create mode 100644 worklenz-frontend/src/api/admin-center/admin-center.api.service.ts create mode 100644 worklenz-frontend/src/api/admin-center/billing.api.service.ts create mode 100644 worklenz-frontend/src/api/api-client.ts create mode 100644 worklenz-frontend/src/api/attachments/attachments.api.service.ts create mode 100644 worklenz-frontend/src/api/auth/auth.api.service.ts create mode 100644 worklenz-frontend/src/api/clients/clients.api.service.ts create mode 100644 worklenz-frontend/src/api/home-page/home-page.api.service.ts create mode 100644 worklenz-frontend/src/api/notifications/notifications.api.service.ts create mode 100644 worklenz-frontend/src/api/project-members/project-members.api.service.ts create mode 100644 worklenz-frontend/src/api/project-templates/project-templates.api.service.ts create mode 100644 worklenz-frontend/src/api/projects/comments/project-comments.api.service.ts create mode 100644 worklenz-frontend/src/api/projects/insights/project-insights.api.service.ts create mode 100644 worklenz-frontend/src/api/projects/lookups/projectHealth.api.service.ts create mode 100644 worklenz-frontend/src/api/projects/lookups/projectStatus.api.service.ts create mode 100644 worklenz-frontend/src/api/projects/projects.api.service.ts create mode 100644 worklenz-frontend/src/api/projects/projects.v1.api.service.ts create mode 100644 worklenz-frontend/src/api/reporting/reporting-export.api.service.ts create mode 100644 worklenz-frontend/src/api/reporting/reporting-members.api.service.ts create mode 100644 worklenz-frontend/src/api/reporting/reporting-projects.api.service.ts create mode 100644 worklenz-frontend/src/api/reporting/reporting.api.service.ts create mode 100644 worklenz-frontend/src/api/reporting/reporting.timesheet.api.service.ts create mode 100644 worklenz-frontend/src/api/schedule/schedule.api.service.ts create mode 100644 worklenz-frontend/src/api/settings/categories/categories.api.service.ts create mode 100644 worklenz-frontend/src/api/settings/job-titles/job-titles.api.service.ts create mode 100644 worklenz-frontend/src/api/settings/labels/labels.api.service.ts create mode 100644 worklenz-frontend/src/api/settings/language-timezones/language-timezones-api.service.ts create mode 100644 worklenz-frontend/src/api/settings/profile/profile-settings.api.service.ts create mode 100644 worklenz-frontend/src/api/task-templates/task-templates.api.service.ts create mode 100644 worklenz-frontend/src/api/taskAttributes/labels/labels.api.service.ts create mode 100644 worklenz-frontend/src/api/taskAttributes/phases/phases.api.service.ts create mode 100644 worklenz-frontend/src/api/taskAttributes/priority/priority.api.service.ts create mode 100644 worklenz-frontend/src/api/taskAttributes/status/status.api.service.ts create mode 100644 worklenz-frontend/src/api/tasks/subtasks.api.service.ts create mode 100644 worklenz-frontend/src/api/tasks/task-activity-logs.api.service.ts create mode 100644 worklenz-frontend/src/api/tasks/task-attachments.api.service.ts create mode 100644 worklenz-frontend/src/api/tasks/task-comments.api.service.ts create mode 100644 worklenz-frontend/src/api/tasks/task-dependencies.api.service.ts create mode 100644 worklenz-frontend/src/api/tasks/task-list-bulk-actions.api.service.ts create mode 100644 worklenz-frontend/src/api/tasks/task-time-logs.api.service.ts create mode 100644 worklenz-frontend/src/api/tasks/tasks-custom-columns.service.ts create mode 100644 worklenz-frontend/src/api/tasks/tasks.api.service.ts create mode 100644 worklenz-frontend/src/api/team-members/teamMembers.api.service.ts create mode 100644 worklenz-frontend/src/api/teams/teams.api.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/account-setup/account-setup-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/account-setup/account-setup.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/account-setup/account-setup/account-setup.component.html delete mode 100644 worklenz-frontend/src/app/administrator/account-setup/account-setup/account-setup.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/account-setup/account-setup/account-setup.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/account-setup/account-setup/account-setup.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/account-setup/teams-list/teams-list.component.html delete mode 100644 worklenz-frontend/src/app/administrator/account-setup/teams-list/teams-list.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/account-setup/teams-list/teams-list.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/account-setup/teams-list/teams-list.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/admin-center-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/admin-center-service.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/admin-center.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/layout/layout.component.html delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/layout/layout.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/layout/layout.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/layout/layout.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/overview/overview.component.html delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/overview/overview.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/overview/overview.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/overview/overview.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/sidebar/sidebar.component.html delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/sidebar/sidebar.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/sidebar/sidebar.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/teams/teams.component.html delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/teams/teams.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/teams/teams.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/teams/teams.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/users/users.component.html delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/users/users.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/users/users.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/admin-center/users/users.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/administrator-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/administrator.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/avatars/avatars.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/avatars/avatars.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/avatars/avatars.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/clients-autocomplete/clients-autocomplete.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/clients-autocomplete/clients-autocomplete.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/clients-autocomplete/clients-autocomplete.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/convert-to-subtask-modal/convert-to-subtask-modal.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/convert-to-subtask-modal/convert-to-subtask-modal.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/convert-to-subtask-modal/convert-to-subtask-modal.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/convert-to-subtask-modal/convert-to-subtask-modal.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/import-tasks-template/import-tasks-template.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/import-tasks-template/import-tasks-template.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/import-tasks-template/import-tasks-template.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/job-titles-autocomplete/job-titles-autocomplete.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/job-titles-autocomplete/job-titles-autocomplete.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/job-titles-autocomplete/job-titles-autocomplete.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/job-titles-autocomplete/job-titles-autocomplete.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/na/na.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/na/na.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/na/na.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/na/na.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/personal-overview/personal-overview.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/personal-overview/personal-overview.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/personal-overview/personal-overview.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/personal-overview/personal-overview.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-categories-autocomplete/project-categories-autocomplete.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/project-categories-autocomplete/project-categories-autocomplete.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/project-categories-autocomplete/project-categories-autocomplete.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-categories-autocomplete/project-categories-autocomplete.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-folders-autocomplete/project-folders-autocomplete.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/project-folders-autocomplete/project-folders-autocomplete.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/project-folders-autocomplete/project-folders-autocomplete.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-folders-autocomplete/project-folders-autocomplete.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-form-modal/project-form-modal.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/project-form-modal/project-form-modal.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/project-form-modal/project-form-modal.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-form-modal/project-form-modal.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-members-form/project-members-form.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/project-members-form/project-members-form.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/project-members-form/project-members-form.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-members-form/project-members-form.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-template-create-drawer/project-template-create-drawer.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/project-template-create-drawer/project-template-create-drawer.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/project-template-create-drawer/project-template-create-drawer.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-template-create-drawer/project-template-create-drawer.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-template-import-drawer/custom-template-list/custom-template-list.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/project-template-import-drawer/custom-template-list/custom-template-list.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/project-template-import-drawer/custom-template-list/custom-template-list.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-template-import-drawer/custom-template-list/custom-template-list.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-template-import-drawer/project-template-import-drawer.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/project-template-import-drawer/project-template-import-drawer.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/project-template-import-drawer/project-template-import-drawer.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-template-import-drawer/project-template-import-drawer.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-template-import-drawer/worklenz-template-list/worklenz-template-list.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/project-template-import-drawer/worklenz-template-list/worklenz-template-list.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/project-template-import-drawer/worklenz-template-list/worklenz-template-list.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-template-import-drawer/worklenz-template-list/worklenz-template-list.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-updates-drawer/project-updates-drawer.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/project-updates-drawer/project-updates-drawer.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/project-updates-drawer/project-updates-drawer.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-updates-drawer/project-updates-drawer.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-updates-input/project-updates-input.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/project-updates-input/project-updates-input.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/project-updates-input/project-updates-input.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-updates-input/project-updates-input.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-updates-list/project-updates-list.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/project-updates-list/project-updates-list.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/project-updates-list/project-updates-list.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/project-updates-list/project-updates-list.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/projects-autocomplete/projects-autocomplete.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/projects-autocomplete/projects-autocomplete.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/projects-autocomplete/projects-autocomplete.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/projects-autocomplete/projects-autocomplete.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/resource-gantt/resource-gantt.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/resource-gantt/resource-gantt.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/resource-gantt/resource-gantt.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/resource-gantt/resource-gantt.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/status-form/status-form.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/status-form/status-form.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/status-form/status-form.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/status-form/status-form.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-priority-label/task-priority-label.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-priority-label/task-priority-label.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-priority-label/task-priority-label.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-priority-label/task-priority-label.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-template-drawer/task-template-drawer.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-template-drawer/task-template-drawer.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-template-drawer/task-template-drawer.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-template-drawer/task-template-drawer.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-timer/interfaces.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-timer/task-timer.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-timer/task-timer.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-timer/task-timer.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-timer/task-timer.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-timer/task-timer.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/interfaces.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-activity-log/task-view-activity-log.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-activity-log/task-view-activity-log.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-activity-log/task-view-activity-log.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-activity-log/task-view-activity-log.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-assignees/task-view-assignees.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-assignees/task-view-assignees.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-assignees/task-view-assignees.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-assignees/task-view-assignees.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments-thumb/task-view-attachments-thumb.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments-thumb/task-view-attachments-thumb.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments-thumb/task-view-attachments-thumb.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments-thumb/task-view-attachments-thumb.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-comments-input/task-view-comments-input.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-comments-input/task-view-comments-input.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-comments-input/task-view-comments-input.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-comments-input/task-view-comments-input.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-comments/task-view-comments.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-comments/task-view-comments.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-comments/task-view-comments.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-comments/task-view-comments.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-description/task-view-description.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-description/task-view-description.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-description/task-view-description.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-description/task-view-description.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-due-date/task-view-due-date.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-due-date/task-view-due-date.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-due-date/task-view-due-date.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-due-date/task-view-due-date.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-estimation/task-view-estimation.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-estimation/task-view-estimation.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-estimation/task-view-estimation.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-estimation/task-view-estimation.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-info/task-view-info.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-info/task-view-info.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-info/task-view-info.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-info/task-view-info.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-labels/task-view-labels.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-labels/task-view-labels.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-labels/task-view-labels.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-labels/task-view-labels.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-name/task-view-name.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-name/task-view-name.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-name/task-view-name.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-name/task-view-name.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-notify-to-user/task-view-notify-to-user.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-notify-to-user/task-view-notify-to-user.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-notify-to-user/task-view-notify-to-user.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-notify-to-user/task-view-notify-to-user.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-phase/task-view-phase.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-phase/task-view-phase.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-phase/task-view-phase.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-phase/task-view-phase.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-priority/task-view-priority.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-priority/task-view-priority.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-priority/task-view-priority.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-priority/task-view-priority.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-sub-tasks/task-view-sub-tasks.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-sub-tasks/task-view-sub-tasks.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-sub-tasks/task-view-sub-tasks.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-sub-tasks/task-view-sub-tasks.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-time-log/task-view-time-log.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-time-log/task-view-time-log.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-time-log/task-view-time-log.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view-time-log/task-view-time-log.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view.service.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/task-view/task-view.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/tasks-group-view/tasks-group-view.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/tasks-group-view/tasks-group-view.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/tasks-group-view/tasks-group-view.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/tasks-group-view/tasks-group-view.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/tasks-progress-bar/tasks-progress-bar.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/tasks-progress-bar/tasks-progress-bar.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/tasks-progress-bar/tasks-progress-bar.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/tasks-progress-bar/tasks-progress-bar.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/tasks-progress-bar/with-percentage-mark.pipe.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/team-members-autocomplete/team-members-autocomplete.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/team-members-autocomplete/team-members-autocomplete.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/team-members-autocomplete/team-members-autocomplete.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/team-members-autocomplete/team-members-autocomplete.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/team-members-form/team-members-form.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/team-members-form/team-members-form.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/team-members-form/team-members-form.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/team-members-form/team-members-form.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/toggle-menu-button/toggle-menu-button.component.html delete mode 100644 worklenz-frontend/src/app/administrator/components/toggle-menu-button/toggle-menu-button.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/components/toggle-menu-button/toggle-menu-button.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/components/toggle-menu-button/toggle-menu-button.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/layout/alerts/alerts.component.html delete mode 100644 worklenz-frontend/src/app/administrator/layout/alerts/alerts.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/layout/alerts/alerts.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/layout/alerts/alerts.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/layout/header/header.component.html delete mode 100644 worklenz-frontend/src/app/administrator/layout/header/header.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/layout/header/header.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/layout/header/header.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/layout/layout.component.html delete mode 100644 worklenz-frontend/src/app/administrator/layout/layout.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/layout/layout.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/layout/layout.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/layout/licensing-alerts/licensing-alerts.component.html delete mode 100644 worklenz-frontend/src/app/administrator/layout/licensing-alerts/licensing-alerts.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/layout/licensing-alerts/licensing-alerts.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/layout/licensing-alerts/licensing-alerts.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/layout/notifications-drawer/notification-template/notification-template.component.html delete mode 100644 worklenz-frontend/src/app/administrator/layout/notifications-drawer/notification-template/notification-template.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/layout/notifications-drawer/notification-template/notification-template.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/layout/notifications-drawer/notification-template/notification-template.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/layout/notifications-drawer/notifications-drawer.component.html delete mode 100644 worklenz-frontend/src/app/administrator/layout/notifications-drawer/notifications-drawer.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/layout/notifications-drawer/notifications-drawer.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/layout/notifications-drawer/notifications-drawer.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/layout/notifications-drawer/tag-background.pipe.ts delete mode 100644 worklenz-frontend/src/app/administrator/layout/notifications-drawer/types.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-card/task-card.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-card/task-card.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-card/task-card.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-card/task-card.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-creation-assignees/task-creation-assignees.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-creation-assignees/task-creation-assignees.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-creation-assignees/task-creation-assignees.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-creation-assignees/task-creation-assignees.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-end-date/task-end-date.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-end-date/task-end-date.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-end-date/task-end-date.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-end-date/task-end-date.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-labels/task-labels.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-labels/task-labels.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-labels/task-labels.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-labels/task-labels.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-members/task-members.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-members/task-members.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-members/task-members.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-members/task-members.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-name/task-name.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-name/task-name.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-name/task-name.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-name/task-name.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-priority/task-priority.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-priority/task-priority.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-priority/task-priority.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-priority/task-priority.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-progress/task-progress.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-progress/task-progress.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-progress/task-progress.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-progress/task-progress.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-subtask-count/task-subtask-count.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-subtask-count/task-subtask-count.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-subtask-count/task-subtask-count.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-subtask-count/task-subtask-count.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/kanban-board.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/kanban-board.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/kanban-board.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/kanban-board.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/models/board.model.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/models/column.model.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/pipes/validate-min-date.pipe.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-hash-map-service.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-view-v2-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-view-v2.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-view-v2.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/gantt-chart-v2-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/gantt-chart-v2.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-input/add-task-input.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-input/add-task-input.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-input/add-task-input.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-input/add-task-input.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-row/add-task-row.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-row/add-task-row.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-row/add-task-row.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-row/add-task-row.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/end-date/end-date.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/end-date/end-date.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/end-date/end-date.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/end-date/end-date.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/filters/filters.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/filters/filters.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/filters/filters.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/filters/filters.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/start-date/start-date.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/start-date/start-date.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/start-date/start-date.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/start-date/start-date.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-bar/task-bar.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-bar/task-bar.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-bar/task-bar.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-bar/task-bar.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-name/task-name.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-name/task-name.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-name/task-name.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-name/task-name.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/directives/drag-move.directive.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/project-roadmap-v2-custom.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/project-roadmap-v2-custom.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/project-roadmap-v2-custom.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/project-roadmap-v2-custom.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/services/roadmap-v2-hashmap.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/services/roadmap-v2-service.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/interfaces.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/pipes/end-name-check.pipe.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-columns-toggle/task-list-columns-toggle.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-columns-toggle/task-list-columns-toggle.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-columns-toggle/task-list-columns-toggle.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-columns-toggle/task-list-columns-toggle.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-filters/task-list-filters.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-filters/task-list-filters.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-filters/task-list-filters.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-filters/task-list-filters.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-hash-map.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-add-task-input/task-list-add-task-input.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-add-task-input/task-list-add-task-input.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-add-task-input/task-list-add-task-input.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-add-task-input/task-list-add-task-input.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-bulk-actions/task-list-bulk-actions.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-bulk-actions/task-list-bulk-actions.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-bulk-actions/task-list-bulk-actions.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-bulk-actions/task-list-bulk-actions.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/interfaces/convert-subtask-request.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/subtask-convert-service.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/task-list-context-menu.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/task-list-context-menu.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/task-list-context-menu.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/task-list-context-menu.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-group-settings/task-list-group-settings.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-group-settings/task-list-group-settings.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-group-settings/task-list-group-settings.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-group-settings/task-list-group-settings.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-header/task-list-header.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-header/task-list-header.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-header/task-list-header.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-header/task-list-header.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-duration/task-list-phase-duration.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-duration/task-list-phase-duration.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-duration/task-list-phase-duration.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-duration/task-list-phase-duration.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-settings-drawer/task-list-phase-settings-drawer.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-settings-drawer/task-list-phase-settings-drawer.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-settings-drawer/task-list-phase-settings-drawer.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-settings-drawer/task-list-phase-settings-drawer.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/ellipsis-tooltip-title.pipe.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/sub-tasks-arrow-color.pipe.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/sub-tasks-arrow-icon.pipe.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/truncate-if-long.pipe.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/validate-max-date.pipe.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/validate-min-date.pipe.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-description/task-list-description.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-description/task-list-description.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-description/task-list-description.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-description/task-list-description.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-end-date/task-list-end-date.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-end-date/task-list-end-date.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-end-date/task-list-end-date.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-end-date/task-list-end-date.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-labels/task-list-labels.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-labels/task-list-labels.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-labels/task-list-labels.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-labels/task-list-labels.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-members/task-list-members.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-members/task-list-members.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-members/task-list-members.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-members/task-list-members.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-phase/task-list-phase.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-phase/task-list-phase.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-phase/task-list-phase.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-phase/task-list-phase.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-priority/task-list-priority.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-priority/task-list-priority.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-priority/task-list-priority.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-priority/task-list-priority.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-row.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-row.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-row.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-row.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-start-date/task-list-start-date.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-start-date/task-list-start-date.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-start-date/task-list-start-date.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-start-date/task-list-start-date.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-status/task-list-status.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-status/task-list-status.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-status/task-list-status.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-status/task-list-status.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-timer/task-list-timer.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-timer/task-list-timer.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-timer/task-list-timer.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-timer/task-list-timer.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-progress/task-progress.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-progress/task-progress.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-progress/task-progress.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-progress/task-progress.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-table.component.html delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-table.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-table.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-table.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-v2-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-v2.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-v2.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/activity-log/activity-log.component.html delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/activity-log/activity-log.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/activity-log/activity-log.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/activity-log/activity-log.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/dashboard.component.html delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/dashboard.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/dashboard.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/dashboard.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-projects/my-projects.component.html delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-projects/my-projects.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-projects/my-projects.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-projects/my-projects.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-done/task-done.component.html delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-done/task-done.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-done/task-done.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-done/task-done.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-due-date/task-due-date.component.html delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-due-date/task-due-date.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-due-date/task-due-date.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-due-date/task-due-date.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-name/task-name.component.html delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-name/task-name.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-name/task-name.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-name/task-name.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-project/task-project.component.html delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-project/task-project.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-project/task-project.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-project/task-project.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-status/task-status.component.html delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-status/task-status.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-status/task-status.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-status/task-status.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/task-add-container/task-add-container.component.html delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/task-add-container/task-add-container.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/task-add-container/task-add-container.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/task-add-container/task-add-container.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/tasks-table.component.html delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/tasks-table.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/tasks-table.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/tasks-table.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/my-tasks.component.html delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/my-tasks.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/my-tasks.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/my-tasks.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/personal-tasks/personal-tasks.component.html delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/personal-tasks/personal-tasks.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/personal-tasks/personal-tasks.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/dashboard/personal-tasks/personal-tasks.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/homepage-service.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/homepage.enum.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/intefaces.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/my-dashboard-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/my-dashboard.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/personal-todo-list/personal-todo-list.component.html delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/personal-todo-list/personal-todo-list.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/personal-todo-list/personal-todo-list.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/personal-todo-list/personal-todo-list.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/projects-tasks/projects-tasks.component.html delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/projects-tasks/projects-tasks.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/projects-tasks/projects-tasks.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/my-dashboard/projects-tasks/projects-tasks.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/all-tasks-attachments/all-tasks-attachments.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/all-tasks-attachments/all-tasks-attachments.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/all-tasks-attachments/all-tasks-attachments.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/all-tasks-attachments/all-tasks-attachments.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/context-menu/context-menu.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/context-menu/context-menu.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/context-menu/context-menu.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/context-menu/context-menu.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/end-date/end-date.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/end-date/end-date.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/end-date/end-date.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/end-date/end-date.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-task-add-container/member-task-add-container.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-task-add-container/member-task-add-container.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-task-add-container/member-task-add-container.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-task-add-container/member-task-add-container.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/member-tasks-drawer.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/member-tasks-drawer.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/member-tasks-drawer.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/member-tasks-drawer.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/overview-tab/overview-tab.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/overview-tab/overview-tab.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/overview-tab/overview-tab.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/overview-tab/overview-tab.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/phase/phase.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/phase/phase.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/phase/phase.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/phase/phase.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/priority/priority.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/priority/priority.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/priority/priority.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/priority/priority.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/start-date/start-date.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/start-date/start-date.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/start-date/start-date.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/start-date/start-date.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/status/status.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/status/status.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/status/status.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/status/status.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-header/task-list-header.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-header/task-list-header.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-header/task-list-header.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-header/task-list-header.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-row/task-list-row.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-row/task-list-row.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-row/task-list-row.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-row/task-list-row.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-name/task-name.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-name/task-name.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-name/task-name.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-name/task-name.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/services/wl-tasks-hash-map.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/services/wl-tasks.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/workload-gaant-chart-v2.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/workload-gaant-chart-v2.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/workload-gaant-chart-v2.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/workload-gaant-chart-v2.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/components/project-stats/project-stats.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/components/project-stats/project-stats.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/components/project-stats/project-stats.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/components/project-stats/project-stats.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-insights.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-insights.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-insights.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-insights.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/last-updated-tasks/last-updated-tasks.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/last-updated-tasks/last-updated-tasks.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/last-updated-tasks/last-updated-tasks.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/last-updated-tasks/last-updated-tasks.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/priority-breakdown/priority-breakdown.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/priority-breakdown/priority-breakdown.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/priority-breakdown/priority-breakdown.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/priority-breakdown/priority-breakdown.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/project-deadline/project-deadline.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/project-deadline/project-deadline.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/project-deadline/project-deadline.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/project-deadline/project-deadline.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/status-overview/status-overview.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/status-overview/status-overview.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/status-overview/status-overview.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/status-overview/status-overview.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-stats/member-stats.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-stats/member-stats.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-stats/member-stats.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-stats/member-stats.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-tasks/member-tasks.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-tasks/member-tasks.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-tasks/member-tasks.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-tasks/member-tasks.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-members/project-insights-member-overview/project-insights-member-overview.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-members/project-insights-member-overview/project-insights-member-overview.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-members/project-insights-member-overview/project-insights-member-overview.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-members/project-insights-member-overview/project-insights-member-overview.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-overview/project-overview.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-overview/project-overview.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-overview/project-overview.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/project-overview/project-overview.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/task-insights/task-insights.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/task-insights/task-insights.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/task-insights/task-insights.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-insights/task-insights/task-insights.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-members/project-members.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-members/project-members.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-members/project-members.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-members/project-members.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-updates/project-updates.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-updates/project-updates.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-updates/project-updates.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-updates/project-updates.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-view/project-sharing/project-sharing-functions.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-view/project-sharing/template.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-view/project-view-extra/project-view-extra.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-view/project-view-extra/project-view-extra.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-view/project-view-extra/project-view-extra.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-view/project-view-extra/project-view-extra.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-view/project-view.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-view/project-view.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-view/project-view.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/project-view/project-view.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects.service.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects/pipes/project-filter-by-tooltip.pipe.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects/pipes/project-filter-by-tooltip.pipe.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects/projects-folder-form-drawer/projects-folder-form-drawer.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects/projects-folder-form-drawer/projects-folder-form-drawer.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects/projects-folder-form-drawer/projects-folder-form-drawer.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects/projects-folder-form-drawer/projects-folder-form-drawer.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects/projects-folder-form-drawer/projects-folder-form-drawer.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects/projects-folder-view/projects-folder-view.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects/projects-folder-view/projects-folder-view.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects/projects-folder-view/projects-folder-view.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects/projects-folder-view/projects-folder-view.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects/projects-list-view/projects-list-view.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects/projects-list-view/projects-list-view.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects/projects-list-view/projects-list-view.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects/projects-list-view/projects-list-view.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects/projects.component.html delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects/projects.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects/projects.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/projects/projects/projects.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/estimated-vs-actual-chart/estimated-vs-actual-chart.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/estimated-vs-actual-chart/estimated-vs-actual-chart.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/estimated-vs-actual-chart/estimated-vs-actual-chart.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/estimated-vs-actual-chart/estimated-vs-actual-chart.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/member-logs-breakdown/member-logs-breakdown.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/member-logs-breakdown/member-logs-breakdown.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/member-logs-breakdown/member-logs-breakdown.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/member-logs-breakdown/member-logs-breakdown.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/project-category/project-category.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/project-category/project-category.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/project-category/project-category.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/project-category/project-category.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/project-health/project-health.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/project-health/project-health.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/project-health/project-health.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/project-health/project-health.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/project-logs-breakdown/project-logs-breakdown.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/project-logs-breakdown/project-logs-breakdown.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/project-logs-breakdown/project-logs-breakdown.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/project-logs-breakdown/project-logs-breakdown.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/project-start-end-dates/project-start-end-dates.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/project-start-end-dates/project-start-end-dates.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/project-start-end-dates/project-start-end-dates.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/project-start-end-dates/project-start-end-dates.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/project-status/project-status.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/project-status/project-status.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/project-status/project-status.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/project-status/project-status.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/rpt-header/rpt-header.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/rpt-header/rpt-header.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/rpt-header/rpt-header.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/rpt-header/rpt-header.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/rpt-layout/rpt-layout.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/rpt-layout/rpt-layout.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/rpt-layout/rpt-layout.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/components/rpt-layout/rpt-layout.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-drawer-title/rpt-drawer-title.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-drawer-title/rpt-drawer-title.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-drawer-title/rpt-drawer-title.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-drawer-title/rpt-drawer-title.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-flat-task-list/rpt-flat-task-list.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-flat-task-list/rpt-flat-task-list.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-flat-task-list/rpt-flat-task-list.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-flat-task-list/rpt-flat-task-list.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-flat-task-list/rpt-flat-tasks-list.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-grouped-task-list/rpt-grouped-task-list.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-grouped-task-list/rpt-grouped-task-list.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-grouped-task-list/rpt-grouped-task-list.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-grouped-task-list/rpt-grouped-task-list.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-grouped-task-list/rpt-grouped-task-list.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-member-projects-list/rpt-member-projects-list.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-member-projects-list/rpt-member-projects-list.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-member-projects-list/rpt-member-projects-list.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-member-projects-list/rpt-member-projects-list.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-projects-list/rpt-projects-list.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-projects-list/rpt-projects-list.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-projects-list/rpt-projects-list.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-projects-list/rpt-projects-list.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-projects-list/rpt-projects-list.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/reporting-drawers.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-overview/rpt-member-drawer-overview.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-overview/rpt-member-drawer-overview.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-overview/rpt-member-drawer-overview.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-overview/rpt-member-drawer-overview.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-projects/rpt-member-drawer-projects.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-projects/rpt-member-drawer-projects.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-projects/rpt-member-drawer-projects.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-projects/rpt-member-drawer-projects.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-tasks/rpt-member-drawer-tasks.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-tasks/rpt-member-drawer-tasks.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-tasks/rpt-member-drawer-tasks.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-tasks/rpt-member-drawer-tasks.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-members/rpt-project-drawer-members.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-members/rpt-project-drawer-members.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-members/rpt-project-drawer-members.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-members/rpt-project-drawer-members.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-overview/rpt-project-drawer-overview.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-overview/rpt-project-drawer-overview.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-overview/rpt-project-drawer-overview.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-overview/rpt-project-drawer-overview.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-tasks/rpt-project-drawer-tasks.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-tasks/rpt-project-drawer-tasks.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-tasks/rpt-project-drawer-tasks.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-tasks/rpt-project-drawer-tasks.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/activity-logs/activity-logs.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/activity-logs/activity-logs.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/activity-logs/activity-logs.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/activity-logs/activity-logs.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/duration-filter/duration-filter.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/duration-filter/duration-filter.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/duration-filter/duration-filter.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/duration-filter/duration-filter.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/rpt-single-member-drawer-overview.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/rpt-single-member-drawer-overview.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/rpt-single-member-drawer-overview.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/rpt-single-member-drawer-overview.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/service/log-header.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/time-logs/single-member-time-logs.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/time-logs/single-member-time-logs.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/time-logs/single-member-time-logs.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/time-logs/single-member-time-logs.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-projects-drawer/rpt-single-member-projects-drawer.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-projects-drawer/rpt-single-member-projects-drawer.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-projects-drawer/rpt-single-member-projects-drawer.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-projects-drawer/rpt-single-member-projects-drawer.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-stat/rpt-single-member-stat.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-stat/rpt-single-member-stat.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-stat/rpt-single-member-stat.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-stat/rpt-single-member-stat.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-task-view-drawer/rpt-task-view-drawer.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-task-view-drawer/rpt-task-view-drawer.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-task-view-drawer/rpt-task-view-drawer.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-task-view-drawer/rpt-task-view-drawer.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-task-view-drawer/rpt-task-view-drawer.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-tasks-drawer/rpt-tasks-drawer.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-tasks-drawer/rpt-tasks-drawer.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-tasks-drawer/rpt-tasks-drawer.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-tasks-drawer/rpt-tasks-drawer.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-tasks-drawer/rpt-tasks-drawer.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-members/rpt-team-drawer-members.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-members/rpt-team-drawer-members.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-members/rpt-team-drawer-members.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-members/rpt-team-drawer-members.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-projects/rpt-team-drawer-projects.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-projects/rpt-team-drawer-projects.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-projects/rpt-team-drawer-projects.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-projects/rpt-team-drawer-projects.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-overview/rpt-team-overview.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-overview/rpt-team-overview.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-overview/rpt-team-overview.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-overview/rpt-team-overview.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/interfaces.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation/rpt-allocation.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation/rpt-allocation.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation/rpt-allocation.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation/rpt-allocation.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members/rpt-members.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members/rpt-members.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members/rpt-members.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members/rpt-members.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview-cards/rpt-overview-cards.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview-cards/rpt-overview-cards.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview-cards/rpt-overview-cards.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview-cards/rpt-overview-cards.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects-estimated-vs-actual/rpt-projects-estimated-vs-actual.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects-estimated-vs-actual/rpt-projects-estimated-vs-actual.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects-estimated-vs-actual/rpt-projects-estimated-vs-actual.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects-estimated-vs-actual/rpt-projects-estimated-vs-actual.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/rpt-time-estimation-vs-actual-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/rpt-time-estimation-vs-actual.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/time-estimation-vs-actual-projects/time-estimation-vs-actual-projects.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/time-estimation-vs-actual-projects/time-estimation-vs-actual-projects.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/time-estimation-vs-actual-projects/time-estimation-vs-actual-projects.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/time-estimation-vs-actual-projects/time-estimation-vs-actual-projects.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/rpt-time-members-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/rpt-time-members.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/time-members/time-members.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/time-members/time-members.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/time-members/time-members.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/time-members/time-members.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/rpt-time-projects-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/rpt-time-projects.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/time-projects/time-projects.component.html delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/time-projects/time-projects.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/time-projects/time-projects.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/time-projects/time-projects.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/pipes/with-count.pipe.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/pipes/with-count.pipe.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/reporting-api.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/reporting-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/reporting-service.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/reporting.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/reporting/reporting.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/project-schedule/project-schedule.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/project-schedule/project-schedule.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/project-schedule/project-schedule.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/project-schedule/project-schedule.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/add-member-allocation/add-member-allocation.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/add-member-allocation/add-member-allocation.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/add-member-allocation/add-member-allocation.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/add-member-allocation/add-member-allocation.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/context-menu/context-menu.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/context-menu/context-menu.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/context-menu/context-menu.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/context-menu/context-menu.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/end-date/end-date.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/end-date/end-date.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/end-date/end-date.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/end-date/end-date.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-indicator/member-indicator.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-indicator/member-indicator.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-indicator/member-indicator.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-indicator/member-indicator.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-task-add-container/member-task-add-container.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-task-add-container/member-task-add-container.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-task-add-container/member-task-add-container.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-task-add-container/member-task-add-container.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/phase/phase.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/phase/phase.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/phase/phase.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/phase/phase.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/priority/priority.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/priority/priority.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/priority/priority.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/priority/priority.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-indicator/project-indicator.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-indicator/project-indicator.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-indicator/project-indicator.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-indicator/project-indicator.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-member-tasks-drawer/project-member-tasks-drawer.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-member-tasks-drawer/project-member-tasks-drawer.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-member-tasks-drawer/project-member-tasks-drawer.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-member-tasks-drawer/project-member-tasks-drawer.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/start-date/start-date.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/start-date/start-date.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/start-date/start-date.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/start-date/start-date.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/status/status.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/status/status.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/status/status.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/status/status.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-input/task-add-input.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-input/task-add-input.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-input/task-add-input.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-input/task-add-input.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-row/task-add-row.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-row/task-add-row.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-row/task-add-row.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-row/task-add-row.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-header/task-list-header.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-header/task-list-header.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-header/task-list-header.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-header/task-list-header.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-row/task-list-row.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-row/task-list-row.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-row/task-list-row.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-row/task-list-row.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-name/task-name.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-name/task-name.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-name/task-name.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-name/task-name.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/tasks-context-menu/tasks-context-menu.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/tasks-context-menu/tasks-context-menu.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/tasks-context-menu/tasks-context-menu.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/tasks-context-menu/tasks-context-menu.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/projects-schedule.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/projects-schedule.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/projects-schedule.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/projects-schedule.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/service/project-schedule-service.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/service/schedule-member-tasks-hashmap-service.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/service/schedule-member-tasks-service.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-v2/service/scheduler-common.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-view/schedule-view.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-view/schedule-view.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-view/schedule-view.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule-view/schedule-view.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/schedule.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/team-schedule/team-schedule.component.html delete mode 100644 worklenz-frontend/src/app/administrator/schedule/team-schedule/team-schedule.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/schedule/team-schedule/team-schedule.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/schedule/team-schedule/team-schedule.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/categories/categories-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/categories/categories.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/categories/categories/categories.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/categories/categories/categories.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/categories/categories/categories.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/categories/categories/categories.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/change-password/change-password.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/change-password/change-password.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/change-password/change-password.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/change-password/change-password.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/clients/clients-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/clients/clients.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/clients/clients/clients.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/clients/clients/clients.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/clients/clients/clients.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/clients/clients/clients.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/job-titles/job-titles-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/job-titles/job-titles.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/job-titles/job-titles/job-titles.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/job-titles/job-titles/job-titles.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/job-titles/job-titles/job-titles.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/job-titles/job-titles/job-titles.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/labels/labels.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/labels/labels.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/labels/labels.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/labels/labels.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/language-and-region/language-and-region.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/language-and-region/language-and-region.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/language-and-region/language-and-region.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/language-and-region/language-and-region.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings/notification-settings.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings/notification-settings.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings/notification-settings.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings/notification-settings.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/profile/profile.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/profile/profile.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/profile/profile.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/profile/profile.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/add-task-input/add-task-input.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/add-task-input/add-task-input.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/add-task-input/add-task-input.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/add-task-input/add-task-input.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/context-menu/context-menu.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/context-menu/context-menu.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/context-menu/context-menu.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/context-menu/context-menu.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/group-filter/group-filter.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/group-filter/group-filter.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/group-filter/group-filter.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/group-filter/group-filter.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/phase-settings-drawer/phase-settings-drawer.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/phase-settings-drawer/phase-settings-drawer.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/phase-settings-drawer/phase-settings-drawer.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/phase-settings-drawer/phase-settings-drawer.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-description/task-description.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-description/task-description.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-description/task-description.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-description/task-description.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-end-date/task-end-date.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-end-date/task-end-date.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-end-date/task-end-date.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-end-date/task-end-date.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-estimation/task-estimation.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-estimation/task-estimation.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-estimation/task-estimation.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-estimation/task-estimation.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-labels/task-labels.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-labels/task-labels.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-labels/task-labels.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-labels/task-labels.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-phase/task-phase.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-phase/task-phase.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-phase/task-phase.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-phase/task-phase.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-priority/task-priority.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-priority/task-priority.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-priority/task-priority.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-priority/task-priority.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-start-date/task-start-date.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-start-date/task-start-date.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-start-date/task-start-date.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-start-date/task-start-date.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-status/task-status.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-status/task-status.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-status/task-status.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-status/task-status.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/status-settings-drawer/status-settings-drawer.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/status-settings-drawer/status-settings-drawer.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/status-settings-drawer/status-settings-drawer.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/status-settings-drawer/status-settings-drawer.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-group-settings/task-list-group-settings.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-group-settings/task-list-group-settings.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-group-settings/task-list-group-settings.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-group-settings/task-list-group-settings.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-header/task-list-header.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-header/task-list-header.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-header/task-list-header.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-header/task-list-header.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/template-name/template-name.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/template-name/template-name.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/template-name/template-name.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/template-name/template-name.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/interfaces.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/pipes/ellipsis-tooltip-title.pipe.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/pipes/end-name-check.pipe.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/pipes/sub-tasks-arrow-color.pipe.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/pipes/sub-tasks-arrow-icon.pipe.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/pipes/truncate-if-long.pipe.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/project-template-edit-view.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/project-template-edit-view.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/project-template-edit-view.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/project-template-edit-view.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/services/pt-task-list-hash-map.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/services/pt-task-list.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/task-list-row/task-list-row.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/task-list-row/task-list-row.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/task-list-row/task-list-row.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-template-edit-view/task-list-row/task-list-row.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-templates/project-templates.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-templates/project-templates.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-templates/project-templates.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/project-templates/project-templates.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/settings-routing.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/settings.module.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/settings.service.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/settings.service.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/settings/settings.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/settings/settings.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/settings/settings.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/settings/settings.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/task-templates/task-templates.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/task-templates/task-templates.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/task-templates/task-templates.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/task-templates/task-templates.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/team-members/team-members.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/team-members/team-members.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/team-members/team-members.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/team-members/team-members.component.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/teams/teams.component.html delete mode 100644 worklenz-frontend/src/app/administrator/settings/teams/teams.component.scss delete mode 100644 worklenz-frontend/src/app/administrator/settings/teams/teams.component.spec.ts delete mode 100644 worklenz-frontend/src/app/administrator/settings/teams/teams.component.ts delete mode 100644 worklenz-frontend/src/app/app-routing.module.ts delete mode 100644 worklenz-frontend/src/app/app.component.html delete mode 100644 worklenz-frontend/src/app/app.component.scss delete mode 100644 worklenz-frontend/src/app/app.component.spec.ts delete mode 100644 worklenz-frontend/src/app/app.component.ts delete mode 100644 worklenz-frontend/src/app/app.module.ts delete mode 100644 worklenz-frontend/src/app/auth/auth-routing.module.ts delete mode 100644 worklenz-frontend/src/app/auth/auth.module.ts delete mode 100644 worklenz-frontend/src/app/auth/layout/layout.component.html delete mode 100644 worklenz-frontend/src/app/auth/layout/layout.component.scss delete mode 100644 worklenz-frontend/src/app/auth/layout/layout.component.spec.ts delete mode 100644 worklenz-frontend/src/app/auth/layout/layout.component.ts delete mode 100644 worklenz-frontend/src/app/auth/login/login.component.html delete mode 100644 worklenz-frontend/src/app/auth/login/login.component.scss delete mode 100644 worklenz-frontend/src/app/auth/login/login.component.spec.ts delete mode 100644 worklenz-frontend/src/app/auth/login/login.component.ts delete mode 100644 worklenz-frontend/src/app/auth/reset-password/reset-password.component.html delete mode 100644 worklenz-frontend/src/app/auth/reset-password/reset-password.component.scss delete mode 100644 worklenz-frontend/src/app/auth/reset-password/reset-password.component.spec.ts delete mode 100644 worklenz-frontend/src/app/auth/reset-password/reset-password.component.ts delete mode 100644 worklenz-frontend/src/app/auth/signup/signup.component.html delete mode 100644 worklenz-frontend/src/app/auth/signup/signup.component.scss delete mode 100644 worklenz-frontend/src/app/auth/signup/signup.component.spec.ts delete mode 100644 worklenz-frontend/src/app/auth/signup/signup.component.ts delete mode 100644 worklenz-frontend/src/app/auth/team-name/team-name.component.html delete mode 100644 worklenz-frontend/src/app/auth/team-name/team-name.component.scss delete mode 100644 worklenz-frontend/src/app/auth/team-name/team-name.component.spec.ts delete mode 100644 worklenz-frontend/src/app/auth/team-name/team-name.component.ts delete mode 100644 worklenz-frontend/src/app/auth/verify-reset-email/verify-reset-email.component.html delete mode 100644 worklenz-frontend/src/app/auth/verify-reset-email/verify-reset-email.component.scss delete mode 100644 worklenz-frontend/src/app/auth/verify-reset-email/verify-reset-email.component.spec.ts delete mode 100644 worklenz-frontend/src/app/auth/verify-reset-email/verify-reset-email.component.ts delete mode 100644 worklenz-frontend/src/app/authenticate/authenticate.component.html delete mode 100644 worklenz-frontend/src/app/authenticate/authenticate.component.scss delete mode 100644 worklenz-frontend/src/app/authenticate/authenticate.component.spec.ts delete mode 100644 worklenz-frontend/src/app/authenticate/authenticate.component.ts delete mode 100644 worklenz-frontend/src/app/directives/infinite-scroll-trigger.directive.spec.ts delete mode 100644 worklenz-frontend/src/app/directives/infinite-scroll-trigger.directive.ts delete mode 100644 worklenz-frontend/src/app/directives/lazy-for.directive.spec.ts delete mode 100644 worklenz-frontend/src/app/directives/lazy-for.directive.ts delete mode 100644 worklenz-frontend/src/app/errors/not-authorized/not-authorized.component.html delete mode 100644 worklenz-frontend/src/app/errors/not-authorized/not-authorized.component.scss delete mode 100644 worklenz-frontend/src/app/errors/not-authorized/not-authorized.component.spec.ts delete mode 100644 worklenz-frontend/src/app/errors/not-authorized/not-authorized.component.ts delete mode 100644 worklenz-frontend/src/app/errors/not-found/not-found.component.html delete mode 100644 worklenz-frontend/src/app/errors/not-found/not-found.component.scss delete mode 100644 worklenz-frontend/src/app/errors/not-found/not-found.component.spec.ts delete mode 100644 worklenz-frontend/src/app/errors/not-found/not-found.component.ts delete mode 100644 worklenz-frontend/src/app/guards/auth.guard.spec.ts delete mode 100644 worklenz-frontend/src/app/guards/auth.guard.ts delete mode 100644 worklenz-frontend/src/app/guards/login-check.guard.spec.ts delete mode 100644 worklenz-frontend/src/app/guards/login-check.guard.ts delete mode 100644 worklenz-frontend/src/app/guards/non-google-account.guard.spec.ts delete mode 100644 worklenz-frontend/src/app/guards/non-google-account.guard.ts delete mode 100644 worklenz-frontend/src/app/guards/team-member.guard.spec.ts delete mode 100644 worklenz-frontend/src/app/guards/team-member.guard.ts delete mode 100644 worklenz-frontend/src/app/guards/team-name.guard.spec.ts delete mode 100644 worklenz-frontend/src/app/guards/team-name.guard.ts delete mode 100644 worklenz-frontend/src/app/guards/team-owner-or-admin-guard.service.ts delete mode 100644 worklenz-frontend/src/app/guards/team-owner-or-admin.guard.spec.ts delete mode 100644 worklenz-frontend/src/app/guards/team-owner.guard.spec.ts delete mode 100644 worklenz-frontend/src/app/guards/team-owner.guard.ts delete mode 100644 worklenz-frontend/src/app/guards/team-url.guard.spec.ts delete mode 100644 worklenz-frontend/src/app/guards/team-url.guard.ts delete mode 100644 worklenz-frontend/src/app/interceptors/http.interceptor.spec.ts delete mode 100644 worklenz-frontend/src/app/interceptors/http.interceptor.ts delete mode 100644 worklenz-frontend/src/app/interfaces/account-center.ts delete mode 100644 worklenz-frontend/src/app/interfaces/allocation-view-model.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/activity-log.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/activity-logs-get-response.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/authorize-response.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/bulk-assign-request.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/bulk-delete-tasks-request.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-archive-request.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-delete-request.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-delete-response.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-labels-request.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-phase-change-request.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-priority-change-request.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-status-change-request.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/client-view-model.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/clients-view-model.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/gantt.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/inline-member.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/job-titles-view-model.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/local-session.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/my-dashboard-all-tasks-view-model.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/organization-team-get-request.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/organization-users-get-request.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/project-comment-create-request.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/project-create-request.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/project-insights.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/project-members-view-model.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/project-tasks-view-model.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/project-template.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/project-view-model.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/projects-get-response.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/projects-view-model.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/reporting.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/reset-password-request.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/task-attachment-view-model.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/task-attachment.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/task-comment-view-model.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/task-comments-create-request.ts.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/task-create-request.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/task-get-response.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/task-list-config.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/task-priorities-get-response.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/task-status-get-response.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/task-template-get-response.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/task-templates-get-response.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/team-activate-response.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/team-get-response.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/team-invitation-view-model.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/team-member-create-request.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/team-members-get-response.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/team-members-view-model.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/team-view-model.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/user-login-request.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/user-login-response.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/user-sign-up-response.ts delete mode 100644 worklenz-frontend/src/app/interfaces/api-models/verify-reset-email.ts delete mode 100644 worklenz-frontend/src/app/interfaces/client.ts delete mode 100644 worklenz-frontend/src/app/interfaces/gantt-chart.ts delete mode 100644 worklenz-frontend/src/app/interfaces/invitation-response.ts delete mode 100644 worklenz-frontend/src/app/interfaces/job-title.ts delete mode 100644 worklenz-frontend/src/app/interfaces/my-tasks.ts delete mode 100644 worklenz-frontend/src/app/interfaces/nav-item-type.ts delete mode 100644 worklenz-frontend/src/app/interfaces/nav-item.ts delete mode 100644 worklenz-frontend/src/app/interfaces/notification.ts delete mode 100644 worklenz-frontend/src/app/interfaces/pagination-component.ts delete mode 100644 worklenz-frontend/src/app/interfaces/password-strength-check.ts delete mode 100644 worklenz-frontend/src/app/interfaces/password-validity-result.ts delete mode 100644 worklenz-frontend/src/app/interfaces/personal-overview.ts delete mode 100644 worklenz-frontend/src/app/interfaces/project-comments.ts delete mode 100644 worklenz-frontend/src/app/interfaces/project-folder.ts delete mode 100644 worklenz-frontend/src/app/interfaces/project-health.ts delete mode 100644 worklenz-frontend/src/app/interfaces/project-manager.ts delete mode 100644 worklenz-frontend/src/app/interfaces/project-template.ts delete mode 100644 worklenz-frontend/src/app/interfaces/project-wise-resources-view-model.ts delete mode 100644 worklenz-frontend/src/app/interfaces/project.ts delete mode 100644 worklenz-frontend/src/app/interfaces/reporting-allocation-settings.ts delete mode 100644 worklenz-frontend/src/app/interfaces/reporting.ts delete mode 100644 worklenz-frontend/src/app/interfaces/roadmap.ts delete mode 100644 worklenz-frontend/src/app/interfaces/role.ts delete mode 100644 worklenz-frontend/src/app/interfaces/schedular.ts delete mode 100644 worklenz-frontend/src/app/interfaces/selectable-category.ts delete mode 100644 worklenz-frontend/src/app/interfaces/selectable-project.ts delete mode 100644 worklenz-frontend/src/app/interfaces/selectable-team.ts delete mode 100644 worklenz-frontend/src/app/interfaces/settings-navigation-item.ts delete mode 100644 worklenz-frontend/src/app/interfaces/sub-task.ts delete mode 100644 worklenz-frontend/src/app/interfaces/task-assignee-update-response.ts delete mode 100644 worklenz-frontend/src/app/interfaces/task-assignee.ts delete mode 100644 worklenz-frontend/src/app/interfaces/task-comment.ts delete mode 100644 worklenz-frontend/src/app/interfaces/task-form-view-model.ts delete mode 100644 worklenz-frontend/src/app/interfaces/task-list-column.ts delete mode 100644 worklenz-frontend/src/app/interfaces/task-list-estimation-change-response.ts delete mode 100644 worklenz-frontend/src/app/interfaces/task-list-status-change-response.ts delete mode 100644 worklenz-frontend/src/app/interfaces/task-priority.ts delete mode 100644 worklenz-frontend/src/app/interfaces/task-status.ts delete mode 100644 worklenz-frontend/src/app/interfaces/task.ts delete mode 100644 worklenz-frontend/src/app/interfaces/team.ts delete mode 100644 worklenz-frontend/src/app/interfaces/timer-start-event-response.ts delete mode 100644 worklenz-frontend/src/app/interfaces/timer-stop-event-response.ts delete mode 100644 worklenz-frontend/src/app/interfaces/todo-list-item.ts delete mode 100644 worklenz-frontend/src/app/interfaces/user.ts delete mode 100644 worklenz-frontend/src/app/interfaces/worklenz-notification.ts delete mode 100644 worklenz-frontend/src/app/interfaces/workload.ts delete mode 100644 worklenz-frontend/src/app/pipes/bind-na.pipe.spec.ts delete mode 100644 worklenz-frontend/src/app/pipes/bind-na.pipe.ts delete mode 100644 worklenz-frontend/src/app/pipes/date-formatter.pipe.ts delete mode 100644 worklenz-frontend/src/app/pipes/ellipsis.pipe.spec.ts delete mode 100644 worklenz-frontend/src/app/pipes/ellipsis.pipe.ts delete mode 100644 worklenz-frontend/src/app/pipes/first-char-upper.pipe.spec.ts delete mode 100644 worklenz-frontend/src/app/pipes/first-char-upper.pipe.ts delete mode 100644 worklenz-frontend/src/app/pipes/from-now.pipe.spec.ts delete mode 100644 worklenz-frontend/src/app/pipes/from-now.pipe.ts delete mode 100644 worklenz-frontend/src/app/pipes/min-date-validator.pipe.ts delete mode 100644 worklenz-frontend/src/app/pipes/safe-string.pipe.spec.ts delete mode 100644 worklenz-frontend/src/app/pipes/safe-string.pipe.ts delete mode 100644 worklenz-frontend/src/app/pipes/search-by-name.pipe.spec.ts delete mode 100644 worklenz-frontend/src/app/pipes/search-by-name.pipe.ts delete mode 100644 worklenz-frontend/src/app/pipes/task-comment-mention.pipe.spec.ts delete mode 100644 worklenz-frontend/src/app/pipes/task-comment-mention.pipe.ts delete mode 100644 worklenz-frontend/src/app/pipes/to-now.pipe.spec.ts delete mode 100644 worklenz-frontend/src/app/pipes/to-now.pipe.ts delete mode 100644 worklenz-frontend/src/app/pipes/with-alpha.pipe.spec.ts delete mode 100644 worklenz-frontend/src/app/pipes/with-alpha.pipe.ts delete mode 100644 worklenz-frontend/src/app/pipes/wl-safe-array.pipe.spec.ts delete mode 100644 worklenz-frontend/src/app/pipes/wl-safe-array.pipe.ts create mode 100644 worklenz-frontend/src/app/routes/account-setup-routes.tsx create mode 100644 worklenz-frontend/src/app/routes/admin-center-routes.tsx create mode 100644 worklenz-frontend/src/app/routes/auth-routes.tsx create mode 100644 worklenz-frontend/src/app/routes/index.tsx create mode 100644 worklenz-frontend/src/app/routes/main-routes.tsx create mode 100644 worklenz-frontend/src/app/routes/not-found-route.tsx create mode 100644 worklenz-frontend/src/app/routes/protected-routes.tsx create mode 100644 worklenz-frontend/src/app/routes/reporting-routes.tsx create mode 100644 worklenz-frontend/src/app/routes/root-routes.tsx create mode 100644 worklenz-frontend/src/app/routes/settings-routes.tsx delete mode 100644 worklenz-frontend/src/app/services/api/access-controls-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/account-center-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/activity-logs.service.spec.ts delete mode 100644 worklenz-frontend/src/app/services/api/activity-logs.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/api-service-base.ts delete mode 100644 worklenz-frontend/src/app/services/api/attachments-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/auth-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/clients-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/gantt-api.service.spec.ts delete mode 100644 worklenz-frontend/src/app/services/api/gantt-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/home-page-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/job-titles-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/logs-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/notifications-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/personal-overview.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/profile-settings-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/project-categories-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/project-categories.service.spec.ts delete mode 100644 worklenz-frontend/src/app/services/api/project-comments-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/project-folders-api.service.spec.ts delete mode 100644 worklenz-frontend/src/app/services/api/project-folders-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/project-healths-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/project-insights.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/project-managers-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/project-members-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/project-roadmap-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/project-statuses-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/project-template-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/project-workload-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/projects-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/pt-labels-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/pt-priorities-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/pt-statuses-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/pt-task-phases-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/pt-tasks-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/reporting-api-v0.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/reporting-export-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/resource-allocation.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/schedule-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/shared-projects-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/sub-tasks-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/task-comments-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/task-labels-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/task-phases-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/task-priorities.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/task-statuses-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/task-templates.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/tasks-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/tasks-log-time.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/team-members-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/teams-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/test-gannt-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/timezones-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/todo-list-api.service.ts delete mode 100644 worklenz-frontend/src/app/services/api/users.service.ts delete mode 100644 worklenz-frontend/src/app/services/app.service.spec.ts delete mode 100644 worklenz-frontend/src/app/services/app.service.ts delete mode 100644 worklenz-frontend/src/app/services/auth.service.spec.ts delete mode 100644 worklenz-frontend/src/app/services/auth.service.ts delete mode 100644 worklenz-frontend/src/app/services/menu-service.spec.ts delete mode 100644 worklenz-frontend/src/app/services/menu.service.ts delete mode 100644 worklenz-frontend/src/app/services/notification-settings.service.spec.ts delete mode 100644 worklenz-frontend/src/app/services/notification-settings.service.ts delete mode 100644 worklenz-frontend/src/app/services/project-form-service.service.ts delete mode 100644 worklenz-frontend/src/app/services/project-phases.service.ts delete mode 100644 worklenz-frontend/src/app/services/project-template.service.ts delete mode 100644 worklenz-frontend/src/app/services/project-updates.service.ts delete mode 100644 worklenz-frontend/src/app/services/socket.service.ts delete mode 100644 worklenz-frontend/src/app/services/utils.service.spec.ts delete mode 100644 worklenz-frontend/src/app/services/utils.service.ts delete mode 100644 worklenz-frontend/src/app/services/worklenz.analytics.ts delete mode 100644 worklenz-frontend/src/app/session-expired/session-expired.component.html delete mode 100644 worklenz-frontend/src/app/session-expired/session-expired.component.scss delete mode 100644 worklenz-frontend/src/app/session-expired/session-expired.component.spec.ts delete mode 100644 worklenz-frontend/src/app/session-expired/session-expired.component.ts delete mode 100644 worklenz-frontend/src/app/shared/constants.ts delete mode 100644 worklenz-frontend/src/app/shared/events.ts delete mode 100644 worklenz-frontend/src/app/shared/session-helper.ts delete mode 100644 worklenz-frontend/src/app/shared/utils.ts delete mode 100644 worklenz-frontend/src/app/shared/worklenz-analytics-events.ts create mode 100644 worklenz-frontend/src/app/store.ts delete mode 100644 worklenz-frontend/src/assets/.gitkeep delete mode 100644 worklenz-frontend/src/assets/css/prebuilt-editor.css create mode 100644 worklenz-frontend/src/assets/icons/file-icon.png delete mode 100644 worklenz-frontend/src/assets/icons/icon-128x128.png delete mode 100644 worklenz-frontend/src/assets/icons/icon-144x144.png delete mode 100644 worklenz-frontend/src/assets/icons/icon-152x152.png delete mode 100644 worklenz-frontend/src/assets/icons/icon-192x192.png delete mode 100644 worklenz-frontend/src/assets/icons/icon-384x384.png delete mode 100644 worklenz-frontend/src/assets/icons/icon-512x512.png delete mode 100644 worklenz-frontend/src/assets/icons/icon-96x96.png rename worklenz-frontend/src/assets/{images => icons/insightsIcons}/block-user.png (100%) rename worklenz-frontend/src/assets/{images => icons/insightsIcons}/clipboard.png (100%) rename worklenz-frontend/src/assets/{images => icons/insightsIcons}/clock-green.png (100%) rename worklenz-frontend/src/assets/{images => icons/insightsIcons}/group.png (100%) rename worklenz-frontend/src/assets/{images => icons/insightsIcons}/insights-check.png (100%) rename worklenz-frontend/src/assets/{images => icons/insightsIcons}/warning.png (100%) delete mode 100644 worklenz-frontend/src/assets/images/404.svg delete mode 100644 worklenz-frontend/src/assets/images/chevron-down-solid.svg delete mode 100644 worklenz-frontend/src/assets/images/clock-red.png delete mode 100644 worklenz-frontend/src/assets/images/clock-yellow.png delete mode 100644 worklenz-frontend/src/assets/images/confetti (1).png delete mode 100644 worklenz-frontend/src/assets/images/confetti.png delete mode 100644 worklenz-frontend/src/assets/images/empty-box.png delete mode 100644 worklenz-frontend/src/assets/images/empty-box.webp delete mode 100644 worklenz-frontend/src/assets/images/folder.svg delete mode 100644 worklenz-frontend/src/assets/images/google_sign_in.png delete mode 100644 worklenz-frontend/src/assets/images/graph-report.png delete mode 100644 worklenz-frontend/src/assets/images/group_1.png delete mode 100644 worklenz-frontend/src/assets/images/insights-calendar.png create mode 100644 worklenz-frontend/src/assets/images/logo-dark-mode.png delete mode 100644 worklenz-frontend/src/assets/images/logo-lg.png delete mode 100644 worklenz-frontend/src/assets/images/logo-sm.png delete mode 100644 worklenz-frontend/src/assets/images/magnifying-glass-solid.svg delete mode 100644 worklenz-frontend/src/assets/images/noimage.png create mode 100644 worklenz-frontend/src/assets/images/not-found-img.png delete mode 100644 worklenz-frontend/src/assets/images/open-icon.svg delete mode 100644 worklenz-frontend/src/assets/images/progress.png delete mode 100644 worklenz-frontend/src/assets/images/project-management.png delete mode 100644 worklenz-frontend/src/assets/images/team-members.svg delete mode 100644 worklenz-frontend/src/assets/images/to-do-list.png delete mode 100644 worklenz-frontend/src/assets/images/user.png create mode 100644 worklenz-frontend/src/components/AuthPageHeader.tsx create mode 100644 worklenz-frontend/src/components/CustomAvatar.tsx create mode 100644 worklenz-frontend/src/components/CustomSearchbar.tsx create mode 100644 worklenz-frontend/src/components/CustomTableTitle.tsx create mode 100644 worklenz-frontend/src/components/EmptyListPlaceholder.tsx create mode 100644 worklenz-frontend/src/components/ErrorBoundary.tsx create mode 100644 worklenz-frontend/src/components/PinRouteToNavbarButton.tsx create mode 100644 worklenz-frontend/src/components/PreferenceSelector.tsx create mode 100644 worklenz-frontend/src/components/TawkTo.tsx create mode 100644 worklenz-frontend/src/components/account-setup/admin-center-common.css create mode 100644 worklenz-frontend/src/components/account-setup/members-step.tsx create mode 100644 worklenz-frontend/src/components/account-setup/organization-step.tsx create mode 100644 worklenz-frontend/src/components/account-setup/project-step.tsx create mode 100644 worklenz-frontend/src/components/account-setup/tasks-step.tsx create mode 100644 worklenz-frontend/src/components/add-members-dropdown-v2/add-members-dropdown.css create mode 100644 worklenz-frontend/src/components/add-members-dropdown-v2/add-members-dropdown.tsx create mode 100644 worklenz-frontend/src/components/add-members-dropdown/add-members-dropdown.css create mode 100644 worklenz-frontend/src/components/add-members-dropdown/add-members-dropdown.tsx create mode 100644 worklenz-frontend/src/components/admin-center/billing/account-storage/account-storage.tsx create mode 100644 worklenz-frontend/src/components/admin-center/billing/billing-tables/charges-table.tsx create mode 100644 worklenz-frontend/src/components/admin-center/billing/billing-tables/invoices-table.tsx create mode 100644 worklenz-frontend/src/components/admin-center/billing/current-bill.css create mode 100644 worklenz-frontend/src/components/admin-center/billing/current-bill.tsx create mode 100644 worklenz-frontend/src/components/admin-center/billing/current-plan-details/current-plan-details.tsx create mode 100644 worklenz-frontend/src/components/admin-center/billing/drawers/redeem-code-drawer/redeem-code-drawer.tsx create mode 100644 worklenz-frontend/src/components/admin-center/billing/drawers/upgrade-plans-lkr/upgrade-plans-lkr.css create mode 100644 worklenz-frontend/src/components/admin-center/billing/drawers/upgrade-plans-lkr/upgrade-plans-lkr.tsx create mode 100644 worklenz-frontend/src/components/admin-center/billing/drawers/upgrade-plans/upgrade-plans.tsx create mode 100644 worklenz-frontend/src/components/admin-center/configuration/configuration.tsx create mode 100644 worklenz-frontend/src/components/admin-center/overview/organization-admins-table/organization-admins-table.tsx create mode 100644 worklenz-frontend/src/components/admin-center/overview/organization-name/organization-name.tsx create mode 100644 worklenz-frontend/src/components/admin-center/overview/organization-owner/organization-owner.tsx create mode 100644 worklenz-frontend/src/components/admin-center/teams/add-team-drawer/add-team-drawer.tsx create mode 100644 worklenz-frontend/src/components/admin-center/teams/settings-drawer/settings-drawer.css create mode 100644 worklenz-frontend/src/components/admin-center/teams/settings-drawer/settings-drawer.tsx create mode 100644 worklenz-frontend/src/components/admin-center/teams/teams-table/teams-table.tsx create mode 100644 worklenz-frontend/src/components/avatars/avatars.tsx create mode 100644 worklenz-frontend/src/components/board/board-assignee-selector/board-assignee-selector.tsx create mode 100644 worklenz-frontend/src/components/board/changeCategoryDropdown/ChangeCategoryDropdown.css create mode 100644 worklenz-frontend/src/components/board/changeCategoryDropdown/ChangeCategoryDropdown.tsx create mode 100644 worklenz-frontend/src/components/board/common-members-section/common-members-section.tsx create mode 100644 worklenz-frontend/src/components/board/common-phase-section/common-phase-section.tsx create mode 100644 worklenz-frontend/src/components/board/common-priority-section/common-priority-section.tsx create mode 100644 worklenz-frontend/src/components/board/commonStatusSection/CommonStatusSection.css create mode 100644 worklenz-frontend/src/components/board/commonStatusSection/CommonStatusSection.tsx create mode 100644 worklenz-frontend/src/components/board/custom-avatar-group.tsx create mode 100644 worklenz-frontend/src/components/board/custom-due-date-picker.tsx create mode 100644 worklenz-frontend/src/components/board/kanban-group/kanban-group.css create mode 100644 worklenz-frontend/src/components/board/kanban-group/kanban-group.tsx create mode 100644 worklenz-frontend/src/components/board/subTaskCard/SubTaskCard.tsx create mode 100644 worklenz-frontend/src/components/board/taskCard/TaskCard.css create mode 100644 worklenz-frontend/src/components/board/taskCard/TaskCard.tsx create mode 100644 worklenz-frontend/src/components/board/taskCreateCard/TaskCreateCard.css create mode 100644 worklenz-frontend/src/components/board/taskCreateCard/TaskCreateCard.tsx create mode 100644 worklenz-frontend/src/components/board/taskCreateCard/priority-task-create-card.tsx create mode 100644 worklenz-frontend/src/components/calendars/homeCalendar/HomeCalendar.tsx create mode 100644 worklenz-frontend/src/components/calendars/homeCalendar/homeCalendar.css create mode 100644 worklenz-frontend/src/components/collapsible/collapsible.tsx create mode 100644 worklenz-frontend/src/components/common/invite-team-members/invite-team-members.tsx create mode 100644 worklenz-frontend/src/components/common/project-status-icon/project-status-icon.tsx create mode 100644 worklenz-frontend/src/components/common/single-avatar/single-avatar.tsx create mode 100644 worklenz-frontend/src/components/common/template-drawer/template-drawer.css create mode 100644 worklenz-frontend/src/components/common/template-drawer/template-drawer.tsx create mode 100644 worklenz-frontend/src/components/home-tasks/statusDropdown/home-tasks-status-dropdown.css create mode 100644 worklenz-frontend/src/components/home-tasks/statusDropdown/home-tasks-status-dropdown.tsx create mode 100644 worklenz-frontend/src/components/home-tasks/taskDatePicker/home-tasks-date-picker.tsx create mode 100644 worklenz-frontend/src/components/navbar/notifications/notifications-drawer/notification/invitation-item.tsx create mode 100644 worklenz-frontend/src/components/navbar/notifications/notifications-drawer/notification/notfication-drawer.tsx create mode 100644 worklenz-frontend/src/components/navbar/notifications/notifications-drawer/notification/notification-button.tsx create mode 100644 worklenz-frontend/src/components/navbar/notifications/notifications-drawer/notification/notification-item.css create mode 100644 worklenz-frontend/src/components/navbar/notifications/notifications-drawer/notification/notification-item.tsx create mode 100644 worklenz-frontend/src/components/navbar/notifications/notifications-drawer/notification/notification-template.tsx create mode 100644 worklenz-frontend/src/components/navbar/notifications/notifications-drawer/notification/push-notification-template.css create mode 100644 worklenz-frontend/src/components/navbar/notifications/notifications-drawer/notification/push-notification-template.tsx create mode 100644 worklenz-frontend/src/components/project-list/TableColumns.css create mode 100644 worklenz-frontend/src/components/project-list/TableColumns.tsx create mode 100644 worklenz-frontend/src/components/project-list/project-list-table/project-list-actions/project-list-actions.tsx create mode 100644 worklenz-frontend/src/components/project-list/project-list-table/project-list-category/project-list-category.tsx create mode 100644 worklenz-frontend/src/components/project-list/project-list-table/project-list-favorite/project-rate-cell.tsx create mode 100644 worklenz-frontend/src/components/project-list/project-list-table/project-list-progress/progress-list-progress.tsx create mode 100644 worklenz-frontend/src/components/project-list/project-list-table/project-list-updated-at/project-list-updated.tsx create mode 100644 worklenz-frontend/src/components/project-list/project-list-table/project-name/project-name-cell.tsx create mode 100644 worklenz-frontend/src/components/project-task-filters/create-status-button/create-status-button.tsx create mode 100644 worklenz-frontend/src/components/project-task-filters/create-status-drawer/create-status-drawer.css create mode 100644 worklenz-frontend/src/components/project-task-filters/create-status-drawer/create-status-drawer.tsx create mode 100644 worklenz-frontend/src/components/project-task-filters/delete-status-drawer/delete-status-drawer.tsx create mode 100644 worklenz-frontend/src/components/project-task-filters/filter-dropdowns/group-by-filter-dropdown.tsx create mode 100644 worklenz-frontend/src/components/project-task-filters/filter-dropdowns/labels-filter-dropdown.tsx create mode 100644 worklenz-frontend/src/components/project-task-filters/filter-dropdowns/members-filter-dropdown.tsx create mode 100644 worklenz-frontend/src/components/project-task-filters/filter-dropdowns/priority-filter-dropdown.tsx create mode 100644 worklenz-frontend/src/components/project-task-filters/filter-dropdowns/search-dropdown.tsx create mode 100644 worklenz-frontend/src/components/project-task-filters/filter-dropdowns/show-fields-filter-dropdown.tsx create mode 100644 worklenz-frontend/src/components/project-task-filters/filter-dropdowns/sort-filter-dropdown.tsx create mode 100644 worklenz-frontend/src/components/projects/project-create-button/project-create-button.tsx create mode 100644 worklenz-frontend/src/components/projects/project-drawer/project-basic-info/project-basic-info.tsx create mode 100644 worklenz-frontend/src/components/projects/project-drawer/project-category-section/project-category-section.tsx create mode 100644 worklenz-frontend/src/components/projects/project-drawer/project-client-section/project-client-section.tsx create mode 100644 worklenz-frontend/src/components/projects/project-drawer/project-drawer.tsx create mode 100644 worklenz-frontend/src/components/projects/project-drawer/project-health-section/project-health-section.tsx create mode 100644 worklenz-frontend/src/components/projects/project-drawer/project-status-section/project-status-section.tsx create mode 100644 worklenz-frontend/src/components/projects/project-manager-dropdown/project-manager-dropdown.css create mode 100644 worklenz-frontend/src/components/projects/project-manager-dropdown/project-manager-dropdown.tsx create mode 100644 worklenz-frontend/src/components/projects/project-member-invite-drawer/project-member-invite-drawer.tsx create mode 100644 worklenz-frontend/src/components/projects/project-stats-card.tsx create mode 100644 worklenz-frontend/src/components/reporting/drawers/overview-team-info/members-tab/reporting-overview-members-tab.tsx create mode 100644 worklenz-frontend/src/components/reporting/drawers/overview-team-info/members-tab/reporting-overview-members-table.tsx create mode 100644 worklenz-frontend/src/components/reporting/drawers/overview-team-info/overview-tab/reports-overview-category-graph.tsx create mode 100644 worklenz-frontend/src/components/reporting/drawers/overview-team-info/overview-tab/reports-overview-project-health-graph.tsx create mode 100644 worklenz-frontend/src/components/reporting/drawers/overview-team-info/overview-tab/reports-overview-status-graph.tsx create mode 100644 worklenz-frontend/src/components/reporting/drawers/overview-team-info/overview-tab/reports-overview-tab.tsx create mode 100644 worklenz-frontend/src/components/reporting/drawers/overview-team-info/overview-team-info-drawer-tabs.tsx create mode 100644 worklenz-frontend/src/components/reporting/drawers/overview-team-info/overview-team-info-drawer.tsx create mode 100644 worklenz-frontend/src/components/reporting/drawers/overview-team-info/projects-tab/projects-reports-table.css create mode 100644 worklenz-frontend/src/components/reporting/drawers/overview-team-info/projects-tab/reporting-overview-projects-tab.tsx create mode 100644 worklenz-frontend/src/components/reporting/drawers/overview-team-info/projects-tab/reporting-overview-projects-table.tsx create mode 100644 worklenz-frontend/src/components/reporting/time-wise-filter.tsx create mode 100644 worklenz-frontend/src/components/save-project-as-template/save-project-as-template.tsx create mode 100644 worklenz-frontend/src/components/schedule-old/tabs/withStartAndEndDates/WithStartAndEndDates.tsx create mode 100644 worklenz-frontend/src/components/schedule-old/team/Team.css create mode 100644 worklenz-frontend/src/components/schedule-old/team/Team.tsx create mode 100644 worklenz-frontend/src/components/schedule/grant-chart/day-allocation-cell.tsx create mode 100644 worklenz-frontend/src/components/schedule/grant-chart/grantt-chart.tsx create mode 100644 worklenz-frontend/src/components/schedule/grant-chart/grantt-members-table.tsx create mode 100644 worklenz-frontend/src/components/schedule/grant-chart/project-timeline-bar.tsx create mode 100644 worklenz-frontend/src/components/schedule/grant-chart/tabs/withStartAndEndDates/WithStartAndEndDates.tsx create mode 100644 worklenz-frontend/src/components/schedule/grant-chart/timeline.tsx create mode 100644 worklenz-frontend/src/components/settings/edit-team-name-modal.tsx create mode 100644 worklenz-frontend/src/components/settings/update-member-drawer.tsx create mode 100644 worklenz-frontend/src/components/suspense-fallback/suspense-fallback.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/activity-log/task-drawer-activity-log.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/attachments/attachments-grid.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/attachments/attachments-preview.css create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/attachments/attachments-preview.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/attachments/attachments-upload.css create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/attachments/attachments-upload.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/comments/task-comments.css create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/comments/task-comments.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/comments/task-view-comment-edit.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/dependencies-table.css create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/dependencies-table.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/description-editor.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-assignee-selector/task-drawer-assignee-selector.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-billable/task-drawer-billable.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-due-date/task-drawer-due-date.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-estimation/task-drawer-estimation.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-key/task-drawer-key.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-labels/task-drawer-labels.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-phase-selector/task-drawer-phase-selector.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-priority-selector/priority-dropdown.css create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-priority-selector/task-drawer-priority-selector.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/info-tab-footer.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/notify-member-selector.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/subtask-table.css create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/subtask-table.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/task-details-form.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/info-tab/task-drawer-info-tab.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/time-log/TaskDrawerTimeLog.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/time-log/task-drawer-time-log.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/time-log/time-log-form.tsx rename worklenz-frontend/src/{app/administrator/components/avatars/avatars.component.scss => components/task-drawer/shared/time-log/time-log-item.css} (100%) create mode 100644 worklenz-frontend/src/components/task-drawer/shared/time-log/time-log-item.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/shared/time-log/time-log-list.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/task-drawer-header/task-drawer-header.css create mode 100644 worklenz-frontend/src/components/task-drawer/task-drawer-header/task-drawer-header.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/task-drawer-status-dropdown/task-drawer-status-dropdown.css create mode 100644 worklenz-frontend/src/components/task-drawer/task-drawer-status-dropdown/task-drawer-status-dropdown.tsx create mode 100644 worklenz-frontend/src/components/task-drawer/task-drawer.css create mode 100644 worklenz-frontend/src/components/task-drawer/task-drawer.tsx create mode 100644 worklenz-frontend/src/components/task-list-common/assigneeSelector/AssigneeSelector.tsx create mode 100644 worklenz-frontend/src/components/task-list-common/convert-to-subtask-drawer/convert-to-subtask-drawer.tsx create mode 100644 worklenz-frontend/src/components/task-list-common/labelsSelector/color-changed-label.tsx create mode 100644 worklenz-frontend/src/components/task-list-common/labelsSelector/custom-color-label.tsx create mode 100644 worklenz-frontend/src/components/task-list-common/labelsSelector/custom-number-label.tsx create mode 100644 worklenz-frontend/src/components/task-list-common/labelsSelector/labels-selector.tsx create mode 100644 worklenz-frontend/src/components/task-list-common/phaseDropdown/PhaseDropdown.tsx create mode 100644 worklenz-frontend/src/components/task-list-common/phaseDropdown/phaseDropdown.css create mode 100644 worklenz-frontend/src/components/task-list-common/priorityDropdown/priority-dropdown.css create mode 100644 worklenz-frontend/src/components/task-list-common/priorityDropdown/priority-dropdown.tsx create mode 100644 worklenz-frontend/src/components/task-list-common/status-dropdown/status-dropdown.css create mode 100644 worklenz-frontend/src/components/task-list-common/status-dropdown/status-dropdown.tsx create mode 100644 worklenz-frontend/src/components/task-list-common/task-row/task-list-due-time-cell/task-row-due-time.tsx create mode 100644 worklenz-frontend/src/components/task-list-common/task-row/task-row-description/task-row-description.tsx create mode 100644 worklenz-frontend/src/components/task-list-common/task-row/task-row-name/task-row-name.tsx create mode 100644 worklenz-frontend/src/components/task-list-common/task-row/task-row-progress/task-row-progress.css create mode 100644 worklenz-frontend/src/components/task-list-common/task-row/task-row-progress/task-row-progress.tsx create mode 100644 worklenz-frontend/src/components/task-list-common/task-row/task-row-time-tracking/task-row-time-tracking.tsx create mode 100644 worklenz-frontend/src/components/task-templates/import-task-template.tsx create mode 100644 worklenz-frontend/src/components/task-templates/task-template-drawer.tsx create mode 100644 worklenz-frontend/src/components/taskListCommon/assignee-selector/assignee-selector.tsx create mode 100644 worklenz-frontend/src/components/taskListCommon/labelsSelector/CustomColordLabel.tsx create mode 100644 worklenz-frontend/src/components/taskListCommon/labelsSelector/CustomNumberLabel.tsx create mode 100644 worklenz-frontend/src/components/taskListCommon/labelsSelector/LabelsSelector.tsx create mode 100644 worklenz-frontend/src/components/taskListCommon/labelsSelector/color-changed-label.tsx create mode 100644 worklenz-frontend/src/components/taskListCommon/phase-dropdown/phase-dropdown.css create mode 100644 worklenz-frontend/src/components/taskListCommon/phase-dropdown/phase-dropdown.tsx create mode 100644 worklenz-frontend/src/components/taskListCommon/priorityDropdown/PriorityDropdown.tsx create mode 100644 worklenz-frontend/src/components/taskListCommon/priorityDropdown/priorityDropdown.css create mode 100644 worklenz-frontend/src/components/taskListCommon/statusDropdown/StatusDropdown.tsx create mode 100644 worklenz-frontend/src/components/taskListCommon/statusDropdown/statusDropdown.css create mode 100644 worklenz-frontend/src/components/taskListCommon/task-list-bulk-actions-bar/components/AssigneesDropdown.tsx create mode 100644 worklenz-frontend/src/components/taskListCommon/task-list-bulk-actions-bar/components/LabelsDropdown.tsx create mode 100644 worklenz-frontend/src/components/taskListCommon/task-list-bulk-actions-bar/task-list-bulk-actions-bar.css create mode 100644 worklenz-frontend/src/components/taskListCommon/task-list-bulk-actions-bar/task-list-bulk-actions-bar.tsx create mode 100644 worklenz-frontend/src/components/taskListCommon/task-timer/task-timer.tsx delete mode 100644 worklenz-frontend/src/environments/environment.prod.ts delete mode 100644 worklenz-frontend/src/environments/environment.ts create mode 100644 worklenz-frontend/src/features/account-setup/account-setup.slice.ts create mode 100644 worklenz-frontend/src/features/actionSetup/buttonSlice.ts create mode 100644 worklenz-frontend/src/features/admin-center/admin-center.slice.ts create mode 100644 worklenz-frontend/src/features/admin-center/billing/billing.slice.ts create mode 100644 worklenz-frontend/src/features/auth/authSlice.ts create mode 100644 worklenz-frontend/src/features/board/board-slice.ts create mode 100644 worklenz-frontend/src/features/board/create-card.slice.ts create mode 100644 worklenz-frontend/src/features/date/dateSlice.ts create mode 100644 worklenz-frontend/src/features/group-by-filter-dropdown/group-by-filter-dropdown-slice.ts create mode 100644 worklenz-frontend/src/features/home-page/home-page.slice.ts create mode 100644 worklenz-frontend/src/features/i18n/language-selector.tsx create mode 100644 worklenz-frontend/src/features/i18n/localesSlice.ts create mode 100644 worklenz-frontend/src/features/navbar/help/HelpButton.css create mode 100644 worklenz-frontend/src/features/navbar/help/HelpButton.tsx create mode 100644 worklenz-frontend/src/features/navbar/invite/InviteButton.tsx create mode 100644 worklenz-frontend/src/features/navbar/mobileMenu/MobileMenuButton.tsx create mode 100644 worklenz-frontend/src/features/navbar/mobileMenu/mobileMenu.css create mode 100644 worklenz-frontend/src/features/navbar/navRoutes.ts create mode 100644 worklenz-frontend/src/features/navbar/navbar-logo.tsx create mode 100644 worklenz-frontend/src/features/navbar/navbar.tsx create mode 100644 worklenz-frontend/src/features/navbar/notificationSlice.ts create mode 100644 worklenz-frontend/src/features/navbar/switchTeam/SwitchTeamButton.tsx create mode 100644 worklenz-frontend/src/features/navbar/switchTeam/switchTeam.css create mode 100644 worklenz-frontend/src/features/navbar/upgradePlan/UpgradePlanButton.tsx create mode 100644 worklenz-frontend/src/features/navbar/user-profile/profile-button.css create mode 100644 worklenz-frontend/src/features/navbar/user-profile/profile-button.tsx create mode 100644 worklenz-frontend/src/features/navbar/user-profile/profile-dropdown.css create mode 100644 worklenz-frontend/src/features/project/project-drawer.slice.ts create mode 100644 worklenz-frontend/src/features/project/project.slice.ts create mode 100644 worklenz-frontend/src/features/projects/bulkActions/BulkTasksActionContainer.tsx create mode 100644 worklenz-frontend/src/features/projects/bulkActions/bulkActionSlice.ts create mode 100644 worklenz-frontend/src/features/projects/insights/project-insights.slice.ts create mode 100644 worklenz-frontend/src/features/projects/lookups/projectCategories/projectCategoriesSlice.ts create mode 100644 worklenz-frontend/src/features/projects/lookups/projectHealth/projectHealthSlice.ts create mode 100644 worklenz-frontend/src/features/projects/lookups/projectStatuses/projectStatusesSlice.ts create mode 100644 worklenz-frontend/src/features/projects/priority/prioritySlice.ts create mode 100644 worklenz-frontend/src/features/projects/projectsSlice.ts create mode 100644 worklenz-frontend/src/features/projects/singleProject/members/projectMembersSlice.ts create mode 100644 worklenz-frontend/src/features/projects/singleProject/phase/ConfigPhaseButton.tsx create mode 100644 worklenz-frontend/src/features/projects/singleProject/phase/PhaseDrawer.tsx create mode 100644 worklenz-frontend/src/features/projects/singleProject/phase/PhaseHeader.tsx create mode 100644 worklenz-frontend/src/features/projects/singleProject/phase/PhaseOptionItem.tsx create mode 100644 worklenz-frontend/src/features/projects/singleProject/phase/phases.slice.ts create mode 100644 worklenz-frontend/src/features/projects/singleProject/task-list-custom-columns/task-list-custom-columns-slice.ts create mode 100644 worklenz-frontend/src/features/projects/singleProject/taskListColumns/taskColumnsSlice.ts create mode 100644 worklenz-frontend/src/features/projects/singleProject/updates/updatesSlice.ts create mode 100644 worklenz-frontend/src/features/projects/status/DeleteStatusSlice.ts create mode 100644 worklenz-frontend/src/features/projects/status/StatusSlice.ts create mode 100644 worklenz-frontend/src/features/projects/update-project/update-project-drawer.tsx create mode 100644 worklenz-frontend/src/features/reporting/membersReports/membersReportsDrawer/activity-log-tab/activity-log-card.tsx create mode 100644 worklenz-frontend/src/features/reporting/membersReports/membersReportsDrawer/activity-log-tab/members-reports-activity-logs-tab.tsx create mode 100644 worklenz-frontend/src/features/reporting/membersReports/membersReportsDrawer/members-reports-drawer-tabs.tsx create mode 100644 worklenz-frontend/src/features/reporting/membersReports/membersReportsDrawer/members-reports-drawer.tsx create mode 100644 worklenz-frontend/src/features/reporting/membersReports/membersReportsDrawer/overviewTab/MembersReportsOverviewTab.tsx create mode 100644 worklenz-frontend/src/features/reporting/membersReports/membersReportsDrawer/overviewTab/MembersReportsPriorityGraph.tsx create mode 100644 worklenz-frontend/src/features/reporting/membersReports/membersReportsDrawer/overviewTab/MembersReportsProjectGraph.tsx create mode 100644 worklenz-frontend/src/features/reporting/membersReports/membersReportsDrawer/overviewTab/MembersReportsStatusGraph.tsx create mode 100644 worklenz-frontend/src/features/reporting/membersReports/membersReportsDrawer/overviewTab/members-overview-projects-stats-drawer/members-overview-projects-stats-drawer.tsx create mode 100644 worklenz-frontend/src/features/reporting/membersReports/membersReportsDrawer/overviewTab/members-overview-projects-stats-drawer/members-overview-projects-stats-table.tsx create mode 100644 worklenz-frontend/src/features/reporting/membersReports/membersReportsDrawer/overviewTab/members-overview-tasks-stats-drawer/members-overview-tasks-stats-drawer.tsx create mode 100644 worklenz-frontend/src/features/reporting/membersReports/membersReportsDrawer/overviewTab/members-overview-tasks-stats-drawer/members-overview-tasks-stats-table.tsx create mode 100644 worklenz-frontend/src/features/reporting/membersReports/membersReportsDrawer/overviewTab/members-reports-stat-card.tsx create mode 100644 worklenz-frontend/src/features/reporting/membersReports/membersReportsDrawer/taskTab/MembersReportsTasksTab.tsx create mode 100644 worklenz-frontend/src/features/reporting/membersReports/membersReportsDrawer/taskTab/MembersReportsTasksTable.tsx create mode 100644 worklenz-frontend/src/features/reporting/membersReports/membersReportsDrawer/taskTab/ProjectFilter.tsx create mode 100644 worklenz-frontend/src/features/reporting/membersReports/membersReportsDrawer/time-log-tab/billable-filter.tsx create mode 100644 worklenz-frontend/src/features/reporting/membersReports/membersReportsDrawer/time-log-tab/members-reports-time-logs-tab.tsx create mode 100644 worklenz-frontend/src/features/reporting/membersReports/membersReportsDrawer/time-log-tab/time-log-card.tsx create mode 100644 worklenz-frontend/src/features/reporting/membersReports/membersReportsSlice.ts create mode 100644 worklenz-frontend/src/features/reporting/projectReports/project-reports-slice.ts create mode 100644 worklenz-frontend/src/features/reporting/projectReports/project-reports-table-column-slice/project-reports-table-column-slice.ts create mode 100644 worklenz-frontend/src/features/reporting/projectReports/projectReportsDrawer/ProjectReportsDrawer.tsx create mode 100644 worklenz-frontend/src/features/reporting/projectReports/projectReportsDrawer/ProjectReportsDrawerTabs.tsx create mode 100644 worklenz-frontend/src/features/reporting/projectReports/projectReportsDrawer/membersTab/ProjectReportsMembersTab.tsx create mode 100644 worklenz-frontend/src/features/reporting/projectReports/projectReportsDrawer/membersTab/ProjectReportsMembersTable.tsx create mode 100644 worklenz-frontend/src/features/reporting/projectReports/projectReportsDrawer/membersTab/projectReportsMembersTaskDrawer/ProjectReportsMembersTaskDrawer.tsx create mode 100644 worklenz-frontend/src/features/reporting/projectReports/projectReportsDrawer/membersTab/projectReportsMembersTaskDrawer/ProjectReportsMembersTaskTable.tsx create mode 100644 worklenz-frontend/src/features/reporting/projectReports/projectReportsDrawer/overviewTab/ProjectReportsDueDateGraph.tsx create mode 100644 worklenz-frontend/src/features/reporting/projectReports/projectReportsDrawer/overviewTab/ProjectReportsOverviewTab.tsx create mode 100644 worklenz-frontend/src/features/reporting/projectReports/projectReportsDrawer/overviewTab/ProjectReportsPriorityGraph.tsx create mode 100644 worklenz-frontend/src/features/reporting/projectReports/projectReportsDrawer/overviewTab/ProjectReportsStatCard.tsx create mode 100644 worklenz-frontend/src/features/reporting/projectReports/projectReportsDrawer/overviewTab/ProjectReportsStatusGraph.tsx create mode 100644 worklenz-frontend/src/features/reporting/projectReports/projectReportsDrawer/tasksTab/ProjectReportsTaskTable.tsx create mode 100644 worklenz-frontend/src/features/reporting/projectReports/projectReportsDrawer/tasksTab/ProjectReportsTasksTab.tsx create mode 100644 worklenz-frontend/src/features/reporting/projectReports/projectReportsDrawer/tasksTab/group-by-filter.tsx create mode 100644 worklenz-frontend/src/features/reporting/reporting.slice.ts create mode 100644 worklenz-frontend/src/features/reporting/time-reports/time-reports-overview.slice.ts create mode 100644 worklenz-frontend/src/features/roadmap/roadmap-slice.ts create mode 100644 worklenz-frontend/src/features/schedule-old/ProjectTimelineModal.tsx create mode 100644 worklenz-frontend/src/features/schedule-old/ScheduleDrawer.tsx create mode 100644 worklenz-frontend/src/features/schedule-old/ScheduleSettingsDrawer.tsx create mode 100644 worklenz-frontend/src/features/schedule-old/scheduleSlice.ts create mode 100644 worklenz-frontend/src/features/schedule/ProjectTimelineModal.tsx create mode 100644 worklenz-frontend/src/features/schedule/ScheduleDrawer.tsx create mode 100644 worklenz-frontend/src/features/schedule/ScheduleSettingsDrawer.tsx create mode 100644 worklenz-frontend/src/features/schedule/scheduleSlice.ts create mode 100644 worklenz-frontend/src/features/settings/categories/CustomColorsCategoryTag.tsx create mode 100644 worklenz-frontend/src/features/settings/categories/categoriesSlice.ts create mode 100644 worklenz-frontend/src/features/settings/categories/color-changed-category.tsx create mode 100644 worklenz-frontend/src/features/settings/client/CreateClientDrawer.tsx create mode 100644 worklenz-frontend/src/features/settings/client/UpdateClientDrawer.tsx create mode 100644 worklenz-frontend/src/features/settings/client/clientSlice.ts create mode 100644 worklenz-frontend/src/features/settings/job/CreateJobTitlesDrawer.tsx create mode 100644 worklenz-frontend/src/features/settings/job/UpdateJobTitlesDrawer.tsx create mode 100644 worklenz-frontend/src/features/settings/job/jobSlice.ts create mode 100644 worklenz-frontend/src/features/settings/label/labelSlice.ts create mode 100644 worklenz-frontend/src/features/settings/member/memberSlice.ts create mode 100644 worklenz-frontend/src/features/settings/taskTemplates/TaskTemplateDrawer.json create mode 100644 worklenz-frontend/src/features/settings/taskTemplates/taskTemplateSlice.ts create mode 100644 worklenz-frontend/src/features/task-drawer/task-drawer.slice.ts create mode 100644 worklenz-frontend/src/features/taskAttributes/taskLabelSlice.ts create mode 100644 worklenz-frontend/src/features/taskAttributes/taskMemberSlice.ts create mode 100644 worklenz-frontend/src/features/taskAttributes/taskPrioritySlice.ts create mode 100644 worklenz-frontend/src/features/taskAttributes/taskStatusSlice.ts create mode 100644 worklenz-frontend/src/features/tasks/tasks.slice.ts create mode 100644 worklenz-frontend/src/features/team-members/team-members.slice.ts create mode 100644 worklenz-frontend/src/features/teams/teamSlice.ts create mode 100644 worklenz-frontend/src/features/theme/ThemeSelector.tsx create mode 100644 worklenz-frontend/src/features/theme/ThemeWrapper.tsx create mode 100644 worklenz-frontend/src/features/theme/themeSlice.ts create mode 100644 worklenz-frontend/src/features/timeReport/projects/ProjectTimeLog.json create mode 100644 worklenz-frontend/src/features/timeReport/projects/ProjectTimeLogDrawer.css create mode 100644 worklenz-frontend/src/features/timeReport/projects/ProjectTimeLogDrawer.tsx create mode 100644 worklenz-frontend/src/features/timeReport/projects/timeLogSlice.ts create mode 100644 worklenz-frontend/src/features/user/userSlice.ts create mode 100644 worklenz-frontend/src/hooks/useAlert.ts create mode 100644 worklenz-frontend/src/hooks/useAppDispatch.ts create mode 100644 worklenz-frontend/src/hooks/useAppSelector.ts create mode 100644 worklenz-frontend/src/hooks/useAuth.ts create mode 100644 worklenz-frontend/src/hooks/useDoumentTItle.ts create mode 100644 worklenz-frontend/src/hooks/useDragCursor.ts create mode 100644 worklenz-frontend/src/hooks/useIsProjectManager.ts create mode 100644 worklenz-frontend/src/hooks/useIsomorphicLayoutEffect.ts create mode 100644 worklenz-frontend/src/hooks/useMixpanelTracking.tsx create mode 100644 worklenz-frontend/src/hooks/useResponsive.ts create mode 100644 worklenz-frontend/src/hooks/useSelectedProject.ts create mode 100644 worklenz-frontend/src/hooks/useSocketService.ts create mode 100644 worklenz-frontend/src/hooks/useTabSearchParam.ts create mode 100644 worklenz-frontend/src/hooks/useTaskDrawerUrlSync.ts create mode 100644 worklenz-frontend/src/hooks/useTaskTimer.ts create mode 100644 worklenz-frontend/src/i18n.ts create mode 100644 worklenz-frontend/src/index.css delete mode 100644 worklenz-frontend/src/index.html create mode 100644 worklenz-frontend/src/index.tsx create mode 100644 worklenz-frontend/src/layouts/AuthLayout.tsx create mode 100644 worklenz-frontend/src/layouts/AuthenticatedLayout.tsx create mode 100644 worklenz-frontend/src/layouts/MainLayout.tsx create mode 100644 worklenz-frontend/src/layouts/ReportingLayout.tsx create mode 100644 worklenz-frontend/src/layouts/SettingsLayout.tsx create mode 100644 worklenz-frontend/src/layouts/admin-center-layout.tsx create mode 100644 worklenz-frontend/src/lib/project/project-constants.ts create mode 100644 worklenz-frontend/src/lib/project/project-view-constants.ts create mode 100644 worklenz-frontend/src/lib/reporting/reporting-constants.ts create mode 100644 worklenz-frontend/src/lib/settings/settings-constants.ts delete mode 100644 worklenz-frontend/src/main.ts delete mode 100644 worklenz-frontend/src/manifest.webmanifest create mode 100644 worklenz-frontend/src/pages/404-page/404-page.tsx create mode 100644 worklenz-frontend/src/pages/account-setup/account-setup.css create mode 100644 worklenz-frontend/src/pages/account-setup/account-setup.tsx create mode 100644 worklenz-frontend/src/pages/admin-center/admin-center-constants.ts create mode 100644 worklenz-frontend/src/pages/admin-center/billing/billing.tsx create mode 100644 worklenz-frontend/src/pages/admin-center/overview/overview.tsx create mode 100644 worklenz-frontend/src/pages/admin-center/projects/projects.css create mode 100644 worklenz-frontend/src/pages/admin-center/projects/projects.tsx create mode 100644 worklenz-frontend/src/pages/admin-center/sidebar/sidebar.css create mode 100644 worklenz-frontend/src/pages/admin-center/sidebar/sidebar.tsx create mode 100644 worklenz-frontend/src/pages/admin-center/teams/teams.css create mode 100644 worklenz-frontend/src/pages/admin-center/teams/teams.tsx create mode 100644 worklenz-frontend/src/pages/admin-center/users/users.tsx create mode 100644 worklenz-frontend/src/pages/auth/authenticating.tsx create mode 100644 worklenz-frontend/src/pages/auth/forgot-password-page.tsx create mode 100644 worklenz-frontend/src/pages/auth/logging-out.tsx create mode 100644 worklenz-frontend/src/pages/auth/login-page.tsx create mode 100644 worklenz-frontend/src/pages/auth/signup-page.tsx create mode 100644 worklenz-frontend/src/pages/auth/verify-reset-email.tsx create mode 100644 worklenz-frontend/src/pages/home/greeting-with-time.tsx create mode 100644 worklenz-frontend/src/pages/home/home-page.tsx create mode 100644 worklenz-frontend/src/pages/home/recent-and-favourite-project-list/add-favourite-project-button.tsx create mode 100644 worklenz-frontend/src/pages/home/recent-and-favourite-project-list/recent-and-favourite-project-list.tsx create mode 100644 worklenz-frontend/src/pages/home/task-list/add-task-inline-form.tsx create mode 100644 worklenz-frontend/src/pages/home/task-list/calendar-view.tsx create mode 100644 worklenz-frontend/src/pages/home/task-list/list-view.tsx create mode 100644 worklenz-frontend/src/pages/home/task-list/tasks-list.css create mode 100644 worklenz-frontend/src/pages/home/task-list/tasks-list.tsx create mode 100644 worklenz-frontend/src/pages/home/todo-list/todo-done-button.tsx create mode 100644 worklenz-frontend/src/pages/home/todo-list/todo-list.tsx create mode 100644 worklenz-frontend/src/pages/license-expired/license-expired.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-list.css create mode 100644 worklenz-frontend/src/pages/projects/project-list.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/board/card.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/board/column.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/board/project-view-board.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/roadmap/project-view-roadmap.css create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/roadmap/project-view-roadmap.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/roadmap/roadmap-grant-chart.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/roadmap/roadmap-table/roadmap-table.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/roadmap/roadmap-table/roadmap-task-cell.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/roadmap/time-filter.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/task-list/table-v2.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/task-list/task-list-columns/task-list-columns.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/task-list/task-list-custom.css create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/task-list/task-list-custom.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/task-list/task-list-header/task-list-header.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/task-list/task-list-instant-task-input/task-list-instant-task-input.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/task-list/task-list-table-old/task-list-table-old.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/task-list/task-list-table-old/task-list-table.css create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/task-list/task-list-table-wrapper/task-list-table-wrapper.css create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/task-list/task-list-table-wrapper/task-list-table-wrapper.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/task-list/task-list.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/ProjectViewTaskList.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/statusTables/StatusGroupTables.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListFilters/GroupByFilterDropdown.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListFilters/LabelsFilterDropdown.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListFilters/MembersFilterDropdown.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListFilters/PriorityFilterDropdown.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListFilters/SearchDropdown.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListFilters/ShowFieldsFilterDropdown.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListFilters/SortFilterDropdown.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListFilters/TaskListFilters.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListTable/TaskListTable.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListTable/TaskListTableWrapper.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListTable/columns/columnList.ts create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListTable/contextMenu/TaskContextMenu.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListTable/taskListTableCells/TaskCell.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListTable/taskListTableCells/TaskProgress.css create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListTable/taskListTableCells/TaskProgress.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListTable/taskListTableCells/TimeTracker.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListTable/taskListTableCells/mockTimeLogs.ts create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListTable/taskListTableRows/AddSubTaskListRow.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListTable/taskListTableRows/AddTaskListRow.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListTable/taskListTableWrapper.css create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/updates/project-view-updates.css create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/updates/project-view-updates.tsx create mode 100644 worklenz-frontend/src/pages/projects/project-view-1/workload/ProjectViewWorkload.tsx rename worklenz-frontend/src/{app/administrator/components/clients-autocomplete/clients-autocomplete.component.scss => pages/projects/project-view-1/workload/projectViewWorkload.css} (100%) create mode 100644 worklenz-frontend/src/pages/projects/projectView/board/board-section/board-section-card/board-create-section-card.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/board/board-section/board-section-card/board-section-card-header.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/board/board-section/board-section-card/board-section-card.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/board/board-section/board-section-container.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/board/board-section/board-sub-task-card/board-create-sub-task-card.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/board/board-section/board-sub-task-card/board-sub-task-card.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/board/board-section/board-task-card/board-view-create-task-card.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/board/board-section/board-task-card/board-view-task-card.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/board/project-view-board.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/files/project-view-files.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/insights/insights-members/insights-members.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/insights/insights-members/tables/assigned-tasks-list.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/insights/insights-members/tables/tasks-by-members.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/insights/insights-overview/graphs/priority-overview.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/insights/insights-overview/graphs/status-overview.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/insights/insights-overview/insights-overview.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/insights/insights-overview/tables/last-updated-tasks.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/insights/insights-overview/tables/project-deadline.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/insights/insights-tasks/insights-tasks.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/insights/insights-tasks/tables/over-logged-tasks-table.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/insights/insights-tasks/tables/overdue-tasks-table.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/insights/insights-tasks/tables/task-completed-early-table.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/insights/insights-tasks/tables/task-completed-late-table.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/insights/member-stats/member-stats.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/insights/project-stats/project-stats.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/insights/project-view-insights.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/members/project-view-members.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/project-view-header.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/project-view.css create mode 100644 worklenz-frontend/src/pages/projects/projectView/project-view.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/groupTables/TaskGroupList.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/groupTables/priorityTables/PriorityGroupTables.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/project-view-task-list.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-filters/task-list-filters.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/context-menu/task-context-menu.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-cells/custom-column-label-cell/custom-column-label-cell.css create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-cells/custom-column-label-cell/custom-column-label-cell.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-cells/custom-column-selection-cell/custom-column-selection-cell.css create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-cells/custom-column-selection-cell/custom-column-selection-cell.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-header/custom-column-header.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/add-custom-column-button.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/custom-column-modal.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/formula-type-column/formula-type-column.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/key-type-column/key-type-column.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/label-type-column/label-type-column.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/number-type-column/formatted-type-number-column.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/number-type-column/number-type-column.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/number-type-column/percentage-type-number-column.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/number-type-column/unformatted-type-number-column.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/number-type-column/with-label-type-number-column.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/selection-type-column/selection-type-column.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-group-wrapper/task-group-wrapper.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-completed-date-cell/task-list-completed-date-cell.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-created-date-cell/task-list-created-date-cell.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-description-cell/task-list-description-cell.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-due-date-cell/task-list-due-date-cell.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-due-time-cell/task-list-due-time-cell.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-estimation-cell/task-list-estimation-cell.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-labels-cell/task-list-labels-cell.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-last-updated-cell/task-list-last-updated-cell.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-members-cell/task-list-members-cell.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-progress-cell/task-list-progress-cell.css create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-progress-cell/task-list-progress-cell.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-reporter-cell/task-list-reporter-cell.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-start-date-cell/task-list-start-date-cell.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-task-cell/task-list-task-cell.css create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-task-cell/task-list-task-cell.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-task-id-cell/task-list-task-id-cell.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-time-tracker-cell/task-list-time-tracker-cell.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-rows/add-sub-task-list-row.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-rows/add-task-list-row.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-wrapper/task-list-table-wrapper.css create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-wrapper/task-list-table-wrapper.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-time-tracker-cell/task-list-time-tracker-cell.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/updates/ProjectViewUpdates.tsx create mode 100644 worklenz-frontend/src/pages/reporting/members-reports/members-reports-table/members-reports-table.tsx create mode 100644 worklenz-frontend/src/pages/reporting/members-reports/members-reports-table/tablesCells/memberCell/MemberCell.tsx create mode 100644 worklenz-frontend/src/pages/reporting/members-reports/members-reports-table/tablesCells/tasksProgressCell/TasksProgressCell.tsx create mode 100644 worklenz-frontend/src/pages/reporting/members-reports/members-reports.tsx create mode 100644 worklenz-frontend/src/pages/reporting/overview-reports/overview-reports.tsx create mode 100644 worklenz-frontend/src/pages/reporting/overview-reports/overview-stat-card.tsx create mode 100644 worklenz-frontend/src/pages/reporting/overview-reports/overview-stats.tsx create mode 100644 worklenz-frontend/src/pages/reporting/overview-reports/overview-table/overview-reports-table.tsx create mode 100644 worklenz-frontend/src/pages/reporting/page-header/custom-page-header.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-filters/project-categories-filter-dropdown.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-filters/project-health-filter-dropdown.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-filters/project-managers-filter-dropdown.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-filters/project-reports-filters.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-filters/project-status-filter-dropdown.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-filters/project-table-show-fields-dropdown.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/projects-reports-table.css create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/projects-reports-table.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/table-cells/estimated-vs-actual-cell/estimated-vs-actual-cell.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/table-cells/last-activity-cell/last-activity-cell.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/table-cells/project-category-cell/project-category-cell.css create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/table-cells/project-category-cell/project-category-cell.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/table-cells/project-cell/project-cell.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/table-cells/project-client-cell/project-client-cell.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/table-cells/project-dates-cell/project-dates-cell.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/table-cells/project-days-left-and-overdue-cell/project-days-left-and-overdue-cell.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/table-cells/project-health-cell/project-health-cell.css create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/table-cells/project-health-cell/project-health-cell.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/table-cells/project-manager-cell/project-manager-cell.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/table-cells/project-status-cell/project-status-cell.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/table-cells/project-team-cell/project-team-cell.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/table-cells/project-update-cell/project-update-cell.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/table-cells/tasks-progress-cell/tasks-progress-cell.tsx create mode 100644 worklenz-frontend/src/pages/reporting/projects-reports/projects-reports.tsx create mode 100644 worklenz-frontend/src/pages/reporting/sidebar/reporting-collapsed-button.tsx create mode 100644 worklenz-frontend/src/pages/reporting/sidebar/reporting-sider.tsx create mode 100644 worklenz-frontend/src/pages/reporting/time-reports/estimated-vs-actual-time-sheet/estimated-vs-actual-time-sheet.tsx create mode 100644 worklenz-frontend/src/pages/reporting/time-reports/members-time-sheet/members-time-sheet.tsx create mode 100644 worklenz-frontend/src/pages/reporting/time-reports/project-time-sheet/project-time-sheet-chart.tsx create mode 100644 worklenz-frontend/src/pages/reporting/time-reports/time-sheet-table/time-sheet-table.css create mode 100644 worklenz-frontend/src/pages/reporting/time-reports/time-sheet-table/time-sheet-table.tsx create mode 100644 worklenz-frontend/src/pages/reporting/timeReports/estimated-vs-actual-time-reports.tsx create mode 100644 worklenz-frontend/src/pages/reporting/timeReports/members-time-reports.tsx create mode 100644 worklenz-frontend/src/pages/reporting/timeReports/overview-time-reports.tsx create mode 100644 worklenz-frontend/src/pages/reporting/timeReports/page-header/billable.tsx create mode 100644 worklenz-frontend/src/pages/reporting/timeReports/page-header/categories.tsx create mode 100644 worklenz-frontend/src/pages/reporting/timeReports/page-header/projects.tsx create mode 100644 worklenz-frontend/src/pages/reporting/timeReports/page-header/team.tsx create mode 100644 worklenz-frontend/src/pages/reporting/timeReports/page-header/time-report-page-header.tsx create mode 100644 worklenz-frontend/src/pages/reporting/timeReports/projects-time-reports.tsx create mode 100644 worklenz-frontend/src/pages/reporting/timeReports/timeReportingRightHeader/TimeReportingRightHeader.tsx create mode 100644 worklenz-frontend/src/pages/schedule-old/schedule.tsx create mode 100644 worklenz-frontend/src/pages/schedule/grant-chart.tsx create mode 100644 worklenz-frontend/src/pages/schedule/schedule.tsx create mode 100644 worklenz-frontend/src/pages/settings/categories/categories-settings.tsx create mode 100644 worklenz-frontend/src/pages/settings/change-password/change-password.tsx create mode 100644 worklenz-frontend/src/pages/settings/clients/client-drawer.tsx create mode 100644 worklenz-frontend/src/pages/settings/clients/clients-settings.tsx create mode 100644 worklenz-frontend/src/pages/settings/job-titles/job-titles-drawer.tsx create mode 100644 worklenz-frontend/src/pages/settings/job-titles/job-titles-settings.tsx create mode 100644 worklenz-frontend/src/pages/settings/labels/labels-settings.tsx rename worklenz-frontend/src/{app/administrator/components/import-tasks-template/import-tasks-template.component.scss => pages/settings/language-and-region/language-and-region-settings.test.ts} (100%) create mode 100644 worklenz-frontend/src/pages/settings/language-and-region/language-and-region-settings.tsx create mode 100644 worklenz-frontend/src/pages/settings/notifications/notifications-settings.tsx create mode 100644 worklenz-frontend/src/pages/settings/profile/profile-settings.css create mode 100644 worklenz-frontend/src/pages/settings/profile/profile-settings.tsx create mode 100644 worklenz-frontend/src/pages/settings/project-templates/project-templates-settings.tsx create mode 100644 worklenz-frontend/src/pages/settings/project-templates/projectTemplateEditView/ProjectTemplateEditView.tsx create mode 100644 worklenz-frontend/src/pages/settings/sidebar/settings-sidebar.tsx create mode 100644 worklenz-frontend/src/pages/settings/task-templates/task-templates-settings.css create mode 100644 worklenz-frontend/src/pages/settings/task-templates/task-templates-settings.tsx create mode 100644 worklenz-frontend/src/pages/settings/team-members/team-members-settings.tsx create mode 100644 worklenz-frontend/src/pages/settings/teams/teams-settings.tsx create mode 100644 worklenz-frontend/src/pages/unauthorized/unauthorized.tsx create mode 100644 worklenz-frontend/src/react-app-env.d.ts create mode 100644 worklenz-frontend/src/reportWebVitals.ts delete mode 100644 worklenz-frontend/src/res/js/jquery-3.6.1.min.js delete mode 100644 worklenz-frontend/src/res/js/jquery.mentiony.js delete mode 100644 worklenz-frontend/src/res/js/smooth-scroll.js delete mode 100644 worklenz-frontend/src/res/scss/antd-variables.scss delete mode 100644 worklenz-frontend/src/res/scss/antd.scss delete mode 100644 worklenz-frontend/src/res/scss/gantt.scss delete mode 100644 worklenz-frontend/src/scss/editable-table.scss delete mode 100644 worklenz-frontend/src/scss/scrollbar.scss delete mode 100644 worklenz-frontend/src/scss/task-view.scss create mode 100644 worklenz-frontend/src/services/alerts/alertMiddleware.ts create mode 100644 worklenz-frontend/src/services/alerts/alertService.ts create mode 100644 worklenz-frontend/src/services/alerts/alertSlice.ts create mode 100644 worklenz-frontend/src/services/auth/auth.service.ts create mode 100644 worklenz-frontend/src/services/socket/socket.service.ts create mode 100644 worklenz-frontend/src/services/task-list/taskList.service.ts create mode 100644 worklenz-frontend/src/shared/constants.ts rename worklenz-frontend/src/{app => }/shared/socket-events.ts (87%) create mode 100644 worklenz-frontend/src/shared/worklenz-analytics-events.ts create mode 100644 worklenz-frontend/src/socket/config.ts create mode 100644 worklenz-frontend/src/socket/socketContext.tsx delete mode 100644 worklenz-frontend/src/styles.scss create mode 100644 worklenz-frontend/src/styles/colors.ts create mode 100644 worklenz-frontend/src/styles/customOverrides.css delete mode 100644 worklenz-frontend/src/test.ts delete mode 100644 worklenz-frontend/src/theme.less create mode 100644 worklenz-frontend/src/types/admin-center/admin-center.types.ts create mode 100644 worklenz-frontend/src/types/admin-center/country.types.ts create mode 100644 worklenz-frontend/src/types/admin-center/team.types.ts create mode 100644 worklenz-frontend/src/types/alert.types.ts create mode 100644 worklenz-frontend/src/types/apiModels/taskPrioritiesGetResponse.types.ts create mode 100644 worklenz-frontend/src/types/auth/local-session.types.ts create mode 100644 worklenz-frontend/src/types/auth/login.types.ts rename worklenz-frontend/src/{app/interfaces/api-models/user-sign-up-request.ts => types/auth/signup.types.ts} (100%) create mode 100644 worklenz-frontend/src/types/auth/verify-reset-email.types.ts rename worklenz-frontend/src/{app/interfaces/avatar-attachment.ts => types/avatarAttachment.types.ts} (100%) create mode 100644 worklenz-frontend/src/types/categories.types.ts create mode 100644 worklenz-frontend/src/types/client.types.ts rename worklenz-frontend/src/{app/interfaces/api-models/server-response.ts => types/common.types.ts} (100%) create mode 100644 worklenz-frontend/src/types/dependencies.types.ts create mode 100644 worklenz-frontend/src/types/home/home-page.types.ts create mode 100644 worklenz-frontend/src/types/home/my-tasks.types.ts create mode 100644 worklenz-frontend/src/types/home/tasks.types.ts create mode 100644 worklenz-frontend/src/types/job.types.ts rename worklenz-frontend/src/{app/interfaces/task-label.ts => types/label.type.ts} (100%) create mode 100644 worklenz-frontend/src/types/member.types.ts create mode 100644 worklenz-frontend/src/types/mixpanel.types.ts create mode 100644 worklenz-frontend/src/types/notification.types.ts create mode 100644 worklenz-frontend/src/types/notifications/notifications.types.ts create mode 100644 worklenz-frontend/src/types/phase.types.ts create mode 100644 worklenz-frontend/src/types/project-list/project-list.types.ts create mode 100644 worklenz-frontend/src/types/project-templates/project-templates.types.ts create mode 100644 worklenz-frontend/src/types/project.types.ts create mode 100644 worklenz-frontend/src/types/project/project-insights.types.ts create mode 100644 worklenz-frontend/src/types/project/project.types.ts rename worklenz-frontend/src/{app/interfaces/project-access-level.ts => types/project/projectAccessLevel.types.ts} (100%) rename worklenz-frontend/src/{app/interfaces/project-category.ts => types/project/projectCategory.types.ts} (100%) create mode 100644 worklenz-frontend/src/types/project/projectComments.types.ts rename worklenz-frontend/src/{app/interfaces/projects-filter-config.ts => types/project/projectFilterConfig.types.ts} (100%) create mode 100644 worklenz-frontend/src/types/project/projectHealth.types.ts create mode 100644 worklenz-frontend/src/types/project/projectInsights.types.ts create mode 100644 worklenz-frontend/src/types/project/projectManager.types.ts rename worklenz-frontend/src/{app/interfaces/project-member.ts => types/project/projectMember.types.ts} (100%) rename worklenz-frontend/src/{app/interfaces/project-status.ts => types/project/projectStatus.types.ts} (100%) create mode 100644 worklenz-frontend/src/types/project/projectTasksViewModel.types.ts create mode 100644 worklenz-frontend/src/types/project/projectTemplate.types.ts create mode 100644 worklenz-frontend/src/types/project/projectViewModel.types.ts create mode 100644 worklenz-frontend/src/types/project/projectsViewModel.types.ts create mode 100644 worklenz-frontend/src/types/projectMember.types.ts create mode 100644 worklenz-frontend/src/types/reporting/reporting-allocation.types.ts create mode 100644 worklenz-frontend/src/types/reporting/reporting-filters.types.ts create mode 100644 worklenz-frontend/src/types/reporting/reporting.types.ts create mode 100644 worklenz-frontend/src/types/schedule/schedule-v2.types.ts create mode 100644 worklenz-frontend/src/types/schedule/schedule.types.ts rename worklenz-frontend/src/{app/interfaces/notification-settings.ts => types/settings/notifications.types.ts} (100%) rename worklenz-frontend/src/{app/interfaces/profile-settings.ts => types/settings/profile.types.ts} (100%) create mode 100644 worklenz-frontend/src/types/settings/task-templates.types.ts rename worklenz-frontend/src/{app/interfaces/timezone.ts => types/settings/timezone.types.ts} (100%) create mode 100644 worklenz-frontend/src/types/socket.types.ts create mode 100644 worklenz-frontend/src/types/status.types.ts create mode 100644 worklenz-frontend/src/types/task.types.ts create mode 100644 worklenz-frontend/src/types/tasks/bulk-action-bar.types.ts create mode 100644 worklenz-frontend/src/types/tasks/subTask.types.ts create mode 100644 worklenz-frontend/src/types/tasks/task-activity-logs-get-request.ts create mode 100644 worklenz-frontend/src/types/tasks/task-assignee-update-response.ts create mode 100644 worklenz-frontend/src/types/tasks/task-attachment-view-model.ts create mode 100644 worklenz-frontend/src/types/tasks/task-comments.types.ts create mode 100644 worklenz-frontend/src/types/tasks/task-create-request.types.ts create mode 100644 worklenz-frontend/src/types/tasks/task-dependency.types.ts create mode 100644 worklenz-frontend/src/types/tasks/task-list-priority.types.ts create mode 100644 worklenz-frontend/src/types/tasks/task-list-status.types.ts rename worklenz-frontend/src/{app/interfaces/api-models/task-log-create-request.ts => types/tasks/task-log-view.types.ts} (100%) rename worklenz-frontend/src/{app/interfaces => types/tasks}/task-phase-change-response.ts (100%) rename worklenz-frontend/src/{app/interfaces/api-models => types/tasks}/task-status-create-request.ts (77%) rename worklenz-frontend/src/{app/interfaces/api-models/task-status-update-model.ts => types/tasks/task-status-update-model.types.ts} (100%) create mode 100644 worklenz-frontend/src/types/tasks/task.types.ts create mode 100644 worklenz-frontend/src/types/tasks/taskLabel.types.ts create mode 100644 worklenz-frontend/src/types/tasks/taskList.types.ts create mode 100644 worklenz-frontend/src/types/tasks/taskListFilters.types.ts rename worklenz-frontend/src/{app/interfaces/api-models/task-phase.ts => types/tasks/taskPhase.types.ts} (100%) create mode 100644 worklenz-frontend/src/types/tasks/taskPriority.types.ts create mode 100644 worklenz-frontend/src/types/tasks/taskStatus.types.ts rename worklenz-frontend/src/{app/interfaces/task-status-category.ts => types/tasks/taskStatusCategory.types.ts} (100%) create mode 100644 worklenz-frontend/src/types/tasks/taskStatusGetResponse.types.ts create mode 100644 worklenz-frontend/src/types/teamMembers/inlineMember.types.ts create mode 100644 worklenz-frontend/src/types/teamMembers/team-member-create-request.ts rename worklenz-frontend/src/{app/interfaces/team-member.ts => types/teamMembers/teamMember.types.ts} (100%) create mode 100644 worklenz-frontend/src/types/teamMembers/teamMembersGetResponse.types.ts create mode 100644 worklenz-frontend/src/types/teamMembers/teamMembersViewModel.types.ts create mode 100644 worklenz-frontend/src/types/teams/team.type.ts create mode 100644 worklenz-frontend/src/types/timeSheet/project.types.ts create mode 100644 worklenz-frontend/src/types/todo.types.ts create mode 100644 worklenz-frontend/src/types/updates.types.ts create mode 100644 worklenz-frontend/src/types/user.types.ts delete mode 100644 worklenz-frontend/src/typings.d.ts create mode 100644 worklenz-frontend/src/utils/calculate-time-difference.ts create mode 100644 worklenz-frontend/src/utils/calculate-time-gap.ts create mode 100644 worklenz-frontend/src/utils/check-task-dependency-status.ts create mode 100644 worklenz-frontend/src/utils/colorUtils.ts create mode 100644 worklenz-frontend/src/utils/current-date-string.ts create mode 100644 worklenz-frontend/src/utils/dateUtils.ts create mode 100644 worklenz-frontend/src/utils/durationDateFormat.ts create mode 100644 worklenz-frontend/src/utils/errorLogger.ts create mode 100644 worklenz-frontend/src/utils/fetchData.ts create mode 100644 worklenz-frontend/src/utils/file-utils.ts create mode 100644 worklenz-frontend/src/utils/format-date-time-with-locale.ts create mode 100644 worklenz-frontend/src/utils/get-initial-theme.ts create mode 100644 worklenz-frontend/src/utils/getPriorityColors.ts create mode 100644 worklenz-frontend/src/utils/getStatusColor.ts create mode 100644 worklenz-frontend/src/utils/greetingString.ts create mode 100644 worklenz-frontend/src/utils/language-utils.ts create mode 100644 worklenz-frontend/src/utils/localStorageFunctions.ts create mode 100644 worklenz-frontend/src/utils/mixpanelInit.ts create mode 100644 worklenz-frontend/src/utils/project-list-utils.ts create mode 100644 worklenz-frontend/src/utils/projectUtils.ts create mode 100644 worklenz-frontend/src/utils/sanitizeInput.ts create mode 100644 worklenz-frontend/src/utils/schedule.ts create mode 100644 worklenz-frontend/src/utils/session-helper.ts create mode 100644 worklenz-frontend/src/utils/simpleDateFormat.ts create mode 100644 worklenz-frontend/src/utils/sort-team-members.ts create mode 100644 worklenz-frontend/src/utils/test-utils.ts create mode 100644 worklenz-frontend/src/utils/themeWiseColor.ts create mode 100644 worklenz-frontend/src/utils/timeUtils.ts create mode 100644 worklenz-frontend/src/utils/timeZoneCurrencyMap.ts create mode 100644 worklenz-frontend/src/utils/toCamelCase.ts create mode 100644 worklenz-frontend/src/utils/toQueryString.ts create mode 100644 worklenz-frontend/src/utils/validateEmail.ts create mode 100644 worklenz-frontend/src/vite-env.d.ts create mode 100644 worklenz-frontend/tailwind.config.js delete mode 100644 worklenz-frontend/tsconfig.app.json delete mode 100644 worklenz-frontend/tsconfig.spec.json create mode 100644 worklenz-frontend/vite.config.ts diff --git a/.env b/.env new file mode 100644 index 00000000..97d22a55 --- /dev/null +++ b/.env @@ -0,0 +1,34 @@ +# Database configuration +DB_USER=postgres +DB_PASSWORD=password +DB_NAME=worklenz_db +DB_HOST=db +DB_PORT=5432 +DB_MAX_CLIENTS=50 + +# Server configuration +NODE_ENV=development +PORT=3000 +SESSION_NAME=worklenz.sid +SESSION_SECRET=worklenz-session-secret +COOKIE_SECRET=worklenz-cookie-secret + +# CORS +SOCKET_IO_CORS=http://localhost:5000 +SERVER_CORS=* + +# Storage configuration (MinIO) +STORAGE_PROVIDER=s3 +AWS_REGION=us-east-1 +AWS_BUCKET=worklenz-bucket +S3_ACCESS_KEY_ID=minioadmin +S3_SECRET_ACCESS_KEY=minioadmin +S3_URL=http://minio:9000 + +# Application URLs +HOSTNAME=localhost:5000 +FRONTEND_URL=http://localhost:5000 + +# For local development, set these to the frontend service +LOGIN_FAILURE_REDIRECT=http://localhost:5000 +LOGIN_SUCCESS_REDIRECT=http://localhost:5000/auth/authenticate \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..97d22a55 --- /dev/null +++ b/.env.example @@ -0,0 +1,34 @@ +# Database configuration +DB_USER=postgres +DB_PASSWORD=password +DB_NAME=worklenz_db +DB_HOST=db +DB_PORT=5432 +DB_MAX_CLIENTS=50 + +# Server configuration +NODE_ENV=development +PORT=3000 +SESSION_NAME=worklenz.sid +SESSION_SECRET=worklenz-session-secret +COOKIE_SECRET=worklenz-cookie-secret + +# CORS +SOCKET_IO_CORS=http://localhost:5000 +SERVER_CORS=* + +# Storage configuration (MinIO) +STORAGE_PROVIDER=s3 +AWS_REGION=us-east-1 +AWS_BUCKET=worklenz-bucket +S3_ACCESS_KEY_ID=minioadmin +S3_SECRET_ACCESS_KEY=minioadmin +S3_URL=http://minio:9000 + +# Application URLs +HOSTNAME=localhost:5000 +FRONTEND_URL=http://localhost:5000 + +# For local development, set these to the frontend service +LOGIN_FAILURE_REDIRECT=http://localhost:5000 +LOGIN_SUCCESS_REDIRECT=http://localhost:5000/auth/authenticate \ No newline at end of file diff --git a/README.md b/README.md index 988d912a..ae535ea8 100644 --- a/README.md +++ b/README.md @@ -120,3 +120,141 @@ Worklenz is open source and released under the [GNU Affero General Public Licens By contributing to Worklenz, you agree that your contributions will be licensed under its AGPL. +# Worklenz React + +This repository contains the React version of Worklenz with a Docker setup for easy development and deployment. + +## Getting Started with Docker + +The project includes a fully configured Docker setup with: +- Frontend React application +- Backend server +- PostgreSQL database +- MinIO for S3-compatible storage + +### Prerequisites + +- Docker and Docker Compose installed on your system +- Git + +### Quick Start + +1. Clone the repository: +```bash +git clone https://github.com/yourusername/worklenz-react-v1.git +cd worklenz-react-v1 +``` + +2. Start the Docker containers (choose one option): + +**Option 1: Using the provided scripts (easiest)** +- On Windows: + ``` + start.bat + ``` +- On Linux/macOS: + ```bash + ./start.sh + ``` + +**Option 2: Using Docker Compose directly** +```bash +docker-compose up -d +``` + +3. The application will be available at: + - Frontend: http://localhost:5000 + - Backend API: http://localhost:3000 + - MinIO Console: http://localhost:9001 (login with minioadmin/minioadmin) + +4. To stop the services (choose one option): + +**Option 1: Using the provided scripts** +- On Windows: + ``` + stop.bat + ``` +- On Linux/macOS: + ```bash + ./stop.sh + ``` + +**Option 2: Using Docker Compose directly** +```bash +docker-compose down +``` + +## MinIO Integration + +The project uses MinIO as an S3-compatible object storage service, which provides an open-source alternative to AWS S3 for development and production. + +### Working with MinIO + +MinIO provides an S3-compatible API, so any code that works with S3 will work with MinIO by simply changing the endpoint URL. The backend has been configured to use MinIO by default, with no additional configuration required. + +- **MinIO Console**: http://localhost:9001 + - Username: minioadmin + - Password: minioadmin + +- **Default Bucket**: worklenz-bucket (created automatically when the containers start) + +### Backend Storage Configuration + +The backend is pre-configured to use MinIO with the following settings: + +```javascript +// S3 credentials with MinIO defaults +export const REGION = process.env.AWS_REGION || "us-east-1"; +export const BUCKET = process.env.AWS_BUCKET || "worklenz-bucket"; +export const S3_URL = process.env.S3_URL || "http://minio:9000/worklenz-bucket"; +export const S3_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID || "minioadmin"; +export const S3_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY || "minioadmin"; +``` + +The S3 client is initialized with special MinIO configuration: + +```javascript +const s3Client = new S3Client({ + region: REGION, + credentials: { + accessKeyId: S3_ACCESS_KEY_ID || "", + secretAccessKey: S3_SECRET_ACCESS_KEY || "", + }, + endpoint: getEndpointFromUrl(), // Extracts endpoint from S3_URL + forcePathStyle: true, // Required for MinIO +}); +``` + +### Environment Configuration + +The `.env` file includes the necessary configuration for using MinIO: + +``` +STORAGE_PROVIDER=s3 +AWS_REGION=us-east-1 +AWS_BUCKET=worklenz-bucket +S3_ACCESS_KEY_ID=minioadmin +S3_SECRET_ACCESS_KEY=minioadmin +S3_URL=http://minio:9000 +``` + +When the backend service starts, it will use these environment variables to connect to MinIO for file storage. + +## Development + +For development, you can use the provided Docker setup which includes all necessary dependencies. The code will be running inside containers, but you can still edit files locally and see changes reflected in real-time. + +## Production Deployment + +For production deployment: + +1. Update the `.env` file with production settings +2. Build custom Docker images or use the provided ones +3. Deploy using Docker Compose or a container orchestration platform like Kubernetes + +For MinIO in production, consider: +- Setting up proper credentials (change default minioadmin/minioadmin) +- Configuring persistent storage +- Setting up proper networking and access controls +- Using multiple MinIO instances for high availability + diff --git a/docker-compose.yml b/docker-compose.yml index 9de527e7..d177d2d5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,83 +1,117 @@ services: frontend: - image: ghcr.io/worklenz/worklenz-frontend - build: - context: ./worklenz-frontend - dockerfile: Dockerfile + image: docker.io/kithceydigital/worklenz_frontend:latest container_name: worklenz_frontend ports: - - "4200:4200" + - "5000:5000" depends_on: backend: condition: service_started networks: - - worklenz + - worklenz backend: - image: ghcr.io/worklenz/worklenz-backend - build: - context: ./worklenz-backend - dockerfile: Dockerfile + image: docker.io/kithceydigital/worklenz_backend:react container_name: worklenz_backend ports: - "3000:3000" depends_on: db: condition: service_healthy + minio: + condition: service_healthy environment: - - ANGULAR_DIST_DIR - - ANGULAR_SRC_DIR - - AWS_REGION + - AWS_REGION=${AWS_REGION:-us-east-1} - BACKEND_PUBLIC_DIR - BACKEND_VIEWS_DIR - COMMIT_BUILD_IMMEDIATELY - COOKIE_SECRET - - DB_HOST + - DB_HOST=${DB_HOST:-db} - DB_MAX_CLIENTS - - DB_NAME - - DB_PASSWORD - - DB_PORT - - DB_USER + - DB_NAME=${DB_NAME:-worklenz_db} + - DB_PASSWORD=${DB_PASSWORD:-password} + - DB_PORT=${DB_PORT:-5432} + - DB_USER=${DB_USER:-postgres} - GOOGLE_CALLBACK_URL - GOOGLE_CLIENT_ID - GOOGLE_CLIENT_SECRET - HOSTNAME - LOGIN_FAILURE_REDIRECT - - NODE_ENV - - PORT + - NODE_ENV=${NODE_ENV:-development} + - PORT=${PORT:-3000} - SESSION_NAME - SESSION_SECRET - SLACK_WEBHOOK - SOCKET_IO_CORS - SOURCE_EMAIL - USE_PG_NATIVE - - BUCKET - - REGION - - S3_URL - - S3_ACCESS_KEY_ID - - S3_SECRET_ACCESS_KEY + - STORAGE_PROVIDER=${STORAGE_PROVIDER:-s3} + - AWS_BUCKET=${BUCKET:-worklenz-bucket} + - AWS_ACCESS_KEY_ID=${S3_ACCESS_KEY_ID:-minioadmin} + - AWS_SECRET_ACCESS_KEY=${S3_SECRET_ACCESS_KEY:-minioadmin} + - S3_URL=${S3_URL:-http://minio:9000} networks: - - worklenz + - worklenz + + minio: + image: minio/minio:latest + container_name: worklenz_minio + ports: + - "9000:9000" + - "9001:9001" + environment: + MINIO_ROOT_USER: ${S3_ACCESS_KEY_ID:-minioadmin} + MINIO_ROOT_PASSWORD: ${S3_SECRET_ACCESS_KEY:-minioadmin} + volumes: + - worklenz_minio_data:/data + command: server /data --console-address ":9001" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 10s + timeout: 5s + retries: 3 + start_period: 10s + networks: + - worklenz + + # MinIO setup helper - creates default bucket on startup + createbuckets: + image: minio/mc + container_name: worklenz_createbuckets + depends_on: + minio: + condition: service_healthy + entrypoint: > + /bin/sh -c " + /usr/bin/mc config host add myminio http://minio:9000 minioadmin minioadmin; + /usr/bin/mc mb --ignore-existing myminio/worklenz-bucket; + /usr/bin/mc policy set public myminio/worklenz-bucket; + exit 0; + " + networks: + - worklenz db: image: postgres:15 container_name: worklenz_db environment: - POSTGRES_DB: "${DB_NAME}" - POSTGRES_PASSWORD: "${DB_PASSWORD}" + POSTGRES_USER: ${DB_USER:-postgres} + POSTGRES_DB: ${DB_NAME:-worklenz_db} + POSTGRES_PASSWORD: ${DB_PASSWORD:-password} healthcheck: - test: ["CMD-SHELL", "pg_isready -d ${DB_NAME} -U ${DB_USER}"] + test: [ "CMD-SHELL", "pg_isready -d ${DB_NAME:-worklenz_db} -U ${DB_USER:-postgres}" ] interval: 10s timeout: 5s retries: 5 networks: - - worklenz + - worklenz volumes: - worklenz_postgres_data:/var/lib/postgresql/data - ./worklenz-backend/database/:/docker-entrypoint-initdb.d volumes: worklenz_postgres_data: + worklenz_minio_data: networks: - worklenz: \ No newline at end of file + worklenz: diff --git a/start.bat b/start.bat new file mode 100644 index 00000000..63dd1022 --- /dev/null +++ b/start.bat @@ -0,0 +1,69 @@ +@echo off +echo. +echo " __ __ _ _" +echo " \ \ / / | | | |" +echo " \ \ /\ / /__ _ __| | _| | ___ _ __ ____" +echo " \ \/ \/ / _ \| '__| |/ / |/ _ \ '_ \|_ /" +echo " \ /\ / (_) | | | <| | __/ | | |/ /" +echo " \/ \/ \___/|_| |_|\_\_|\___|_| |_/___|" +echo. +echo W O R K L E N Z +echo. +echo Starting Worklenz Docker Environment... +echo. + +REM Check if .env file exists +IF NOT EXIST .env ( + echo Warning: .env file not found. Using default configuration. + IF EXIST .env.example ( + copy .env.example .env + echo Created .env file from .env.example + ) +) + +REM Stop any running containers +docker-compose down + +REM Start the containers +docker-compose up -d + +REM Wait for services to be ready +echo Waiting for services to start... +timeout /t 5 /nobreak > nul + +REM Check if services are running +docker ps | findstr "worklenz_frontend" > nul +IF %ERRORLEVEL% EQU 0 ( + echo [92m^✓[0m Frontend is running + echo Frontend URL: http://localhost:5000 +) ELSE ( + echo [91m^✗[0m Frontend service failed to start +) + +docker ps | findstr "worklenz_backend" > nul +IF %ERRORLEVEL% EQU 0 ( + echo [92m^✓[0m Backend is running + echo Backend URL: http://localhost:3000 +) ELSE ( + echo [91m^✗[0m Backend service failed to start +) + +docker ps | findstr "worklenz_minio" > nul +IF %ERRORLEVEL% EQU 0 ( + echo [92m^✓[0m MinIO is running + echo MinIO Console URL: http://localhost:9001 (login: minioadmin/minioadmin) +) ELSE ( + echo [91m^✗[0m MinIO service failed to start +) + +docker ps | findstr "worklenz_db" > nul +IF %ERRORLEVEL% EQU 0 ( + echo [92m^✓[0m Database is running +) ELSE ( + echo [91m^✗[0m Database service failed to start +) + +echo. +echo [92mWorklenz is now running![0m +echo You can access the application at: http://localhost:5000 +echo To stop the services, run: stop.bat \ No newline at end of file diff --git a/start.sh b/start.sh new file mode 100644 index 00000000..5720cf61 --- /dev/null +++ b/start.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +# Colors for terminal output +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Print banner +echo -e "${GREEN}" +echo " __ __ _ _" +echo " \ \ / / | | | |" +echo " \ \ /\ / /__ _ __| | _| | ___ _ __ ____" +echo " \ \/ \/ / _ \| '__| |/ / |/ _ \ '_ \|_ /" +echo " \ /\ / (_) | | | <| | __/ | | |/ /" +echo " \/ \/ \___/|_| |_|\_\_|\___|_| |_/___|" +echo "" +echo " W O R K L E N Z " +echo -e "${NC}" +echo "Starting Worklenz Docker Environment..." + +# Check if .env file exists +if [ ! -f .env ]; then + echo -e "${YELLOW}Warning: .env file not found. Using default configuration.${NC}" + # Copy the example .env file if it exists + if [ -f .env.example ]; then + cp .env.example .env + echo "Created .env file from .env.example" + fi +fi + +# Check if Docker is installed +if ! command -v docker &> /dev/null; then + echo "Error: Docker is not installed or not in PATH" + echo "Please install Docker first: https://docs.docker.com/get-docker/" + exit 1 +fi + +# Check if Docker Compose is installed +if ! command -v docker compose &> /dev/null; then + echo "Warning: Docker Compose V2 not found, trying docker-compose command..." + if ! command -v docker-compose &> /dev/null; then + echo "Error: Docker Compose is not installed or not in PATH" + echo "Please install Docker Compose: https://docs.docker.com/compose/install/" + exit 1 + fi + # Use docker-compose command instead + docker-compose down + docker-compose up -d +else + # Use Docker Compose V2 + docker compose down + docker compose up -d +fi + +# Wait for services to be ready +echo "Waiting for services to start..." +sleep 5 + +# Check if services are running +if docker ps | grep -q "worklenz_frontend"; then + echo -e "${GREEN}✓${NC} Frontend is running" + FRONTEND_URL="http://localhost:5000" + echo " Frontend URL: $FRONTEND_URL" +else + echo "✗ Frontend service failed to start" +fi + +if docker ps | grep -q "worklenz_backend"; then + echo -e "${GREEN}✓${NC} Backend is running" + BACKEND_URL="http://localhost:3000" + echo " Backend URL: $BACKEND_URL" +else + echo "✗ Backend service failed to start" +fi + +if docker ps | grep -q "worklenz_minio"; then + echo -e "${GREEN}✓${NC} MinIO is running" + MINIO_URL="http://localhost:9001" + echo " MinIO Console URL: $MINIO_URL (login: minioadmin/minioadmin)" +else + echo "✗ MinIO service failed to start" +fi + +if docker ps | grep -q "worklenz_db"; then + echo -e "${GREEN}✓${NC} Database is running" +else + echo "✗ Database service failed to start" +fi + +echo -e "\n${GREEN}Worklenz is now running!${NC}" +echo "You can access the application at: http://localhost:5000" +echo "To stop the services, run: docker compose down" \ No newline at end of file diff --git a/stop.bat b/stop.bat new file mode 100644 index 00000000..c506d37b --- /dev/null +++ b/stop.bat @@ -0,0 +1,7 @@ +@echo off +echo [91mStopping Worklenz Docker Environment...[0m + +REM Stop the containers +docker-compose down + +echo [92mWorklenz services have been stopped.[0m \ No newline at end of file diff --git a/stop.sh b/stop.sh new file mode 100644 index 00000000..2923c831 --- /dev/null +++ b/stop.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Colors for terminal output +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +echo -e "${RED}Stopping Worklenz Docker Environment...${NC}" + +# Check which Docker Compose command to use +if command -v docker compose &> /dev/null; then + # Docker Compose V2 + docker compose down +else + # Legacy Docker Compose + docker-compose down +fi + +echo -e "${GREEN}Worklenz services have been stopped.${NC}" \ No newline at end of file diff --git a/worklenz-backend/.env.example b/worklenz-backend/.env.example new file mode 100644 index 00000000..9ed46239 --- /dev/null +++ b/worklenz-backend/.env.example @@ -0,0 +1,67 @@ +# Server +NODE_ENV=development +PORT=3000 +SESSION_NAME=worklenz.sid +SESSION_SECRET="your-session-secret" +COOKIE_SECRET="your-cookie-secret" + +# CORS +SOCKET_IO_CORS=http://localhost:4200 +SERVER_CORS=* + +# Database +DB_USER=postgres +DB_PASSWORD=password +DB_NAME=worklenz_db +DB_HOST=localhost +DB_PORT=5432 +DB_MAX_CLIENTS=50 + +# Google Login +GOOGLE_CLIENT_ID="client_id" +GOOGLE_CLIENT_SECRET="client_secret" +GOOGLE_CALLBACK_URL="http://localhost:3000/secure/google/verify" +LOGIN_FAILURE_REDIRECT="/" +LOGIN_SUCCESS_REDIRECT="http://localhost:4200/auth/authenticate" + +# SENDGRID +SENDGRID_API_KEY="your-sendgrid-api-key" +EMAIL_NOTIFICATIONS=your-email@example.com + +# HOST +HOSTNAME=localhost:4200 + +SLACK_WEBHOOK=your-slack-webhook-url +USE_PG_NATIVE=true + +# JWT SECRET +JWT_SECRET=your-jwt-secret + +# FRONTEND_URL +FRONTEND_URL=https://example.com/ + +# AWS +AWS_REGION="us-west-2" +AWS_ACCESS_KEY_ID="YOUR_AWS_ACCESS_KEY_ID" +AWS_SECRET_ACCESS_KEY="YOUR_AWS_SECRET_ACCESS_KEY" +AWS_BUCKET="your-s3-bucket" +S3_URL="https://s3.your-region.amazonaws.com/your-bucket" + +# STORAGE +STORAGE_PROVIDER=s3 # s3 or azure +AZURE_STORAGE_ACCOUNT_NAME=yourstorageaccount +AZURE_STORAGE_CONTAINER=yourcontainer +AZURE_STORAGE_ACCOUNT_KEY=yourstorageaccountkey +AZURE_STORAGE_URL=https://yourstorageaccount.blob.core.windows.net + +# DIRECTPAY +DP_STAGE=DEV +DP_URL=https://dev.directpay.lk/v1/mpg/api/external/cardManagement +DP_MERCHANT_ID=YOUR_MERCHANT_ID +DP_SECRET_KEY=YOUR_SECRET_KEY +DP_API_KEY=YOUR_API_KEY + +CONTACT_US_EMAIL=support@example.com + +GOOGLE_CAPTCHA_SECRET_KEY=YOUR_SECRET_KEY +GOOGLE_CAPTCHA_PASS_SCORE=0.8 \ No newline at end of file diff --git a/worklenz-backend/.env.template b/worklenz-backend/.env.template index 85db9afa..18aa2d3f 100644 --- a/worklenz-backend/.env.template +++ b/worklenz-backend/.env.template @@ -36,22 +36,40 @@ HOSTNAME=localhost:4200 # SLACK SLACK_WEBHOOK=SLACK_WEBHOOK_HERE -USE_PG_NATIVE=true +USE_PG_NATIVE=false # JWT SECRET -JWT_SECRET=JWT_SECRET_CODE_HERE +JWT_SECRET=JWT_SECRET_HERE + +# FRONTEND_URL +FRONTEND_URL=FRONTEND_URL_HERE + +# STORAGE +STORAGE_PROVIDER=STORAGE_PROVIDER_HERE # values s3 or azure, if s3 is selected, then the following AWS credentials are required. if azure is selected, then the following Azure credentials are required. # AWS -AWS_REGION="us-west-2" +AWS_REGION="AWS_REGION_HERE" AWS_ACCESS_KEY_ID="AWS_ACCESS_KEY_ID_HERE" AWS_SECRET_ACCESS_KEY="AWS_SECRET_ACCESS_KEY_HERE" - -# S3 Credentials -REGION="us-west-2" -BUCKET="BUCKET_NAME_HERE" +AWS_BUCKET="AWS_BUCKET_HERE" S3_URL="S3_URL_HERE" -S3_ACCESS_KEY_ID="S3_ACCESS_KEY_ID_HERE" -S3_SECRET_ACCESS_KEY="S3_SECRET_ACCESS_KEY_HERE" -# SES email -SOURCE_EMAIL="SOURCE_EMAIL_HERE" #Worklenz +# STORAGE +AZURE_STORAGE_ACCOUNT_NAME="AZURE_STORAGE_ACCOUNT_NAME_HERE" +AZURE_STORAGE_CONTAINER="AZURE_STORAGE_CONTAINER_HERE" +AZURE_STORAGE_ACCOUNT_KEY="AZURE_STORAGE_ACCOUNT_KEY_HERE" +AZURE_STORAGE_URL="AZURE_STORAGE_URL_HERE" + +# DIRECTPAY +DP_STAGE=DP_STAGE_HERE #DEV or +DP_URL=DP_URL_HERE +DP_MERCHANT_ID=DP_MERCHANT_ID_HERE +DP_SECRET_KEY=DP_SECRET_KEY_HERE +DP_API_KEY=DP_API_KEY_HERE + +CONTACT_US_EMAIL=CONTACT_US_EMAIL_HERE + +GOOGLE_CAPTCHA_SECRET_KEY=GOOGLE_CAPTCHA_SECRET_KEY_HERE + +# Email Cronjobs +ENABLE_EMAIL_CRONJOBS=true \ No newline at end of file diff --git a/worklenz-backend/Dockerfile b/worklenz-backend/Dockerfile index c0ae5385..f0815533 100644 --- a/worklenz-backend/Dockerfile +++ b/worklenz-backend/Dockerfile @@ -23,4 +23,4 @@ RUN npm run build EXPOSE 3000 # Start the application -CMD ["npm", "start"] +CMD ["npm", "start"] \ No newline at end of file diff --git a/worklenz-backend/database/1_tables.sql b/worklenz-backend/database/1_tables.sql index 7a5c3057..07ac36d6 100644 --- a/worklenz-backend/database/1_tables.sql +++ b/worklenz-backend/database/1_tables.sql @@ -1,5 +1,3 @@ --- CREATE DATABASE worklenz_db; - -- Extensions CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "unaccent"; @@ -10,55 +8,18 @@ CREATE DOMAIN WL_EMAIL AS TEXT CHECK (value ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-] -- Enumerated Types -- Add new values using "ALTER TYPE WL_TASK_LIST_COL_KEY ADD VALUE 'NEW_VALUE_NAME' AFTER 'REPORTER';" -CREATE TYPE WL_TASK_LIST_COL_KEY AS ENUM ('ASSIGNEES', 'COMPLETED_DATE', 'CREATED_DATE', 'DESCRIPTION', 'DUE_DATE', 'ESTIMATION', 'KEY', 'LABELS', 'LAST_UPDATED', 'NAME', 'PRIORITY', 'PROGRESS', 'START_DATE', 'STATUS', 'TIME_TRACKING', 'REPORTER', 'PHASE'); +CREATE TYPE WL_TASK_LIST_COL_KEY AS ENUM ('ASSIGNEES', 'COMPLETED_DATE', 'CREATED_DATE', 'DESCRIPTION', 'DUE_DATE', 'ESTIMATION', 'KEY', 'LABELS', 'LAST_UPDATED', 'NAME', 'PRIORITY', 'PROGRESS', 'START_DATE', 'STATUS', 'TIME_TRACKING', 'REPORTER'); +CREATE TYPE REACTION_TYPES AS ENUM ('like'); -CREATE TABLE archived_projects ( - user_id UUID NOT NULL, - project_id UUID NOT NULL -); +CREATE TYPE DEPENDENCY_TYPE AS ENUM ('blocked_by'); -ALTER TABLE archived_projects - ADD CONSTRAINT archived_projects_pk - PRIMARY KEY (user_id, project_id); +CREATE TYPE SCHEDULE_TYPE AS ENUM ('daily', 'weekly', 'yearly', 'monthly', 'every_x_days', 'every_x_weeks', 'every_x_months'); -CREATE TABLE bounced_emails ( - id UUID DEFAULT uuid_generate_v4() NOT NULL, - email TEXT NOT NULL, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL -); +CREATE TYPE LANGUAGE_TYPE AS ENUM ('en', 'es', 'pt'); -CREATE UNIQUE INDEX bounced_emails_email_uindex - ON bounced_emails (email); - -ALTER TABLE bounced_emails - ADD CONSTRAINT bounced_emails_pk - PRIMARY KEY (id); - -CREATE TABLE clients ( - id UUID DEFAULT uuid_generate_v4() NOT NULL, - name TEXT NOT NULL, - team_id UUID NOT NULL, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL -); - -CREATE INDEX clients_id_team_id_index - ON clients (id, team_id); - -CREATE UNIQUE INDEX clients_name_team_id_uindex - ON clients (name, team_id); - -ALTER TABLE clients - ADD CONSTRAINT clients_pk - PRIMARY KEY (id); - -ALTER TABLE clients - ADD CONSTRAINT clients_name_check - CHECK (CHAR_LENGTH(name) <= 60); - -CREATE TABLE countries ( +-- Utility and referenced tables +CREATE TABLE IF NOT EXISTS countries ( id UUID DEFAULT uuid_generate_v4() NOT NULL, code CHAR(2) NOT NULL, name VARCHAR(150) NOT NULL, @@ -69,7 +30,55 @@ CREATE TABLE countries ( ALTER TABLE countries ADD PRIMARY KEY (id); -CREATE TABLE cpt_phases ( +CREATE TABLE IF NOT EXISTS permissions ( + id TEXT NOT NULL, + name TEXT NOT NULL, + description TEXT NOT NULL +); + +ALTER TABLE permissions + ADD CONSTRAINT permissions_pk + PRIMARY KEY (id); + +-- Tables that reference utility tables +CREATE TABLE IF NOT EXISTS archived_projects ( + user_id UUID NOT NULL, + project_id UUID NOT NULL +); + +ALTER TABLE archived_projects + ADD CONSTRAINT archived_projects_pk + PRIMARY KEY (user_id, project_id); + +CREATE TABLE IF NOT EXISTS bounced_emails ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + email TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL +); + +ALTER TABLE bounced_emails + ADD CONSTRAINT bounced_emails_pk + PRIMARY KEY (id); + +CREATE TABLE IF NOT EXISTS clients ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + name TEXT NOT NULL, + team_id UUID NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL +); + +ALTER TABLE clients + ADD CONSTRAINT clients_pk + PRIMARY KEY (id); + +ALTER TABLE clients + ADD CONSTRAINT clients_name_check + CHECK (CHAR_LENGTH(name) <= 60); + +-- Remaining tables +CREATE TABLE IF NOT EXISTS cpt_phases ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, color_code WL_HEX_COLOR NOT NULL, @@ -77,14 +86,11 @@ CREATE TABLE cpt_phases ( created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL ); -CREATE UNIQUE INDEX cpt_phases_name_project_uindex - ON cpt_phases (name, template_id); - ALTER TABLE cpt_phases ADD CONSTRAINT cpt_phases_pk PRIMARY KEY (id); -CREATE TABLE cpt_task_labels ( +CREATE TABLE IF NOT EXISTS cpt_task_labels ( task_id UUID NOT NULL, label_id UUID NOT NULL ); @@ -93,23 +99,17 @@ ALTER TABLE cpt_task_labels ADD CONSTRAINT cpt_task_labels_pk PRIMARY KEY (task_id, label_id); -CREATE TABLE cpt_task_phases ( +CREATE TABLE IF NOT EXISTS cpt_task_phases ( task_id UUID NOT NULL, phase_id UUID NOT NULL ); -CREATE UNIQUE INDEX cpt_task_phase_cpt_task_phase_uindex - ON cpt_task_phases (task_id, phase_id); - -CREATE UNIQUE INDEX cpt_task_phase_task_id_uindex - ON cpt_task_phases (task_id); - ALTER TABLE cpt_task_phases ADD CONSTRAINT cpt_task_phase_phase_id_fk FOREIGN KEY (phase_id) REFERENCES cpt_phases ON DELETE CASCADE; -CREATE TABLE cpt_task_statuses ( +CREATE TABLE IF NOT EXISTS cpt_task_statuses ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, template_id UUID NOT NULL, @@ -118,14 +118,11 @@ CREATE TABLE cpt_task_statuses ( sort_order INTEGER DEFAULT 0 NOT NULL ); -CREATE UNIQUE INDEX cpt_task_statuses_template_id_name_uindex - ON cpt_task_statuses (template_id, name); - ALTER TABLE cpt_task_statuses ADD CONSTRAINT cpt_task_statuses_pk PRIMARY KEY (id); -CREATE TABLE cpt_tasks ( +CREATE TABLE IF NOT EXISTS cpt_tasks ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, description TEXT, @@ -175,7 +172,7 @@ ALTER TABLE cpt_tasks ADD CONSTRAINT cpt_tasks_total_minutes_check CHECK ((total_minutes >= (0)::NUMERIC) AND (total_minutes <= (999999)::NUMERIC)); -CREATE TABLE custom_project_templates ( +CREATE TABLE IF NOT EXISTS custom_project_templates ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, phase_label TEXT DEFAULT 'Phase'::TEXT NOT NULL, @@ -186,9 +183,6 @@ CREATE TABLE custom_project_templates ( notes TEXT ); -CREATE UNIQUE INDEX custom_project_templates_name_team_id_uindex - ON custom_project_templates (name, team_id); - ALTER TABLE custom_project_templates ADD CONSTRAINT custom_project_templates_pk PRIMARY KEY (id); @@ -208,7 +202,23 @@ ALTER TABLE cpt_tasks FOREIGN KEY (template_id) REFERENCES custom_project_templates ON DELETE CASCADE; -CREATE TABLE email_invitations ( +CREATE OR REPLACE FUNCTION lower_email() RETURNS trigger + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + + IF (is_null_or_empty(NEW.email) IS FALSE) + THEN + NEW.email = LOWER(TRIM(NEW.email)); + END IF; + + RETURN NEW; +END +$$; + +CREATE TABLE IF NOT EXISTS email_invitations ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, email WL_EMAIL NOT NULL, @@ -222,7 +232,12 @@ ALTER TABLE email_invitations ADD CONSTRAINT email_invitations_pk PRIMARY KEY (id); -CREATE TABLE favorite_projects ( +CREATE TRIGGER email_invitations_email_lower + BEFORE INSERT OR UPDATE + ON email_invitations +EXECUTE PROCEDURE lower_email(); + +CREATE TABLE IF NOT EXISTS favorite_projects ( user_id UUID NOT NULL, project_id UUID NOT NULL ); @@ -231,18 +246,12 @@ ALTER TABLE favorite_projects ADD CONSTRAINT favorite_projects_pk PRIMARY KEY (user_id, project_id); -CREATE TABLE job_titles ( +CREATE TABLE IF NOT EXISTS job_titles ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, team_id UUID NOT NULL ); -CREATE UNIQUE INDEX job_titles_name_team_id_uindex - ON job_titles (name, team_id); - -CREATE INDEX job_titles_team_id_index - ON job_titles (team_id); - ALTER TABLE job_titles ADD CONSTRAINT job_titles_pk PRIMARY KEY (id); @@ -251,7 +260,247 @@ ALTER TABLE job_titles ADD CONSTRAINT job_titles_name_check CHECK (CHAR_LENGTH(name) <= 55); -CREATE TABLE notification_settings ( +CREATE TABLE IF NOT EXISTS licensing_coupon_codes ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + coupon_code TEXT NOT NULL, + is_redeemed BOOLEAN DEFAULT FALSE, + is_app_sumo BOOLEAN DEFAULT FALSE, + projects_limit INTEGER, + team_members_limit INTEGER DEFAULT 3, + storage_limit INTEGER DEFAULT 5, + redeemed_by UUID, + batch_id UUID, + created_by UUID NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + redeemed_at TIMESTAMP WITH TIME ZONE, + is_refunded BOOLEAN DEFAULT FALSE, + reason TEXT, + feedback TEXT, + refunded_at TIMESTAMP WITH TIME ZONE +); + +ALTER TABLE licensing_coupon_codes + ADD CONSTRAINT licensing_coupon_codes_pk + PRIMARY KEY (id); + +ALTER TABLE licensing_coupon_codes + ADD CONSTRAINT licensing_coupon_codes_app_sumo_batches__fk + FOREIGN KEY (batch_id) REFERENCES licensing_app_sumo_batches + ON DELETE CASCADE; + +ALTER TABLE licensing_coupon_codes + ADD CONSTRAINT licensing_coupon_codes_created_by_fk + FOREIGN KEY (created_by) REFERENCES licensing_admin_users; + +CREATE TABLE IF NOT EXISTS licensing_credit_subs ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + next_plan_id UUID NOT NULL, + user_id UUID NOT NULL, + credit_given NUMERIC NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + created_by UUID NOT NULL, + checkout_url TEXT, + credit_balance NUMERIC DEFAULT 0 +); + +ALTER TABLE licensing_credit_subs + ADD CONSTRAINT licensing_credit_subs_pk + PRIMARY KEY (id); + +ALTER TABLE licensing_credit_subs + ADD CONSTRAINT licensing_credit_subs_created_by_fk + FOREIGN KEY (created_by) REFERENCES licensing_admin_users; + +CREATE TABLE IF NOT EXISTS licensing_custom_subs ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + user_id UUID NOT NULL, + billing_type TEXT DEFAULT 'year'::CHARACTER VARYING NOT NULL, + currency TEXT DEFAULT 'LKR'::CHARACTER VARYING NOT NULL, + rate NUMERIC DEFAULT 0 NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + end_date DATE NOT NULL, + user_limit INTEGER +); + +ALTER TABLE licensing_custom_subs + ADD PRIMARY KEY (id); + +CREATE TABLE IF NOT EXISTS licensing_custom_subs_logs ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + subscription_id UUID NOT NULL, + log_text TEXT NOT NULL, + description TEXT, + admin_user_id UUID NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL +); + +ALTER TABLE licensing_custom_subs_logs + ADD PRIMARY KEY (id); + +ALTER TABLE licensing_custom_subs_logs + ADD CONSTRAINT licensing_custom_subs_logs_licensing_admin_users_id_fk + FOREIGN KEY (admin_user_id) REFERENCES licensing_admin_users; + +CREATE TABLE IF NOT EXISTS licensing_payment_details ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + user_id UUID, + alert_id TEXT NOT NULL, + alert_name TEXT NOT NULL, + balance_currency TEXT DEFAULT 'USD'::TEXT, + balance_earnings NUMERIC DEFAULT 0 NOT NULL, + balance_fee NUMERIC DEFAULT 0 NOT NULL, + balance_gross NUMERIC DEFAULT 0 NOT NULL, + balance_tax NUMERIC DEFAULT 0 NOT NULL, + checkout_id TEXT NOT NULL, + country TEXT NOT NULL, + coupon TEXT NOT NULL, + currency TEXT DEFAULT 'USD'::TEXT NOT NULL, + custom_data TEXT, + customer_name TEXT NOT NULL, + earnings NUMERIC DEFAULT 0 NOT NULL, + email TEXT NOT NULL, + event_time TEXT NOT NULL, + fee NUMERIC DEFAULT 0 NOT NULL, + initial_payment NUMERIC DEFAULT 1 NOT NULL, + instalments NUMERIC DEFAULT 1 NOT NULL, + marketing_consent INTEGER DEFAULT 0, + next_bill_date DATE NOT NULL, + next_payment_amount NUMERIC DEFAULT 0 NOT NULL, + order_id TEXT NOT NULL, + p_signature TEXT NOT NULL, + passthrough TEXT, + payment_method TEXT DEFAULT 'card'::TEXT NOT NULL, + payment_tax NUMERIC DEFAULT 0, + plan_name TEXT NOT NULL, + quantity NUMERIC DEFAULT 0 NOT NULL, + receipt_url TEXT NOT NULL, + sale_gross TEXT DEFAULT 0 NOT NULL, + status TEXT NOT NULL, + subscription_id TEXT NOT NULL, + subscription_payment_id TEXT NOT NULL, + subscription_plan_id INTEGER, + unit_price NUMERIC DEFAULT 0 NOT NULL, + paddle_user_id TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + payment_status TEXT DEFAULT 'success'::TEXT NOT NULL +); + +ALTER TABLE licensing_payment_details + ADD PRIMARY KEY (id); + +CREATE TABLE IF NOT EXISTS licensing_pricing_plans ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + name TEXT DEFAULT ''::TEXT NOT NULL, + billing_type TEXT DEFAULT 'month'::TEXT NOT NULL, + billing_period INTEGER DEFAULT 1 NOT NULL, + default_currency TEXT DEFAULT 'USD'::TEXT NOT NULL, + initial_price TEXT DEFAULT '0'::TEXT NOT NULL, + recurring_price TEXT DEFAULT '0'::TEXT NOT NULL, + trial_days INTEGER DEFAULT 0 NOT NULL, + paddle_id INTEGER DEFAULT 0, + active BOOLEAN DEFAULT FALSE NOT NULL, + is_startup_plan BOOLEAN DEFAULT FALSE NOT NULL +); + +ALTER TABLE licensing_pricing_plans + ADD CONSTRAINT licensing_pricing_plans_pk + PRIMARY KEY (id); + +ALTER TABLE licensing_credit_subs + ADD CONSTRAINT licensing_credit_subs_next_plan_id_fk + FOREIGN KEY (next_plan_id) REFERENCES licensing_pricing_plans; + +ALTER TABLE licensing_pricing_plans + ADD UNIQUE (paddle_id); + +ALTER TABLE licensing_payment_details + ADD CONSTRAINT licensing_payment_details_licensing_pricing_plans_paddle_id_fk + FOREIGN KEY (subscription_plan_id) REFERENCES licensing_pricing_plans (paddle_id); + +ALTER TABLE licensing_pricing_plans + ADD CONSTRAINT billing_type_allowed + CHECK (billing_type = ANY (ARRAY ['month'::TEXT, 'year'::TEXT])); + +CREATE TABLE IF NOT EXISTS licensing_settings ( + default_trial_storage NUMERIC DEFAULT 1 NOT NULL, + default_storage NUMERIC DEFAULT 25 NOT NULL, + storage_addon_price NUMERIC DEFAULT 0 NOT NULL, + storage_addon_size NUMERIC DEFAULT 0, + default_monthly_plan UUID, + default_annual_plan UUID, + default_startup_plan UUID, + projects_limit INTEGER DEFAULT 5 NOT NULL, + team_member_limit INTEGER DEFAULT 0 NOT NULL, + free_tier_storage INTEGER DEFAULT 5 NOT NULL, + trial_duration INTEGER DEFAULT 14 NOT NULL +); + +COMMENT ON COLUMN licensing_settings.default_trial_storage IS 'default storage amount for a trial in Gigabytes(GB)'; + +COMMENT ON COLUMN licensing_settings.default_storage IS 'default storage amount for a paid account in Gigabytes(GB)'; + +ALTER TABLE licensing_settings + ADD CONSTRAINT licensing_settings_licensing_pricing_plans_id_fk + FOREIGN KEY (default_startup_plan) REFERENCES licensing_pricing_plans; + +ALTER TABLE licensing_settings + ADD CONSTRAINT licensing_settings_licensing_user_plans_id_fk + FOREIGN KEY (default_monthly_plan) REFERENCES licensing_pricing_plans; + +ALTER TABLE licensing_settings + ADD CONSTRAINT licensing_settings_licensing_user_plans_id_fk_2 + FOREIGN KEY (default_annual_plan) REFERENCES licensing_pricing_plans; + +CREATE TABLE IF NOT EXISTS licensing_user_subscription_modifiers ( + subscription_id INTEGER NOT NULL, + modifier_id INTEGER NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL +); + +CREATE TABLE IF NOT EXISTS licensing_user_subscriptions ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + user_id UUID NOT NULL, + paddle_user_id INTEGER, + cancel_url TEXT, + update_url TEXT, + checkout_id TEXT, + next_bill_date TEXT, + quantity INTEGER DEFAULT 1 NOT NULL, + subscription_id INTEGER, + subscription_plan_id INTEGER, + unit_price NUMERIC, + plan_id UUID NOT NULL, + status TEXT, + custom_value_month NUMERIC DEFAULT 0 NOT NULL, + custom_value_year NUMERIC DEFAULT 0 NOT NULL, + custom_storage_amount NUMERIC DEFAULT 0 NOT NULL, + custom_storage_unit TEXT DEFAULT 'MB'::TEXT NOT NULL, + cancellation_effective_date DATE, + currency TEXT DEFAULT 'USD'::TEXT NOT NULL, + event_time TEXT, + paused_at TEXT, + paused_from TEXT, + paused_reason TEXT, + active BOOLEAN DEFAULT TRUE +); + +ALTER TABLE licensing_user_subscriptions + ADD CONSTRAINT licensing_user_plans_pk + PRIMARY KEY (id); + +ALTER TABLE licensing_user_subscriptions + ADD UNIQUE (subscription_id); + +ALTER TABLE licensing_user_subscriptions + ADD CONSTRAINT licensing_user_subscriptions_licensing_pricing_plans_id_fk + FOREIGN KEY (plan_id) REFERENCES licensing_pricing_plans; + +ALTER TABLE licensing_user_subscriptions + ADD CONSTRAINT licensing_user_subscriptions_statuses_allowed + CHECK (status = ANY + (ARRAY ['active'::TEXT, 'past_due'::TEXT, 'trialing'::TEXT, 'paused'::TEXT, 'deleted'::TEXT])); + +CREATE TABLE IF NOT EXISTS notification_settings ( email_notifications_enabled BOOLEAN DEFAULT TRUE NOT NULL, popup_notifications_enabled BOOLEAN DEFAULT TRUE NOT NULL, show_unread_items_count BOOLEAN DEFAULT TRUE NOT NULL, @@ -260,14 +509,11 @@ CREATE TABLE notification_settings ( team_id UUID NOT NULL ); -CREATE INDEX notification_settings_team_user_id_index - ON notification_settings (team_id, user_id); - ALTER TABLE notification_settings ADD CONSTRAINT notification_settings_pk PRIMARY KEY (user_id, team_id); -CREATE TABLE organizations ( +CREATE TABLE IF NOT EXISTS organizations ( id UUID DEFAULT uuid_generate_v4() NOT NULL, organization_name TEXT NOT NULL, contact_number TEXT, @@ -286,39 +532,25 @@ CREATE TABLE organizations ( user_id UUID NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - license_type_id UUID + license_type_id UUID, + is_lkr_billing BOOLEAN DEFAULT FALSE, + working_hours DOUBLE PRECISION DEFAULT 8 NOT NULL ); -CREATE UNIQUE INDEX organizations_user_id_uindex - ON organizations (user_id); - ALTER TABLE organizations ADD CONSTRAINT organizations_pk PRIMARY KEY (id); ALTER TABLE organizations - ADD CONSTRAINT users_data_countries_id_fk - FOREIGN KEY (country) REFERENCES countries; + ADD CONSTRAINT organizations_pk_2 + UNIQUE (user_id); ALTER TABLE organizations ADD CONSTRAINT subscription_statuses_allowed CHECK (subscription_status = ANY (ARRAY ['active'::TEXT, 'past_due'::TEXT, 'trialing'::TEXT, 'paused'::TEXT, 'deleted'::TEXT, 'life_time_deal'::TEXT, 'free'::TEXT, 'custom'::TEXT, 'credit'::TEXT])); -CREATE TABLE permissions ( - id TEXT NOT NULL, - name TEXT NOT NULL, - description TEXT NOT NULL -); - -CREATE UNIQUE INDEX permissions_name_uindex - ON permissions (name); - -ALTER TABLE permissions - ADD CONSTRAINT permissions_pk - PRIMARY KEY (id); - -CREATE TABLE personal_todo_list ( +CREATE TABLE IF NOT EXISTS personal_todo_list ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, description TEXT, @@ -330,9 +562,6 @@ CREATE TABLE personal_todo_list ( updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL ); -CREATE UNIQUE INDEX personal_todo_list_index_uindex - ON personal_todo_list (user_id, index); - ALTER TABLE personal_todo_list ADD CONSTRAINT personal_todo_list_pk PRIMARY KEY (id); @@ -345,32 +574,7 @@ ALTER TABLE personal_todo_list ADD CONSTRAINT personal_todo_list_name_check CHECK (CHAR_LENGTH(name) <= 100); -CREATE TABLE pg_sessions ( - sid VARCHAR NOT NULL, - sess JSON NOT NULL, - expire TIMESTAMP(6) NOT NULL -); - -ALTER TABLE pg_sessions - ADD PRIMARY KEY (sid); - -CREATE TABLE project_access_levels ( - id UUID DEFAULT uuid_generate_v4() NOT NULL, - name TEXT NOT NULL, - key TEXT NOT NULL -); - -CREATE UNIQUE INDEX project_access_levels_key_uindex - ON project_access_levels (key); - -CREATE UNIQUE INDEX project_access_levels_name_uindex - ON project_access_levels (name); - -ALTER TABLE project_access_levels - ADD CONSTRAINT project_access_levels_pk - PRIMARY KEY (id); - -CREATE TABLE project_categories ( +CREATE TABLE IF NOT EXISTS project_categories ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, color_code WL_HEX_COLOR DEFAULT '#70a6f3'::TEXT NOT NULL, @@ -380,14 +584,11 @@ CREATE TABLE project_categories ( updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL ); -CREATE UNIQUE INDEX project_categories_name_team_id_uindex - ON project_categories (name, team_id); - ALTER TABLE project_categories ADD CONSTRAINT project_categories_pk PRIMARY KEY (id); -CREATE TABLE project_comment_mentions ( +CREATE TABLE IF NOT EXISTS project_comment_mentions ( comment_id UUID NOT NULL, mentioned_index INTEGER NOT NULL, mentioned_by UUID NOT NULL, @@ -395,7 +596,7 @@ CREATE TABLE project_comment_mentions ( created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL ); -CREATE TABLE project_comments ( +CREATE TABLE IF NOT EXISTS project_comments ( id UUID DEFAULT uuid_generate_v4() NOT NULL, content TEXT NOT NULL, created_by UUID NOT NULL, @@ -404,9 +605,6 @@ CREATE TABLE project_comments ( updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL ); -CREATE INDEX project_comments_project_id_index - ON project_comments (project_id); - ALTER TABLE project_comments ADD CONSTRAINT project_comments_pk PRIMARY KEY (id); @@ -420,7 +618,7 @@ ALTER TABLE project_comments ADD CONSTRAINT project_comments_content_length_check CHECK (CHAR_LENGTH(content) <= 2000); -CREATE TABLE project_folders ( +CREATE TABLE IF NOT EXISTS project_folders ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, key TEXT NOT NULL, @@ -432,12 +630,6 @@ CREATE TABLE project_folders ( updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL ); -CREATE UNIQUE INDEX project_folders_team_id_key_uindex - ON project_folders (team_id, key); - -CREATE UNIQUE INDEX project_folders_team_id_name_uindex - ON project_folders (team_id, name); - ALTER TABLE project_folders ADD CONSTRAINT project_folders_pk PRIMARY KEY (id); @@ -446,7 +638,7 @@ ALTER TABLE project_folders ADD CONSTRAINT project_folders_parent_folder_fk FOREIGN KEY (parent_folder_id) REFERENCES project_folders; -CREATE TABLE project_logs ( +CREATE TABLE IF NOT EXISTS project_logs ( id UUID DEFAULT uuid_generate_v4() NOT NULL, team_id UUID NOT NULL, project_id UUID NOT NULL, @@ -458,19 +650,20 @@ ALTER TABLE project_logs ADD CONSTRAINT project_logs_pk PRIMARY KEY (id); -CREATE TABLE project_member_allocations ( - id UUID DEFAULT uuid_generate_v4() NOT NULL, - project_id UUID NOT NULL, - team_member_id UUID NOT NULL, - allocated_from TIMESTAMP WITH TIME ZONE NOT NULL, - allocated_to TIMESTAMP WITH TIME ZONE NOT NULL +CREATE TABLE IF NOT EXISTS project_member_allocations ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + project_id UUID NOT NULL, + team_member_id UUID NOT NULL, + allocated_from TIMESTAMP WITH TIME ZONE NOT NULL, + allocated_to TIMESTAMP WITH TIME ZONE NOT NULL, + seconds_per_day INTEGER ); ALTER TABLE project_member_allocations ADD CONSTRAINT project_member_allocations_pk PRIMARY KEY (id); -CREATE TABLE project_members ( +CREATE TABLE IF NOT EXISTS project_members ( id UUID DEFAULT uuid_generate_v4() NOT NULL, team_member_id UUID NOT NULL, project_access_level_id UUID NOT NULL, @@ -480,18 +673,6 @@ CREATE TABLE project_members ( default_view TEXT DEFAULT 'TASK_LIST'::TEXT NOT NULL ); -CREATE INDEX project_members_project_id_index - ON project_members (project_id); - -CREATE INDEX project_members_project_id_member_id_index - ON project_members (project_id, team_member_id); - -CREATE INDEX project_members_team_member_id_index - ON project_members (team_member_id); - -CREATE UNIQUE INDEX project_members_team_member_project_uindex - ON project_members (team_member_id, project_id); - ALTER TABLE project_members ADD CONSTRAINT project_members_pk PRIMARY KEY (id); @@ -500,7 +681,7 @@ ALTER TABLE project_members ADD CONSTRAINT project_members_access_level_fk FOREIGN KEY (project_access_level_id) REFERENCES project_access_levels; -CREATE TABLE project_phases ( +CREATE TABLE IF NOT EXISTS project_phases ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, color_code WL_HEX_COLOR NOT NULL, @@ -511,14 +692,11 @@ CREATE TABLE project_phases ( sort_index INTEGER DEFAULT 0 ); -CREATE UNIQUE INDEX project_phases_name_project_uindex - ON project_phases (name, project_id); - ALTER TABLE project_phases ADD CONSTRAINT project_phases_pk PRIMARY KEY (id); -CREATE TABLE project_subscribers ( +CREATE TABLE IF NOT EXISTS project_subscribers ( id UUID DEFAULT uuid_generate_v4() NOT NULL, user_id UUID NOT NULL, project_id UUID NOT NULL, @@ -526,33 +704,26 @@ CREATE TABLE project_subscribers ( created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL ); -CREATE UNIQUE INDEX project_subscribers_user_task_team_member_uindex - ON project_subscribers (user_id, project_id, team_member_id); - ALTER TABLE project_subscribers ADD CONSTRAINT project_subscribers_pk PRIMARY KEY (id); -CREATE TABLE project_task_list_cols ( - id UUID DEFAULT uuid_generate_v4() NOT NULL, - name TEXT NOT NULL, - key WL_TASK_LIST_COL_KEY NOT NULL, - index INTEGER DEFAULT 0 NOT NULL, - pinned BOOLEAN DEFAULT TRUE NOT NULL, - project_id UUID NOT NULL +CREATE TABLE IF NOT EXISTS project_task_list_cols ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + name TEXT NOT NULL, + key WL_TASK_LIST_COL_KEY NOT NULL, + index INTEGER DEFAULT 0 NOT NULL, + pinned BOOLEAN DEFAULT TRUE NOT NULL, + project_id UUID NOT NULL, + custom_column BOOLEAN DEFAULT FALSE, + custom_column_obj JSONB ); -CREATE INDEX project_task_list_cols_index - ON project_task_list_cols (project_id, index); - -CREATE UNIQUE INDEX project_task_list_cols_key_project_uindex - ON project_task_list_cols (key, project_id); - ALTER TABLE project_task_list_cols ADD CONSTRAINT project_task_list_cols_pk PRIMARY KEY (id); -CREATE TABLE projects ( +CREATE TABLE IF NOT EXISTS projects ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, key TEXT NOT NULL, @@ -576,30 +747,6 @@ CREATE TABLE projects ( estimated_working_days INTEGER DEFAULT 0 ); -CREATE INDEX projects_folder_id_index - ON projects (folder_id); - -CREATE INDEX projects_id_team_id_index - ON projects (id, team_id); - -CREATE UNIQUE INDEX projects_key_team_id_uindex - ON projects (key, team_id); - -CREATE INDEX projects_name_index - ON projects (name); - -CREATE UNIQUE INDEX projects_name_team_id_uindex - ON projects (name, team_id); - -CREATE INDEX projects_team_id_folder_id_index - ON projects (team_id, folder_id); - -CREATE INDEX projects_team_id_index - ON projects (team_id); - -CREATE INDEX projects_team_id_name_index - ON projects (team_id, name); - ALTER TABLE projects ADD CONSTRAINT projects_pk PRIMARY KEY (id); @@ -672,7 +819,7 @@ ALTER TABLE projects ADD CONSTRAINT projects_notes_check CHECK (CHAR_LENGTH(notes) <= 500); -CREATE TABLE pt_labels ( +CREATE TABLE IF NOT EXISTS pt_labels ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, color_code TEXT NOT NULL, @@ -683,7 +830,7 @@ ALTER TABLE pt_labels ADD CONSTRAINT pt_project_templates_labels_pk PRIMARY KEY (id); -CREATE TABLE pt_phases ( +CREATE TABLE IF NOT EXISTS pt_phases ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, color_code TEXT, @@ -694,7 +841,7 @@ ALTER TABLE pt_phases ADD CONSTRAINT pt_project_template_phases_pk PRIMARY KEY (id); -CREATE TABLE pt_project_templates ( +CREATE TABLE IF NOT EXISTS pt_project_templates ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, key TEXT NOT NULL, @@ -721,7 +868,7 @@ ALTER TABLE pt_project_templates ADD CONSTRAINT pt_project_templates_key_unique UNIQUE (key); -CREATE TABLE pt_statuses ( +CREATE TABLE IF NOT EXISTS pt_statuses ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, template_id UUID NOT NULL, @@ -737,7 +884,7 @@ ALTER TABLE pt_statuses FOREIGN KEY (template_id) REFERENCES pt_project_templates ON DELETE CASCADE; -CREATE TABLE pt_task_labels ( +CREATE TABLE IF NOT EXISTS pt_task_labels ( task_id UUID NOT NULL, label_id UUID NOT NULL ); @@ -750,23 +897,17 @@ ALTER TABLE pt_task_labels ADD CONSTRAINT pt_task_labels_label_id_fk FOREIGN KEY (label_id) REFERENCES pt_labels; -CREATE TABLE pt_task_phases ( +CREATE TABLE IF NOT EXISTS pt_task_phases ( task_id UUID NOT NULL, phase_id UUID NOT NULL ); -CREATE UNIQUE INDEX pt_task_phase_pt_task_phase_uindex - ON pt_task_phases (task_id, phase_id); - -CREATE UNIQUE INDEX pt_task_phase_task_id_uindex - ON pt_task_phases (task_id); - ALTER TABLE pt_task_phases ADD CONSTRAINT pt_task_phase_phase_id_fk FOREIGN KEY (phase_id) REFERENCES pt_phases ON DELETE CASCADE; -CREATE TABLE pt_task_statuses ( +CREATE TABLE IF NOT EXISTS pt_task_statuses ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, template_id UUID NOT NULL, @@ -775,9 +916,6 @@ CREATE TABLE pt_task_statuses ( sort_order INTEGER DEFAULT 0 NOT NULL ); -CREATE UNIQUE INDEX pt_task_statuses_template_id_name_uindex - ON pt_task_statuses (template_id, name); - ALTER TABLE pt_task_statuses ADD CONSTRAINT pt_task_statuses_pk PRIMARY KEY (id); @@ -787,7 +925,7 @@ ALTER TABLE pt_task_statuses FOREIGN KEY (template_id) REFERENCES pt_project_templates ON DELETE CASCADE; -CREATE TABLE pt_tasks ( +CREATE TABLE IF NOT EXISTS pt_tasks ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, description TEXT, @@ -841,7 +979,7 @@ ALTER TABLE pt_tasks ADD CONSTRAINT pt_tasks_total_minutes_check CHECK ((total_minutes >= (0)::NUMERIC) AND (total_minutes <= (999999)::NUMERIC)); -CREATE TABLE role_permissions ( +CREATE TABLE IF NOT EXISTS role_permissions ( role_id UUID NOT NULL, permission_id TEXT NOT NULL ); @@ -854,7 +992,7 @@ ALTER TABLE role_permissions ADD CONSTRAINT role_permissions_permission_id_fk FOREIGN KEY (permission_id) REFERENCES permissions; -CREATE TABLE roles ( +CREATE TABLE IF NOT EXISTS roles ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, team_id UUID NOT NULL, @@ -863,17 +1001,6 @@ CREATE TABLE roles ( owner BOOLEAN DEFAULT FALSE NOT NULL ); -CREATE UNIQUE INDEX roles_default_uindex - ON roles (team_id) - WHERE (default_role IS TRUE); - -CREATE UNIQUE INDEX roles_name_team_id_uindex - ON roles (name, team_id); - -CREATE UNIQUE INDEX roles_owner_uindex - ON roles (team_id) - WHERE (owner IS TRUE); - ALTER TABLE roles ADD CONSTRAINT roles_pk PRIMARY KEY (id); @@ -886,21 +1013,18 @@ ALTER TABLE role_permissions ADD CONSTRAINT role_permissions_role_id_fk FOREIGN KEY (role_id) REFERENCES roles; -CREATE TABLE spam_emails ( +CREATE TABLE IF NOT EXISTS spam_emails ( id UUID DEFAULT uuid_generate_v4() NOT NULL, email TEXT NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL ); -CREATE UNIQUE INDEX spam_emails_email_uindex - ON spam_emails (email); - ALTER TABLE spam_emails ADD CONSTRAINT spam_emails_pk PRIMARY KEY (id); -CREATE TABLE sys_project_healths ( +CREATE TABLE IF NOT EXISTS sys_project_healths ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, color_code WL_HEX_COLOR NOT NULL, @@ -912,11 +1036,7 @@ ALTER TABLE sys_project_healths ADD CONSTRAINT sys_project_healths_pk PRIMARY KEY (id); -ALTER TABLE projects - ADD CONSTRAINT projects_sys_project_healths_id_fk - FOREIGN KEY (health_id) REFERENCES sys_project_healths; - -CREATE TABLE sys_project_statuses ( +CREATE TABLE IF NOT EXISTS sys_project_statuses ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, color_code WL_HEX_COLOR NOT NULL, @@ -929,19 +1049,16 @@ ALTER TABLE sys_project_statuses ADD CONSTRAINT sys_project_statuses_pk PRIMARY KEY (id); -ALTER TABLE projects - ADD CONSTRAINT projects_status_id_fk - FOREIGN KEY (status_id) REFERENCES sys_project_statuses; - -CREATE TABLE sys_task_status_categories ( - id UUID DEFAULT uuid_generate_v4() NOT NULL, - name TEXT NOT NULL, - color_code WL_HEX_COLOR NOT NULL, - index INTEGER DEFAULT 0 NOT NULL, - is_todo BOOLEAN DEFAULT FALSE NOT NULL, - is_doing BOOLEAN DEFAULT FALSE NOT NULL, - is_done BOOLEAN DEFAULT FALSE NOT NULL, - description TEXT +CREATE TABLE IF NOT EXISTS sys_task_status_categories ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + name TEXT NOT NULL, + color_code WL_HEX_COLOR NOT NULL, + index INTEGER DEFAULT 0 NOT NULL, + is_todo BOOLEAN DEFAULT FALSE NOT NULL, + is_doing BOOLEAN DEFAULT FALSE NOT NULL, + is_done BOOLEAN DEFAULT FALSE NOT NULL, + description TEXT, + color_code_dark WL_HEX_COLOR ); ALTER TABLE sys_task_status_categories @@ -960,7 +1077,7 @@ ALTER TABLE pt_task_statuses ADD CONSTRAINT pt_task_statuses_category_id_fk FOREIGN KEY (category_id) REFERENCES sys_task_status_categories; -CREATE TABLE task_activity_logs ( +CREATE TABLE IF NOT EXISTS task_activity_logs ( id UUID DEFAULT uuid_generate_v4() NOT NULL, task_id UUID NOT NULL, team_id UUID NOT NULL, @@ -992,7 +1109,7 @@ ALTER TABLE task_activity_logs ADD CONSTRAINT task_activity_logs_log_type_check CHECK (log_type = ANY (ARRAY ['create'::TEXT, 'update'::TEXT, 'delete'::TEXT, 'assign'::TEXT, 'unassign'::TEXT])); -CREATE TABLE task_attachments ( +CREATE TABLE IF NOT EXISTS task_attachments ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, size BIGINT DEFAULT 0 NOT NULL, @@ -1004,9 +1121,6 @@ CREATE TABLE task_attachments ( created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL ); -CREATE INDEX task_attachments_task_id_index - ON task_attachments (task_id); - ALTER TABLE task_attachments ADD CONSTRAINT task_attachments_pk PRIMARY KEY (id); @@ -1020,7 +1134,7 @@ ALTER TABLE task_attachments ADD CONSTRAINT task_attachments_name_check CHECK (CHAR_LENGTH(name) <= 110); -CREATE TABLE task_comment_contents ( +CREATE TABLE IF NOT EXISTS task_comment_contents ( index INTEGER NOT NULL, comment_id UUID NOT NULL, team_member_id UUID, @@ -1033,9 +1147,9 @@ ALTER TABLE task_comment_contents ALTER TABLE task_comment_contents ADD CONSTRAINT task_comment_contents_name_check - CHECK (CHAR_LENGTH(text_content) <= 2000); + CHECK (CHAR_LENGTH(text_content) <= 5000); -CREATE TABLE task_comments ( +CREATE TABLE IF NOT EXISTS task_comments ( id UUID DEFAULT uuid_generate_v4() NOT NULL, user_id UUID NOT NULL, team_member_id UUID NOT NULL, @@ -1045,9 +1159,6 @@ CREATE TABLE task_comments ( ses_message_id TEXT ); -CREATE INDEX task_comments_task_id_index - ON task_comments (task_id); - ALTER TABLE task_comments ADD CONSTRAINT task_comments_pk PRIMARY KEY (id); @@ -1057,31 +1168,22 @@ ALTER TABLE task_comment_contents FOREIGN KEY (comment_id) REFERENCES task_comments ON DELETE CASCADE; -CREATE TABLE task_labels ( +CREATE TABLE IF NOT EXISTS task_labels ( task_id UUID NOT NULL, label_id UUID NOT NULL ); -CREATE INDEX task_labels_task_id_index - ON task_labels (task_id); - ALTER TABLE task_labels ADD CONSTRAINT task_labels_pk PRIMARY KEY (task_id, label_id); -CREATE TABLE team_labels ( +CREATE TABLE IF NOT EXISTS team_labels ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, color_code WL_HEX_COLOR NOT NULL, team_id UUID NOT NULL ); -CREATE INDEX team_labels_name_index - ON team_labels (name); - -CREATE UNIQUE INDEX team_labels_name_team_uindex - ON team_labels (name, team_id); - ALTER TABLE team_labels ADD CONSTRAINT team_labels_pk PRIMARY KEY (id); @@ -1100,32 +1202,24 @@ ALTER TABLE team_labels ADD CONSTRAINT team_labels_name_check CHECK (CHAR_LENGTH(name) <= 40); -CREATE TABLE task_phase ( +CREATE TABLE IF NOT EXISTS task_phase ( task_id UUID NOT NULL, phase_id UUID NOT NULL ); -CREATE UNIQUE INDEX task_phase_task_id_uindex - ON task_phase (task_id); - -CREATE UNIQUE INDEX task_phase_task_phase_uindex - ON task_phase (task_id, phase_id); - ALTER TABLE task_phase ADD CONSTRAINT task_phase_phase_id_fk FOREIGN KEY (phase_id) REFERENCES project_phases ON DELETE CASCADE; -CREATE TABLE task_priorities ( - id UUID DEFAULT uuid_generate_v4() NOT NULL, - name TEXT NOT NULL, - value INTEGER DEFAULT 0 NOT NULL, - color_code WL_HEX_COLOR NOT NULL +CREATE TABLE IF NOT EXISTS task_priorities ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + name TEXT NOT NULL, + value INTEGER DEFAULT 0 NOT NULL, + color_code WL_HEX_COLOR NOT NULL, + color_code_dark WL_HEX_COLOR ); -CREATE UNIQUE INDEX task_priorities_name_uindex - ON task_priorities (name); - ALTER TABLE task_priorities ADD CONSTRAINT task_priorities_pk PRIMARY KEY (id); @@ -1138,7 +1232,7 @@ ALTER TABLE pt_tasks ADD CONSTRAINT pt_tasks_priority_fk FOREIGN KEY (priority_id) REFERENCES task_priorities; -CREATE TABLE task_statuses ( +CREATE TABLE IF NOT EXISTS task_statuses ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, project_id UUID NOT NULL, @@ -1147,15 +1241,6 @@ CREATE TABLE task_statuses ( sort_order INTEGER DEFAULT 0 NOT NULL ); -CREATE INDEX task_statuses_name_index - ON task_statuses (name); - -CREATE INDEX task_statuses_project_category_index - ON task_statuses (project_id, category_id); - -CREATE UNIQUE INDEX task_statuses_project_id_name_uindex - ON task_statuses (project_id, name); - ALTER TABLE task_statuses ADD CONSTRAINT task_statuses_pk PRIMARY KEY (id); @@ -1173,7 +1258,7 @@ ALTER TABLE task_statuses ADD CONSTRAINT task_statuses_name_check CHECK (CHAR_LENGTH(name) <= 50); -CREATE TABLE task_subscribers ( +CREATE TABLE IF NOT EXISTS task_subscribers ( id UUID DEFAULT uuid_generate_v4() NOT NULL, user_id UUID NOT NULL, task_id UUID NOT NULL, @@ -1182,15 +1267,6 @@ CREATE TABLE task_subscribers ( created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL ); -CREATE INDEX task_subscribers_task_id_index - ON task_subscribers (task_id); - -CREATE INDEX task_subscribers_user_id_index - ON task_subscribers (user_id); - -CREATE UNIQUE INDEX task_subscribers_user_task_team_member_uindex - ON task_subscribers (user_id, task_id, team_member_id); - ALTER TABLE task_subscribers ADD CONSTRAINT task_subscribers_pk PRIMARY KEY (id); @@ -1199,7 +1275,7 @@ ALTER TABLE task_subscribers ADD CONSTRAINT task_subscribers_action_check CHECK (action = 'WHEN_DONE'::TEXT); -CREATE TABLE task_templates ( +CREATE TABLE IF NOT EXISTS task_templates ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, team_id UUID NOT NULL, @@ -1207,14 +1283,11 @@ CREATE TABLE task_templates ( updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL ); -CREATE UNIQUE INDEX task_templates_name_team_uindex - ON task_templates (name, team_id); - ALTER TABLE task_templates ADD CONSTRAINT task_templates_pk PRIMARY KEY (id); -CREATE TABLE task_templates_tasks ( +CREATE TABLE IF NOT EXISTS task_templates_tasks ( name TEXT NOT NULL, template_id UUID NOT NULL, total_minutes NUMERIC DEFAULT 0 NOT NULL @@ -1225,20 +1298,17 @@ ALTER TABLE task_templates_tasks FOREIGN KEY (template_id) REFERENCES task_templates ON DELETE CASCADE; -CREATE TABLE task_timers ( +CREATE TABLE IF NOT EXISTS task_timers ( task_id UUID NOT NULL, user_id UUID NOT NULL, start_time TIMESTAMP WITH TIME ZONE ); -CREATE INDEX task_timers_task_id_user_id_index - ON task_timers (task_id, user_id); - ALTER TABLE task_timers ADD CONSTRAINT task_timers_pk PRIMARY KEY (task_id, user_id); -CREATE TABLE task_updates ( +CREATE TABLE IF NOT EXISTS task_updates ( id UUID DEFAULT uuid_generate_v4() NOT NULL, type TEXT NOT NULL, reporter_id UUID NOT NULL, @@ -1247,7 +1317,8 @@ CREATE TABLE task_updates ( team_id UUID NOT NULL, project_id UUID NOT NULL, is_sent BOOLEAN DEFAULT FALSE NOT NULL, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + retry_count INTEGER DEFAULT 0 ); ALTER TABLE task_updates @@ -1263,7 +1334,7 @@ ALTER TABLE task_updates ADD CONSTRAINT task_updates_type_check CHECK (type = ANY (ARRAY ['ASSIGN'::TEXT, 'UNASSIGN'::TEXT])); -CREATE TABLE task_work_log ( +CREATE TABLE IF NOT EXISTS task_work_log ( id UUID DEFAULT uuid_generate_v4() NOT NULL, time_spent NUMERIC DEFAULT 0 NOT NULL, description TEXT, @@ -1286,7 +1357,7 @@ ALTER TABLE task_work_log ADD CONSTRAINT task_work_log_time_spent_check CHECK (time_spent >= (0)::NUMERIC); -CREATE TABLE tasks ( +CREATE TABLE IF NOT EXISTS tasks ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, description TEXT, @@ -1305,27 +1376,11 @@ CREATE TABLE tasks ( created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, sort_order INTEGER DEFAULT 0 NOT NULL, - roadmap_sort_order INTEGER DEFAULT 0 NOT NULL + roadmap_sort_order INTEGER DEFAULT 0 NOT NULL, + billable BOOLEAN DEFAULT TRUE, + schedule_id UUID ); -CREATE INDEX tasks_created_at_index - ON tasks (created_at); - -CREATE INDEX tasks_id_project_id_index - ON tasks (id, project_id); - -CREATE INDEX tasks_name_index - ON tasks (name); - -CREATE INDEX tasks_parent_task_id_index - ON tasks (parent_task_id); - -CREATE INDEX tasks_project_id_index - ON tasks (project_id); - -CREATE INDEX tasks_sort_order_index - ON tasks (sort_order); - ALTER TABLE tasks ADD CONSTRAINT tasks_pk PRIMARY KEY (id); @@ -1389,15 +1444,16 @@ ALTER TABLE tasks ADD CONSTRAINT tasks_priority_fk FOREIGN KEY (priority_id) REFERENCES task_priorities; -ALTER TABLE tasks - ADD CONSTRAINT tasks_project_fk - FOREIGN KEY (project_id) REFERENCES projects; - ALTER TABLE tasks ADD CONSTRAINT tasks_status_id_fk FOREIGN KEY (status_id) REFERENCES task_statuses ON DELETE RESTRICT; +ALTER TABLE tasks + ADD CONSTRAINT tasks_project_fk + FOREIGN KEY (project_id) REFERENCES projects + ON DELETE CASCADE; + ALTER TABLE tasks ADD CONSTRAINT tasks_description_check CHECK (CHAR_LENGTH(description) <= 500000); @@ -1410,7 +1466,34 @@ ALTER TABLE tasks ADD CONSTRAINT tasks_total_minutes_check CHECK ((total_minutes >= (0)::NUMERIC) AND (total_minutes <= (999999)::NUMERIC)); -CREATE TABLE tasks_assignees ( +CREATE TRIGGER projects_tasks_counter_trigger + BEFORE INSERT + ON tasks + FOR EACH ROW +EXECUTE PROCEDURE update_project_tasks_counter_trigger_fn(); + +CREATE TRIGGER set_task_updated_at + BEFORE UPDATE + ON tasks + FOR EACH ROW +EXECUTE PROCEDURE set_task_updated_at_trigger_fn(); + +CREATE TRIGGER tasks_status_id_change + AFTER UPDATE + OF status_id + ON tasks + FOR EACH ROW +EXECUTE PROCEDURE task_status_change_trigger_fn(); + +CREATE TRIGGER tasks_task_subscriber_notify_done + BEFORE UPDATE + OF status_id + ON tasks + FOR EACH ROW + WHEN (old.status_id IS DISTINCT FROM new.status_id) +EXECUTE PROCEDURE tasks_task_subscriber_notify_done_trigger(); + +CREATE TABLE IF NOT EXISTS tasks_assignees ( task_id UUID NOT NULL, project_member_id UUID NOT NULL, team_member_id UUID NOT NULL, @@ -1419,9 +1502,6 @@ CREATE TABLE tasks_assignees ( assigned_by UUID NOT NULL ); -CREATE INDEX tasks_assignees_team_member_id_index - ON tasks_assignees (team_member_id); - ALTER TABLE tasks_assignees ADD CONSTRAINT tasks_assignees_pk PRIMARY KEY (task_id, project_member_id); @@ -1436,7 +1516,7 @@ ALTER TABLE tasks_assignees FOREIGN KEY (task_id) REFERENCES tasks ON DELETE CASCADE; -CREATE TABLE team_members ( +CREATE TABLE IF NOT EXISTS team_members ( id UUID DEFAULT uuid_generate_v4() NOT NULL, user_id UUID, team_id UUID NOT NULL, @@ -1447,18 +1527,6 @@ CREATE TABLE team_members ( active BOOLEAN DEFAULT TRUE ); -CREATE INDEX team_members_ids_index - ON team_members (team_id, user_id, role_id); - -CREATE INDEX team_members_team_id_index - ON team_members (team_id); - -CREATE INDEX team_members_user_id_team_id_index - ON team_members (user_id, team_id); - -CREATE UNIQUE INDEX team_members_user_id_team_id_uindex - ON team_members (user_id, team_id); - ALTER TABLE team_members ADD CONSTRAINT team_members_pk PRIMARY KEY (id); @@ -1511,10 +1579,19 @@ ALTER TABLE team_members ADD CONSTRAINT team_members_role_id_fk FOREIGN KEY (role_id) REFERENCES roles; --- START: Users -CREATE SEQUENCE IF NOT EXISTS users_user_no_seq START 1; +CREATE TRIGGER insert_notification_settings + AFTER INSERT + ON team_members + FOR EACH ROW +EXECUTE PROCEDURE notification_settings_insert_trigger_fn(); -CREATE TABLE users ( +CREATE TRIGGER remove_notification_settings + BEFORE DELETE + ON team_members + FOR EACH ROW +EXECUTE PROCEDURE notification_settings_delete_trigger_fn(); + +CREATE TABLE IF NOT EXISTS users ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, email WL_EMAIL NOT NULL, @@ -1529,21 +1606,12 @@ CREATE TABLE users ( created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, last_active TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, - temp_email BOOLEAN DEFAULT FALSE + temp_email BOOLEAN DEFAULT FALSE, + is_deleted BOOLEAN DEFAULT FALSE, + deleted_at TIMESTAMP WITH TIME ZONE, + language LANGUAGE_TYPE DEFAULT 'en'::LANGUAGE_TYPE ); -CREATE INDEX users_email_index - ON users (email); - -CREATE UNIQUE INDEX users_email_uindex - ON users (email); - -CREATE UNIQUE INDEX users_google_id_uindex - ON users (google_id); - -CREATE UNIQUE INDEX users_socket_id_uindex - ON users (socket_id); - ALTER TABLE users ADD CONSTRAINT users_pk PRIMARY KEY (id); @@ -1556,6 +1624,34 @@ ALTER TABLE favorite_projects ADD CONSTRAINT favorite_projects_user_id_fk FOREIGN KEY (user_id) REFERENCES users; +ALTER TABLE licensing_coupon_codes + ADD CONSTRAINT licensing_coupon_codes_users_id_fk + FOREIGN KEY (redeemed_by) REFERENCES users; + +ALTER TABLE licensing_credit_subs + ADD CONSTRAINT licensing_credit_subs_user_id_fk + FOREIGN KEY (user_id) REFERENCES users; + +ALTER TABLE licensing_custom_subs + ADD CONSTRAINT licensing_custom_subs_users_id_fk + FOREIGN KEY (user_id) REFERENCES users; + +ALTER TABLE licensing_payment_details + ADD CONSTRAINT licensing_payment_details_users_id_fk + FOREIGN KEY (user_id) REFERENCES users; + +ALTER TABLE licensing_user_payment_methods + ADD CONSTRAINT licensing_user_payment_methods_users_id_fk + FOREIGN KEY (user_id) REFERENCES users; + +ALTER TABLE licensing_user_subscriptions + ADD CONSTRAINT licensing_user_subscriptions_users_id_fk + FOREIGN KEY (user_id) REFERENCES users; + +ALTER TABLE licensing_user_subscriptions_log + ADD CONSTRAINT licensing_user_subscriptions_log_users_id_fk + FOREIGN KEY (user_id) REFERENCES users; + ALTER TABLE notification_settings ADD CONSTRAINT notification_settings_user_id_fk FOREIGN KEY (user_id) REFERENCES users @@ -1563,11 +1659,12 @@ ALTER TABLE notification_settings ALTER TABLE organizations ADD CONSTRAINT organization_user_id_pk - FOREIGN KEY (user_id) REFERENCES users ON DELETE CASCADE; + FOREIGN KEY (user_id) REFERENCES users + ON DELETE CASCADE; ALTER TABLE personal_todo_list ADD CONSTRAINT personal_todo_list_user_id_fk - FOREIGN KEY (user_id) REFERENCES users ON DELETE CASCADE; + FOREIGN KEY (user_id) REFERENCES users; ALTER TABLE project_categories ADD CONSTRAINT project_categories_created_by_fk @@ -1654,7 +1751,12 @@ ALTER TABLE users ADD CONSTRAINT users_name_check CHECK (CHAR_LENGTH(name) <= 55); -CREATE TABLE teams ( +CREATE TRIGGER users_email_lower + BEFORE INSERT OR UPDATE + ON users +EXECUTE PROCEDURE lower_email(); + +CREATE TABLE IF NOT EXISTS teams ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, user_id UUID NOT NULL, @@ -1663,9 +1765,6 @@ CREATE TABLE teams ( organization_id UUID ); -CREATE INDEX teams_id_user_id_index - ON teams (id, user_id); - ALTER TABLE teams ADD CONSTRAINT teams_pk PRIMARY KEY (id); @@ -1692,7 +1791,8 @@ ALTER TABLE email_invitations ALTER TABLE job_titles ADD CONSTRAINT job_titles_team_id_fk - FOREIGN KEY (team_id) REFERENCES teams; + FOREIGN KEY (team_id) REFERENCES teams + ON DELETE CASCADE; ALTER TABLE notification_settings ADD CONSTRAINT notification_settings_team_id_fk @@ -1774,16 +1874,13 @@ ALTER TABLE teams ADD CONSTRAINT teams_name_check CHECK (CHAR_LENGTH(name) <= 55); -CREATE TABLE timezones ( +CREATE TABLE IF NOT EXISTS timezones ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, abbrev TEXT NOT NULL, utc_offset INTERVAL NOT NULL ); -CREATE INDEX timezones_name_index - ON timezones (name); - ALTER TABLE timezones ADD CONSTRAINT timezones_pk PRIMARY KEY (id); @@ -1792,7 +1889,7 @@ ALTER TABLE users ADD CONSTRAINT users_timezone_id_fk FOREIGN KEY (timezone_id) REFERENCES timezones; -CREATE TABLE user_notifications ( +CREATE TABLE IF NOT EXISTS user_notifications ( id UUID DEFAULT uuid_generate_v4() NOT NULL, message TEXT NOT NULL, user_id UUID NOT NULL, @@ -1827,7 +1924,7 @@ ALTER TABLE user_notifications ADD CONSTRAINT user_notifications_user_id_fk FOREIGN KEY (user_id) REFERENCES users; -CREATE TABLE users_data ( +CREATE TABLE IF NOT EXISTS users_data ( user_id UUID NOT NULL, organization_name TEXT NOT NULL, contact_number TEXT, @@ -1845,9 +1942,6 @@ CREATE TABLE users_data ( updating_plan BOOLEAN DEFAULT FALSE ); -CREATE UNIQUE INDEX users_data_user_id_uindex - ON users_data (user_id); - ALTER TABLE users_data ADD UNIQUE (user_id); @@ -1860,7 +1954,7 @@ ALTER TABLE users_data ADD CONSTRAINT users_data_users_id_fk FOREIGN KEY (user_id) REFERENCES users; -CREATE TABLE worklenz_alerts ( +CREATE TABLE IF NOT EXISTS worklenz_alerts ( description TEXT NOT NULL, type TEXT NOT NULL, active BOOLEAN DEFAULT FALSE @@ -1870,8 +1964,36 @@ ALTER TABLE worklenz_alerts ADD CONSTRAINT worklenz_alerts_type_check CHECK (type = ANY (ARRAY ['success'::TEXT, 'info'::TEXT, 'warning'::TEXT, 'error'::TEXT])); +CREATE TABLE IF NOT EXISTS licensing_coupon_logs ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + coupon_code TEXT NOT NULL, + redeemed_by UUID NOT NULL, + redeemed_at TIMESTAMP WITH TIME ZONE NOT NULL, + is_refunded BOOLEAN DEFAULT TRUE NOT NULL, + reason TEXT, + reverted_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + feedback TEXT +); -CREATE TABLE task_comment_mentions ( +ALTER TABLE licensing_coupon_logs + ADD CONSTRAINT licensing_coupon_logs_pk + PRIMARY KEY (id); + +ALTER TABLE licensing_coupon_logs + ADD CONSTRAINT licensing_coupon_logs_users_id_fk + FOREIGN KEY (redeemed_by) REFERENCES users; + +CREATE TABLE IF NOT EXISTS sys_license_types ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + name TEXT NOT NULL, + key TEXT NOT NULL, + description TEXT +); + +ALTER TABLE sys_license_types + ADD PRIMARY KEY (id); + +CREATE TABLE IF NOT EXISTS task_comment_mentions ( comment_id UUID NOT NULL, mentioned_index INTEGER DEFAULT 0 NOT NULL, mentioned_by UUID NOT NULL, @@ -1890,12 +2012,289 @@ ALTER TABLE task_comment_mentions ALTER TABLE task_comment_mentions ADD CONSTRAINT task_comment_mentions_informed_by_fk - FOREIGN KEY (informed_by) REFERENCES team_members; + FOREIGN KEY (informed_by) REFERENCES team_members + ON DELETE CASCADE; -CREATE TABLE email_logs ( +CREATE TABLE IF NOT EXISTS email_logs ( id UUID DEFAULT uuid_generate_v4() NOT NULL, email TEXT NOT NULL, subject TEXT NOT NULL, html TEXT NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); + +CREATE TABLE IF NOT EXISTS task_comment_reactions ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + comment_id UUID NOT NULL, + user_id UUID NOT NULL, + team_member_id UUID NOT NULL, + reaction_type REACTION_TYPES DEFAULT 'like'::REACTION_TYPES NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +ALTER TABLE task_comment_reactions + ADD CONSTRAINT task_comment_reactions_pk + PRIMARY KEY (id); + +ALTER TABLE task_comment_reactions + ADD CONSTRAINT task_comment_reactions_user_id_fk + FOREIGN KEY (user_id) REFERENCES users; + +ALTER TABLE task_comment_reactions + ADD CONSTRAINT task_comment_reactions_comment_id_fk + FOREIGN KEY (comment_id) REFERENCES task_comments + ON DELETE CASCADE; + +ALTER TABLE task_comment_reactions + ADD CONSTRAINT task_comment_reactions_team_member_id_fk + FOREIGN KEY (team_member_id) REFERENCES team_members + ON DELETE CASCADE; + +CREATE TABLE IF NOT EXISTS task_dependencies ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + task_id UUID NOT NULL, + related_task_id UUID NOT NULL, + dependency_type DEPENDENCY_TYPE DEFAULT 'blocked_by'::DEPENDENCY_TYPE NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +ALTER TABLE task_dependencies + ADD CONSTRAINT task_dependencies_pk + PRIMARY KEY (id); + +ALTER TABLE task_dependencies + ADD CONSTRAINT task_dependencies_unique_key + UNIQUE (task_id, related_task_id, dependency_type); + +ALTER TABLE task_dependencies + ADD CONSTRAINT task_dependencies_tasks_id_fk + FOREIGN KEY (task_id) REFERENCES tasks + ON DELETE CASCADE; + +ALTER TABLE task_dependencies + ADD CONSTRAINT task_dependencies_tasks_id_fk_2 + FOREIGN KEY (related_task_id) REFERENCES tasks + ON DELETE CASCADE; + +CREATE TABLE IF NOT EXISTS task_recurring_schedules ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + schedule_type SCHEDULE_TYPE DEFAULT 'daily'::SCHEDULE_TYPE, + days_of_week INTEGER[], + day_of_month INTEGER, + week_of_month INTEGER, + interval_days INTEGER, + interval_weeks INTEGER, + interval_months INTEGER, + start_date DATE, + end_date DATE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +ALTER TABLE task_recurring_schedules + ADD CONSTRAINT task_recurring_schedules_pk + PRIMARY KEY (id); + +CREATE TABLE IF NOT EXISTS task_recurring_templates ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + task_id UUID NOT NULL, + schedule_id UUID NOT NULL, + name TEXT NOT NULL, + description TEXT, + end_date TIMESTAMP WITH TIME ZONE, + priority_id UUID NOT NULL, + project_id UUID NOT NULL, + assignees JSONB, + labels JSONB, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +ALTER TABLE task_recurring_templates + ADD CONSTRAINT task_recurring_templates_projects_id_fk + FOREIGN KEY (project_id) REFERENCES projects + ON DELETE CASCADE; + +ALTER TABLE task_recurring_templates + ADD CONSTRAINT task_recurring_templates_task_priorities_id_fk + FOREIGN KEY (priority_id) REFERENCES task_priorities; + +ALTER TABLE task_recurring_templates + ADD CONSTRAINT task_recurring_templates_task_recurring_schedules_id_fk + FOREIGN KEY (schedule_id) REFERENCES task_recurring_schedules + ON DELETE CASCADE; + +ALTER TABLE task_recurring_templates + ADD CONSTRAINT task_recurring_templates_tasks_id_fk + FOREIGN KEY (task_id) REFERENCES tasks + ON DELETE CASCADE; + +CREATE TABLE IF NOT EXISTS task_comment_attachments ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + name TEXT NOT NULL, + size BIGINT DEFAULT 0 NOT NULL, + type TEXT NOT NULL, + task_id UUID NOT NULL, + comment_id UUID NOT NULL, + team_id UUID NOT NULL, + project_id UUID NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL +); + +ALTER TABLE task_comment_attachments + ADD CONSTRAINT task_comment_attachments_pk + PRIMARY KEY (id); + +ALTER TABLE task_comment_attachments + ADD CONSTRAINT task_comment_attachments_comment_id_fk + FOREIGN KEY (comment_id) REFERENCES task_comments + ON DELETE CASCADE; + +ALTER TABLE task_comment_attachments + ADD CONSTRAINT task_comment_attachments_project_id_fk + FOREIGN KEY (project_id) REFERENCES projects + ON DELETE CASCADE; + +ALTER TABLE task_comment_attachments + ADD CONSTRAINT task_comment_attachments_task_id_fk + FOREIGN KEY (task_id) REFERENCES tasks + ON DELETE CASCADE; + +ALTER TABLE task_comment_attachments + ADD CONSTRAINT task_comment_attachments_team_id_fk + FOREIGN KEY (team_id) REFERENCES teams + ON DELETE CASCADE; + +ALTER TABLE task_comment_attachments + ADD CONSTRAINT task_comment_attachments_name_check + CHECK (CHAR_LENGTH(name) <= 100); + +CREATE TABLE IF NOT EXISTS cc_custom_columns ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + project_id UUID NOT NULL, + name TEXT NOT NULL, + key TEXT NOT NULL, + field_type TEXT NOT NULL, + width INTEGER DEFAULT 150, + is_visible BOOLEAN DEFAULT TRUE, + is_custom_column BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +ALTER TABLE cc_custom_columns + ADD PRIMARY KEY (id); + +ALTER TABLE cc_custom_columns + ADD UNIQUE (project_id, key); + +ALTER TABLE cc_custom_columns + ADD FOREIGN KEY (project_id) REFERENCES projects + ON DELETE CASCADE; + +CREATE TABLE IF NOT EXISTS cc_column_configurations ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + column_id UUID NOT NULL, + field_title TEXT, + field_type TEXT, + number_type TEXT, + decimals INTEGER, + label TEXT, + label_position TEXT, + preview_value TEXT, + expression TEXT, + first_numeric_column_key TEXT, + second_numeric_column_key TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +ALTER TABLE cc_column_configurations + ADD PRIMARY KEY (id); + +ALTER TABLE cc_column_configurations + ADD FOREIGN KEY (column_id) REFERENCES cc_custom_columns + ON DELETE CASCADE; + +CREATE TABLE IF NOT EXISTS cc_selection_options ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + column_id UUID NOT NULL, + selection_id TEXT NOT NULL, + selection_name TEXT NOT NULL, + selection_color TEXT, + selection_order INTEGER NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +ALTER TABLE cc_selection_options + ADD PRIMARY KEY (id); + +ALTER TABLE cc_selection_options + ADD FOREIGN KEY (column_id) REFERENCES cc_custom_columns + ON DELETE CASCADE; + +CREATE TABLE IF NOT EXISTS cc_label_options ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + column_id UUID NOT NULL, + label_id TEXT NOT NULL, + label_name TEXT NOT NULL, + label_color TEXT, + label_order INTEGER NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +ALTER TABLE cc_label_options + ADD PRIMARY KEY (id); + +ALTER TABLE cc_label_options + ADD FOREIGN KEY (column_id) REFERENCES cc_custom_columns + ON DELETE CASCADE; + +CREATE TABLE IF NOT EXISTS cc_column_values ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + task_id UUID NOT NULL, + column_id UUID NOT NULL, + text_value TEXT, + number_value NUMERIC(18, 6), + date_value TIMESTAMP WITH TIME ZONE, + boolean_value BOOLEAN, + json_value JSONB, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +ALTER TABLE cc_column_values + ADD PRIMARY KEY (id); + +ALTER TABLE cc_column_values + ADD UNIQUE (task_id, column_id); + +ALTER TABLE cc_column_values + ADD FOREIGN KEY (task_id) REFERENCES tasks + ON DELETE CASCADE; + +ALTER TABLE cc_column_values + ADD FOREIGN KEY (column_id) REFERENCES cc_custom_columns + ON DELETE CASCADE; + +CREATE TABLE IF NOT EXISTS organization_working_days ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + monday BOOLEAN DEFAULT TRUE NOT NULL, + tuesday BOOLEAN DEFAULT TRUE NOT NULL, + wednesday BOOLEAN DEFAULT TRUE NOT NULL, + thursday BOOLEAN DEFAULT TRUE NOT NULL, + friday BOOLEAN DEFAULT TRUE NOT NULL, + saturday BOOLEAN DEFAULT FALSE NOT NULL, + sunday BOOLEAN DEFAULT FALSE NOT NULL, + organization_id UUID NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL +); + +ALTER TABLE organization_working_days + ADD CONSTRAINT organization_working_days_pk + PRIMARY KEY (id); + +ALTER TABLE organization_working_days + ADD CONSTRAINT org_organization_id_fk + FOREIGN KEY (organization_id) REFERENCES organizations; diff --git a/worklenz-backend/database/2_dml.sql b/worklenz-backend/database/2_dml.sql new file mode 100644 index 00000000..c2d51b73 --- /dev/null +++ b/worklenz-backend/database/2_dml.sql @@ -0,0 +1,402 @@ +CREATE OR REPLACE FUNCTION sys_insert_task_priorities() RETURNS VOID AS +$$ +BEGIN + INSERT INTO task_priorities (name, value, color_code, color_code_dark) VALUES ('Medium', 1, '#fbc84c', '#FFC227'); + INSERT INTO task_priorities (name, value, color_code, color_code_dark) VALUES ('Low', 0, '#75c997', '#46D980'); + INSERT INTO task_priorities (name, value, color_code, color_code_dark) VALUES ('High', 2, '#f37070', '#FF4141'); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION sys_insert_project_access_levels() RETURNS VOID AS +$$ +BEGIN + INSERT INTO project_access_levels (name, key) + VALUES ('Admin', 'ADMIN'); + INSERT INTO project_access_levels (name, key) + VALUES ('Member', 'MEMBER'); + INSERT INTO project_access_levels (name, key) + VALUES ('Project Manager', 'PROJECT_MANAGER'); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION sys_insert_task_status_categories() RETURNS VOID AS +$$ +BEGIN + INSERT INTO public.sys_task_status_categories (name, color_code, index, is_todo, is_doing, is_done, description, + color_code_dark) + VALUES ('To do', '#a9a9a9', 1, TRUE, FALSE, FALSE, + 'For tasks that have not been started.', '#989898'); + INSERT INTO public.sys_task_status_categories (name, color_code, index, is_todo, is_doing, is_done, description, + color_code_dark) + VALUES ('Doing', '#70a6f3', 2, FALSE, TRUE, FALSE, + 'For tasks that have been started.', '#4190FF'); + INSERT INTO public.sys_task_status_categories (name, color_code, index, is_todo, is_doing, is_done, description, + color_code_dark) + VALUES ('Done', '#75c997', 3, FALSE, FALSE, TRUE, + 'For tasks that have been completed.', '#46D980'); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION sys_insert_project_statuses() RETURNS VOID AS +$$ +BEGIN + INSERT INTO public.sys_project_statuses (name, color_code, icon, sort_order, is_default) + VALUES ('Cancelled', '#f37070', 'close-circle', 0, FALSE), + ('Blocked', '#cbc8a1', 'stop', 1, FALSE), + ('On Hold', '#cbc8a1', 'stop', 2, FALSE), + ('Proposed', '#cbc8a1', 'clock-circle', 3, TRUE), + ('In Planning', '#cbc8a1', 'clock-circle', 4, FALSE), + ('In Progress', '#80ca79', 'clock-circle', 5, FALSE), + ('Completed', '#80ca79', 'check-circle', 6, FALSE), + ('Continuous', '#80ca79', 'clock-circle', 7, FALSE); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION sys_insert_project_healths() RETURNS VOID AS +$$ +BEGIN + INSERT INTO sys_project_healths (name, color_code, sort_order, is_default) + VALUES ('Not Set', '#a9a9a9', 0, TRUE); + INSERT INTO sys_project_healths (name, color_code, sort_order, is_default) + VALUES ('Needs Attention', '#fbc84c', 1, FALSE); + INSERT INTO sys_project_healths (name, color_code, sort_order, is_default) + VALUES ('At Risk', '#f37070', 2, FALSE); + INSERT INTO sys_project_healths (name, color_code, sort_order, is_default) + VALUES ('Good', '#75c997', 3, FALSE); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION sys_insert_project_templates() RETURNS VOID AS +$$ +DECLARE + medium_priority_id UUID; + todo_category_id UUID; + doing_category_id UUID; + done_category_id UUID; +BEGIN + -- Fetch IDs to avoid repeated subqueries + SELECT id INTO medium_priority_id FROM task_priorities WHERE name = 'Medium' LIMIT 1; + SELECT id INTO todo_category_id FROM public.sys_task_status_categories WHERE name = 'To do' LIMIT 1; + SELECT id INTO doing_category_id FROM public.sys_task_status_categories WHERE name = 'Doing' LIMIT 1; + SELECT id INTO done_category_id FROM public.sys_task_status_categories WHERE name = 'Done' LIMIT 1; + + INSERT INTO public.pt_project_templates (id, name, key, description, phase_label, image_url, color_code) + VALUES ('0a769952-e9b8-4e48-b562-f4ebb0f5914e', 'Software Development', 'SD', 'The "Software Development" project template is a specialized and comprehensive template tailored to the unique needs of software development teams and companies. It offers a structured framework for planning, tracking, and executing software development projects, making it an essential resource for software development firms, tech startups, IT departments, and any business involved in software product development.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/software-developement.gif', '#3b7ad4'), + ('c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', 'Design & Creative', 'DC', 'The "Design & Creative" project template is a versatile solution meticulously crafted to support and enhance the creative and design processes of businesses across various industries. It offers a structured and efficient approach to managing design and creative projects.\r\n', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/creative-and-designing.gif', '#3b7ad4'), + ('f25549d0-d5de-46aa-bbd5-eaab7a970799', 'HR & Recruiting', 'HR', 'The "HR & Recruiting" project template is a specialized tool designed to streamline and enhance human resources and talent acquisition processes. This template is essential for businesses and organizations of all sizes and industries that are involved in HR management and recruitment activities. It provides a structured approach to managing HR projects, making it suitable for a diverse range of businesses.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/hr.gif', '#3b7ad4'), + ('22805d87-ba25-4b2a-8384-7a4eeedc3b63', 'Information Technology', 'IT', 'The "Information Technology" project template is a comprehensive framework tailored to meet the unique needs of businesses and organizations operating in the field of technology and IT services. This versatile template is designed to facilitate the management of IT-related projects, making it applicable to a wide range of businesses.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/information-technology.gif', '#3b7ad4'), + ('a014fae0-1b70-4c05-96b4-1fa8c54591b0', 'Finance', 'FIN', 'The "Finance" project template is a strategic tool meticulously designed to guide businesses and financial professionals in managing critical financial processes, making it applicable to a wide range of industries and businesses. This template provides a structured approach to financial planning, decision-making, and review, making it valuable for various types of organizations.\r\n', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/finance.gif', '#3b7ad4'), + ('e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', 'Personal use', 'PU', 'The "Personal Use" project template is a versatile and user-centric template designed to help individuals manage their personal projects, tasks, and goals with efficiency and organization. This template is adaptable for a wide range of personal activities and can be utilized by anyone seeking a structured approach to planning, tracking, and completing their personal endeavors.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/personal-use.gif', '#3b7ad4'), + ('0aa3f6fc-678f-4d17-8df2-9674ca81fad2', 'Legal', 'LEG', 'The "Legal" project template is a specialized framework meticulously designed to facilitate organized and efficient management of legal projects and matters. This versatile template is essential for businesses, legal departments, and law firms involved in legal activities, making it applicable to a wide range of industries and organizations', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/legal.gif', '#3b7ad4'), + ('5d1ffd21-a7b7-4a5a-9d19-f7fd3ee253e7', 'Nonprofit', 'NON', 'The "Nonprofit" project template is a purpose-built solution tailored to the unique needs and challenges faced by organizations dedicated to serving a greater social or community cause. It provides a structured framework for planning, executing, and monitoring nonprofit initiatives, making it an ideal template for nonprofit organizations, charities, foundations, and community groups.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/non-profit.gif', '#3b7ad4'), + ('39db59be-1dba-448b-87f4-3b955ea699d2', 'Bug Tracking', 'BT', 'The "Bug Tracking" project template is a versatile solution meticulously designed to streamline and enhance the bug management processes of businesses across diverse industries. This template is especially valuable for organizations that rely on software development, IT services, or digital product management. It provides a structured and efficient approach to tracking, resolving, and improving software issues.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/bug-tracking.gif', '#3b7ad4'), + ('d1baf96f-07f4-4468-bb4f-44cdedfad965', 'Manufacturing', 'MAN', 'The "Manufacturing" project template is a specialized and comprehensive template designed to facilitate the management of manufacturing processes and product development from conception to production. This template provides a structured framework for planning, prototyping, and manufacturing, making it essential for manufacturing companies, product design firms, startups, and organizations involved in the development and production of physical products.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/maufacturing.gif', '#3b7ad4'), + ('d90f9194-200a-4afa-a896-ee5afda2cc8a', 'Construction', 'CON', 'The "Construction" project template is a comprehensive solution designed to facilitate efficient project execution for businesses operating in the construction industry. This template offers a structured approach to managing various aspects of construction projects.\r\n', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/construction.gif', '#3b7ad4'), + ('bdf32f36-e85b-434f-aeda-dde2e8ade8c7', 'Sales & CRM', 'SC', 'The "Sales & CRM" project template is a comprehensive solution designed to optimize sales processes and enhance customer relationship management for businesses across various industries. It provides a structured framework for tracking leads, opportunities, and customer interactions, making it an invaluable template for sales-driven organizations and businesses aiming to foster strong customer relationships.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/sales.gif', '#3b7ad4'), + ('c86ed436-f9af-4332-8a2d-2afe9a6d66d4', 'Education', 'EDU', 'The "Education" project template is a dynamic tool designed to facilitate organized and effective management of educational initiatives and projects. This versatile template can be utilized by a variety of educational institutions and businesses involved in the field of education\r\n', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/education.gif', '#3b7ad4'), + ('c94c7306-73ab-4e17-bad4-0e7ff4a9da09', 'Services & Consulting', 'SCE', 'The "Services & Consulting" project template is a robust and versatile template designed to facilitate the efficient delivery of services, consulting engagements, and project-based work across various industries. This template offers a structured framework for managing projects from initiation to closure, making it an invaluable resource for service providers, consulting firms, and businesses engaged in project-driven activities.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/services.gif', '#3b7ad4'), + ('c6917d57-f84a-40fc-aff0-70fd0aab951f', 'Marketing', 'MAR', 'The "Marketing" project template is a versatile and essential template designed to streamline and manage marketing initiatives and campaigns across various industries and business types. This template offers a structured framework for planning, executing, and evaluating marketing projects, making it invaluable for marketing agencies, in-house marketing teams, e-commerce businesses, startups, and organizations seeking to enhance their marketing efforts.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/marketing.gif', '#3b7ad4'); + + INSERT INTO public.pt_statuses (id, name, template_id, category_id) + VALUES ('c3242606-5a24-48aa-8320-cc90a05c2589', 'To Do', '39db59be-1dba-448b-87f4-3b955ea699d2', todo_category_id), + ('05ed8d04-92b1-4c44-bd06-abee29641f31', 'Doing', '39db59be-1dba-448b-87f4-3b955ea699d2', doing_category_id), + ('66e80bc8-6b29-4e72-a484-1593eb1fb44b', 'Done', '39db59be-1dba-448b-87f4-3b955ea699d2', done_category_id), + ('d8a7ac27-cdfb-48fe-b8cd-c87128269a93', 'To Do', 'd90f9194-200a-4afa-a896-ee5afda2cc8a', todo_category_id), + ('a83565f9-2c22-486a-8c10-59182bfe19c6', 'Doing', 'd90f9194-200a-4afa-a896-ee5afda2cc8a', doing_category_id), + ('78803d54-ee61-4389-a5c2-c676f1007d75', 'Done', 'd90f9194-200a-4afa-a896-ee5afda2cc8a', done_category_id), + ('70cbcea9-2b3c-453a-af0d-fb8a7f6270fd', 'To Do', 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', todo_category_id), + ('bf37bf58-c0d1-498f-b208-c9e80509a593', 'Doing', 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', doing_category_id), + ('c7384096-7008-4eb4-b95f-c0706b9647d6', 'Done', 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', done_category_id), + ('efdb40e7-78ae-4a5a-83ec-d372a5816ae5', 'To Do', 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', todo_category_id), + ('ca81d57d-b7d2-45fc-adcd-1c926fd93e6c', 'Doing', 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', doing_category_id), + ('e6dffb96-6879-4064-ab8f-73ca784b1964', 'Done', 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', done_category_id), + ('0010ae09-0dd6-4c08-b351-30124e0dc436', 'To Do', 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', todo_category_id), + ('bde5228a-23b9-4f9d-87fe-1784311d68f6', 'Doing', 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', doing_category_id), + ('5b01b5bd-f29e-477d-94a6-dc5b8101db37', 'Done', 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', done_category_id), + ('35430ef9-040f-465f-b5e3-1f33b26124dd', 'To Do', 'f25549d0-d5de-46aa-bbd5-eaab7a970799', todo_category_id), + ('bac101f9-cec3-43aa-8bd8-354de42fdff2', 'Doing', 'f25549d0-d5de-46aa-bbd5-eaab7a970799', doing_category_id), + ('400b58bd-ad09-4cd1-b5bd-a9f7cb46cb47', 'Done', 'f25549d0-d5de-46aa-bbd5-eaab7a970799', done_category_id), + ('8e9cc873-d9a5-404f-9f38-8ca4fba91a0d', 'To Do', '22805d87-ba25-4b2a-8384-7a4eeedc3b63', todo_category_id), + ('856cd7b1-76b1-4182-b2c8-60c278839913', 'Doing', '22805d87-ba25-4b2a-8384-7a4eeedc3b63', doing_category_id), + ('cbf86a20-8b6d-43d0-99d7-523fb600cdac', 'Done', '22805d87-ba25-4b2a-8384-7a4eeedc3b63', done_category_id), + ('6f0f7261-7f1a-40f3-a771-04fbc5cd0059', 'To Do', '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', todo_category_id), + ('54452070-d355-4fb2-b416-e57390d1b668', 'Doing', '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', doing_category_id), + ('8aac89fc-cf0c-4a4e-889b-97ccd8e7af94', 'Done', '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', done_category_id), + ('0c3c5259-70d2-407b-8a45-c9b81ac84a3a', 'To Do', 'd1baf96f-07f4-4468-bb4f-44cdedfad965', todo_category_id), + ('61cf7147-4416-4efb-bda7-4db1059f76a9', 'Doing', 'd1baf96f-07f4-4468-bb4f-44cdedfad965', doing_category_id), + ('65f4cbd7-bcbb-4a10-8014-fd3b9a3c9ed9', 'Done', 'd1baf96f-07f4-4468-bb4f-44cdedfad965', done_category_id), + ('1073754d-c498-4e6d-bcee-68bba526e572', 'To Do', 'c6917d57-f84a-40fc-aff0-70fd0aab951f', todo_category_id), + ('44d54c08-bce6-44e6-9cfc-23d1b4a9e011', 'Doing', 'c6917d57-f84a-40fc-aff0-70fd0aab951f', doing_category_id), + ('1ae23b11-5edf-485d-9217-f905dc33ddda', 'Done', 'c6917d57-f84a-40fc-aff0-70fd0aab951f', done_category_id), + ('da60606a-ced5-46bf-bf2f-4ee9d5627a9a', 'To Do', '5d1ffd21-a7b7-4a5a-9d19-f7fd3ee253e7', todo_category_id), + ('d2bff598-5135-4cac-92aa-641b69ae1dab', 'Doing', '5d1ffd21-a7b7-4a5a-9d19-f7fd3ee253e7', doing_category_id), + ('9ec60343-27fe-4af5-a35b-d356ebd61837', 'Done', '5d1ffd21-a7b7-4a5a-9d19-f7fd3ee253e7', done_category_id), + ('2e14a9e9-4c1a-4596-a0f2-574d2e7b2fa4', 'To Do', 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', todo_category_id), + ('1a403c75-5d8c-4752-bf96-e486b8042df6', 'Doing', 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', doing_category_id), + ('70fd7f98-b278-440a-8a33-d3453bfd4b70', 'Done', 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', done_category_id), + ('c20816ac-fe57-4471-91b0-61fa060663ca', 'To Do', 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', todo_category_id), + ('291095da-b3d9-40aa-bcd2-5cd6fb51b89f', 'Doing', 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', doing_category_id), + ('b450e432-63f4-46a9-bd4a-26b0bdd37507', 'Done', 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', done_category_id), + ('4da7cb15-9767-4522-9950-6a4b26e660a6', 'To Do', 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', todo_category_id), + ('b4a409f7-4009-4633-b147-f4f99c73f30b', 'Doing', 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', doing_category_id), + ('171e791d-6601-45e7-8c5e-80591a610ed8', 'Done', 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', done_category_id), + ('757e7655-22c1-4a40-8f3d-2754fbd564dc', 'To Do', '0a769952-e9b8-4e48-b562-f4ebb0f5914e', todo_category_id), + ('e22f483a-4c87-417c-8c3d-2673c44b1079', 'Doing', '0a769952-e9b8-4e48-b562-f4ebb0f5914e', doing_category_id), + ('7fae7fde-26a3-44aa-b694-b6d9228e9d6a', 'Done', '0a769952-e9b8-4e48-b562-f4ebb0f5914e', done_category_id); + + INSERT INTO public.pt_tasks (id, name, description, total_minutes, sort_order, priority_id, template_id, parent_task_id, status_id) + VALUES ('a75993d9-3fb3-4d0b-a5d4-cab53b60462c', 'Testing and Verification', NULL, 0, 0, medium_priority_id, '39db59be-1dba-448b-87f4-3b955ea699d2', NULL, 'c3242606-5a24-48aa-8320-cc90a05c2589'), + ('3fdb6801-bc09-4d71-8273-987cd3d1e0f6', 'Bug Prioritization', NULL, 0, 6, medium_priority_id, '39db59be-1dba-448b-87f4-3b955ea699d2', NULL, '05ed8d04-92b1-4c44-bd06-abee29641f31'), + ('ca64f247-a186-4edb-affd-738f1c2a4d60', 'Bug reporting', NULL, 0, 2, medium_priority_id, '39db59be-1dba-448b-87f4-3b955ea699d2', NULL, 'c3242606-5a24-48aa-8320-cc90a05c2589'), + ('1e493de8-38cf-4e6e-8f0b-5e1f6f3b07f4', 'Bug Assignment', NULL, 0, 5, medium_priority_id, '39db59be-1dba-448b-87f4-3b955ea699d2', NULL, '05ed8d04-92b1-4c44-bd06-abee29641f31'), + ('67b2ab3c-53e5-428c-bbad-8bdc19dc88de', 'Bug Closure', NULL, 0, 4, medium_priority_id, '39db59be-1dba-448b-87f4-3b955ea699d2', NULL, '66e80bc8-6b29-4e72-a484-1593eb1fb44b'), + ('9311ff84-1052-4989-8192-0fea20204fbe', 'Documentation', NULL, 0, 3, medium_priority_id, '39db59be-1dba-448b-87f4-3b955ea699d2', NULL, '66e80bc8-6b29-4e72-a484-1593eb1fb44b'), + ('7d0697cd-868c-4b41-9f4f-f9a8c1131b24', 'Reporting', NULL, 0, 1, medium_priority_id, '39db59be-1dba-448b-87f4-3b955ea699d2', NULL, '66e80bc8-6b29-4e72-a484-1593eb1fb44b'), + ('f994718d-3f1c-4880-99da-0b38ce90e0b4', 'Insulation and HVAC', NULL, 0, 4, medium_priority_id, 'd90f9194-200a-4afa-a896-ee5afda2cc8a', NULL, 'a83565f9-2c22-486a-8c10-59182bfe19c6'), + ('5c5fac9e-5016-4e36-b812-1a9c164adca6', 'Site Preparation', NULL, 0, 8, medium_priority_id, 'd90f9194-200a-4afa-a896-ee5afda2cc8a', NULL, 'a83565f9-2c22-486a-8c10-59182bfe19c6'), + ('9127f591-e532-4f88-b61e-2a02fd6a654a', 'Electrical and Plumbing', NULL, 0, 5, medium_priority_id, 'd90f9194-200a-4afa-a896-ee5afda2cc8a', NULL, 'a83565f9-2c22-486a-8c10-59182bfe19c6'), + ('41a6a358-e603-4bf9-9db5-6595f8f00e00', 'Project Planning', NULL, 0, 0, medium_priority_id, 'd90f9194-200a-4afa-a896-ee5afda2cc8a', NULL, 'd8a7ac27-cdfb-48fe-b8cd-c87128269a93'), + ('c37db3ea-0be7-453b-8588-3672fff523fe', 'Exterior Work', NULL, 0, 6, medium_priority_id, 'd90f9194-200a-4afa-a896-ee5afda2cc8a', NULL, '78803d54-ee61-4389-a5c2-c676f1007d75'), + ('6ecc497e-ce9a-4a5d-a2a6-a951f26223cf', 'Structural Framing', NULL, 0, 1, medium_priority_id, 'd90f9194-200a-4afa-a896-ee5afda2cc8a', NULL, 'd8a7ac27-cdfb-48fe-b8cd-c87128269a93'), + ('d53a405b-0bcf-4372-a7ea-d4bca9c13740', 'Foundation Work', NULL, 0, 9, medium_priority_id, 'd90f9194-200a-4afa-a896-ee5afda2cc8a', NULL, '78803d54-ee61-4389-a5c2-c676f1007d75'), + ('b73154b9-d335-4a3e-8593-84da85801720', 'Finishing and Finishing Work', NULL, 0, 7, medium_priority_id, 'd90f9194-200a-4afa-a896-ee5afda2cc8a', NULL, '78803d54-ee61-4389-a5c2-c676f1007d75'), + ('5caac736-3ccf-4b0d-ada4-e1cf4f6ef631', 'Quality Assurance and Inspections', NULL, 0, 2, medium_priority_id, 'd90f9194-200a-4afa-a896-ee5afda2cc8a', NULL, 'd8a7ac27-cdfb-48fe-b8cd-c87128269a93'), + ('44346d7f-fbbd-468a-8c37-e439c268c3e1', 'Utilities and Systems Integration', NULL, 0, 3, medium_priority_id, 'd90f9194-200a-4afa-a896-ee5afda2cc8a', NULL, 'd8a7ac27-cdfb-48fe-b8cd-c87128269a93'), + ('870daf76-fedb-4198-b13a-9ac453a73dee', 'Brainstorm creative ideas and concepts, and sketch initial designs.', NULL, 0, 0, medium_priority_id, 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', NULL, '70cbcea9-2b3c-453a-af0d-fb8a7f6270fd'), + ('0ebb388d-c7e5-49ce-bf25-e2ce7b9511a1', 'Create storyboards or wireframes to outline the structure and flow of the design.', NULL, 0, 1, medium_priority_id, 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', NULL, '70cbcea9-2b3c-453a-af0d-fb8a7f6270fd'), + ('5d09ae52-7e88-41d6-af82-4efdc55b4af0', 'Create user-friendly interfaces for digital products, ensuring a seamless user experience.', NULL, 0, 2, medium_priority_id, 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', NULL, '70cbcea9-2b3c-453a-af0d-fb8a7f6270fd'), + ('675a8efc-17f2-4c9b-bd06-227020ffa688', 'Develop high-quality visual design assets based on approved concepts.', NULL, 0, 3, medium_priority_id, 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', NULL, '70cbcea9-2b3c-453a-af0d-fb8a7f6270fd'), + ('5403584c-57c4-40f6-8ac5-7c0b89d10370', 'Prepare design files for final production, including optimizing for different platforms.', NULL, 0, 4, medium_priority_id, 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', NULL, '70cbcea9-2b3c-453a-af0d-fb8a7f6270fd'), + ('14de7fa7-e8ac-4d1d-8249-250c733f57c0', 'Collaborate with production teams to produce and deliver the final design assets.', NULL, 0, 5, medium_priority_id, 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', NULL, '70cbcea9-2b3c-453a-af0d-fb8a7f6270fd'), + ('188fcdab-ea71-4be3-96f1-cbe224b53310', 'Research and analyze the target market, including demographics and trends.', NULL, 0, 6, medium_priority_id, 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', NULL, '70cbcea9-2b3c-453a-af0d-fb8a7f6270fd'), + ('a67b4800-b35d-4448-90ba-e6aa1053ea3f', 'Study competitors'' design strategies and identify opportunities for differentiation.', NULL, 0, 7, medium_priority_id, 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', NULL, '70cbcea9-2b3c-453a-af0d-fb8a7f6270fd'), + ('1b5be3a3-ae6d-43f7-8a5a-fa48c96fd54a', 'Class Scheduling', NULL, 0, 8, medium_priority_id, 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', NULL, 'e6dffb96-6879-4064-ab8f-73ca784b1964'), + ('fbe32f14-a8e9-401b-8a39-ad53ebd520fa', 'Curriculum Development', NULL, 0, 5, medium_priority_id, 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', NULL, 'ca81d57d-b7d2-45fc-adcd-1c926fd93e6c'), + ('1a9bedd1-dbfb-4d25-9b16-ec1eeb773bac', 'Assignments and Homework', NULL, 0, 7, medium_priority_id, 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', NULL, 'ca81d57d-b7d2-45fc-adcd-1c926fd93e6c'), + ('3e7db894-b421-46dc-954e-5c38d6270483', 'Grading and Assessment', NULL, 0, 6, medium_priority_id, 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', NULL, 'e6dffb96-6879-4064-ab8f-73ca784b1964'), + ('744e8dff-76b0-4450-8ad3-1d38d137c4df', 'Student Progress Tracking', NULL, 0, 4, medium_priority_id, 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', NULL, 'ca81d57d-b7d2-45fc-adcd-1c926fd93e6c'), + ('f9b8c333-b8d2-45b8-aff1-d2686aecc23d', 'Resource Management', NULL, 0, 0, medium_priority_id, 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', NULL, 'efdb40e7-78ae-4a5a-83ec-d372a5816ae5'), + ('97619dc4-8879-45cc-b17b-bc34db6a658f', 'Research Projects', NULL, 0, 1, medium_priority_id, 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', NULL, 'efdb40e7-78ae-4a5a-83ec-d372a5816ae5'), + ('2e277e0f-b389-41de-8caf-c577245be524', 'Event Planning', NULL, 0, 2, medium_priority_id, 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', NULL, 'efdb40e7-78ae-4a5a-83ec-d372a5816ae5'), + ('c3bb796c-cfe2-47a8-935e-a9176652279d', 'Budget Management', NULL, 0, 3, medium_priority_id, 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', NULL, 'efdb40e7-78ae-4a5a-83ec-d372a5816ae5'), + ('c981c84d-bbe7-48a1-891b-8485b44ba6dc', 'Online Learning Management', NULL, 0, 9, medium_priority_id, 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', NULL, 'e6dffb96-6879-4064-ab8f-73ca784b1964'), + ('adbe95e7-a5df-4741-bf1d-79be33654d43', 'Budget Planning', NULL, 0, 0, medium_priority_id, 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', NULL, '0010ae09-0dd6-4c08-b351-30124e0dc436'), + ('140792fb-d588-4928-8d8a-40641d8982f0', 'Financial Reporting', NULL, 0, 3, medium_priority_id, 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', NULL, '0010ae09-0dd6-4c08-b351-30124e0dc436'), + ('ae43bf0c-32b9-42e7-8c2d-e13921734669', 'Vendor and Supplier Management', NULL, 0, 4, medium_priority_id, 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', NULL, '0010ae09-0dd6-4c08-b351-30124e0dc436'), + ('c76b2ba9-a2f3-4acc-8b23-cbf89f9e858e', 'Invoice Management', NULL, 0, 5, medium_priority_id, 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', NULL, '0010ae09-0dd6-4c08-b351-30124e0dc436'), + ('0079334e-6bbd-45df-8591-444d53df8195', 'Expense Approval Workflow', NULL, 0, 6, medium_priority_id, 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', NULL, '0010ae09-0dd6-4c08-b351-30124e0dc436'), + ('a656409f-48a2-473c-b53f-9431701e9641', 'Tax Planning and Compliance', NULL, 0, 7, medium_priority_id, 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', NULL, '0010ae09-0dd6-4c08-b351-30124e0dc436'), + ('5312ae9f-378d-4aff-ac79-55b401750f82', 'Budget Monitoring', NULL, 0, 1, medium_priority_id, 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', NULL, '0010ae09-0dd6-4c08-b351-30124e0dc436'), + ('d884b9e0-189a-4a2f-9467-92c132295e40', 'Expense Tracking', NULL, 0, 2, medium_priority_id, 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', NULL, '0010ae09-0dd6-4c08-b351-30124e0dc436'), + ('6e33e82f-ac08-4162-9b84-0f98f8153ec4', 'Identify and reach out to potential candidates through various channels', NULL, 0, 2, medium_priority_id, 'f25549d0-d5de-46aa-bbd5-eaab7a970799', NULL, '35430ef9-040f-465f-b5e3-1f33b26124dd'), + ('8f02befa-3671-4a18-bd01-817e2fb8f9c7', 'Define the job position, responsibilities, and requirements.', NULL, 0, 4, medium_priority_id, 'f25549d0-d5de-46aa-bbd5-eaab7a970799', NULL, '35430ef9-040f-465f-b5e3-1f33b26124dd'), + ('f96e15cf-4a99-4f62-a19d-649d890dc364', 'Schedule and conduct interviews with shortlisted candidates.', NULL, 0, 0, medium_priority_id, 'f25549d0-d5de-46aa-bbd5-eaab7a970799', NULL, 'bac101f9-cec3-43aa-8bd8-354de42fdff2'), + ('e1a78723-a98d-4fa9-ba11-3d5b1429796c', 'Contact provided references to validate candidates'' background and skills.', NULL, 0, 1, medium_priority_id, 'f25549d0-d5de-46aa-bbd5-eaab7a970799', NULL, 'bac101f9-cec3-43aa-8bd8-354de42fdff2'), + ('e7ca181d-d348-4745-a7d9-8e8f1abcda77', 'Collect feedback from interviewers and team members', NULL, 0, 5, medium_priority_id, 'f25549d0-d5de-46aa-bbd5-eaab7a970799', NULL, '400b58bd-ad09-4cd1-b5bd-a9f7cb46cb47'), + ('55a7db50-f37d-46b5-be2b-b9938b892ad7', 'Document feedback for future reference and improvement.', NULL, 0, 3, medium_priority_id, 'f25549d0-d5de-46aa-bbd5-eaab7a970799', NULL, '400b58bd-ad09-4cd1-b5bd-a9f7cb46cb47'), + ('52199ccf-bbea-465d-9e0a-192abd5b4ddf', 'Designing, coding, testing, and maintaining software applications and systems.', NULL, 0, 0, medium_priority_id, '22805d87-ba25-4b2a-8384-7a4eeedc3b63', NULL, '8e9cc873-d9a5-404f-9f38-8ca4fba91a0d'), + ('87005eaf-d51c-4876-92ea-5071973de1f6', 'Managing servers and computer systems, including installation, configuration, updates, and maintenance.', NULL, 0, 3, medium_priority_id, '22805d87-ba25-4b2a-8384-7a4eeedc3b63', NULL, '856cd7b1-76b1-4182-b2c8-60c278839913'), + ('64f5c488-7600-4d94-a85b-6fff17ffc746', 'Conducting security audits, vulnerability assessments, and risk management.', NULL, 0, 2, medium_priority_id, '22805d87-ba25-4b2a-8384-7a4eeedc3b63', NULL, 'cbf86a20-8b6d-43d0-99d7-523fb600cdac'), + ('ad7c0cf8-af9c-46e0-b717-492b29ce4411', 'Managing data integrity, security, and backups.', NULL, 0, 1, medium_priority_id, '22805d87-ba25-4b2a-8384-7a4eeedc3b63', NULL, '8e9cc873-d9a5-404f-9f38-8ca4fba91a0d'), + ('71f423c7-0de0-445b-b84f-a507c2a56398', 'Performance Optimization', NULL, 0, 4, medium_priority_id, '22805d87-ba25-4b2a-8384-7a4eeedc3b63', NULL, '8e9cc873-d9a5-404f-9f38-8ca4fba91a0d'), + ('e6a32779-9a8c-4e6c-bf25-0cc33de94f19', 'Privacy and Data Protection', NULL, 0, 6, medium_priority_id, '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', NULL, '54452070-d355-4fb2-b416-e57390d1b668'), + ('43bec431-1290-420e-8dc6-906204a6d25f', 'Regulatory Filings', NULL, 0, 7, medium_priority_id, '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', NULL, '8aac89fc-cf0c-4a4e-889b-97ccd8e7af94'), + ('59c54e42-163b-45ab-a3dc-ce0816f5ed9f', 'Litigation and Dispute Resolution', NULL, 0, 5, medium_priority_id, '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', NULL, '8aac89fc-cf0c-4a4e-889b-97ccd8e7af94'), + ('d82d92a3-54a5-41ac-b77b-c72535bffeb6', 'Contract Management', NULL, 0, 0, medium_priority_id, '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', NULL, '6f0f7261-7f1a-40f3-a771-04fbc5cd0059'), + ('20697a2d-6586-4ffb-901c-529f154c8b43', 'Contract Renewals and Expirations', NULL, 0, 1, medium_priority_id, '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', NULL, '6f0f7261-7f1a-40f3-a771-04fbc5cd0059'), + ('1f8826ea-60a9-4d93-a7f8-96fbe72aef6c', 'Compliance Checks', NULL, 0, 4, medium_priority_id, '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', NULL, '54452070-d355-4fb2-b416-e57390d1b668'), + ('ae28eba7-c9d8-480c-ab42-20e348f0355d', 'Legal Research', NULL, 0, 2, medium_priority_id, '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', NULL, '6f0f7261-7f1a-40f3-a771-04fbc5cd0059'), + ('049681c1-5a04-4cf3-aaff-fdea575daeb6', 'Intellectual Property Management', NULL, 0, 3, medium_priority_id, '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', NULL, '54452070-d355-4fb2-b416-e57390d1b668'), + ('bc5d795c-4d2b-4fe2-938a-5f7758e8c90f', 'Develop detailed product design specifications, including materials, dimensions, and functionality.', NULL, 0, 0, medium_priority_id, 'd1baf96f-07f4-4468-bb4f-44cdedfad965', NULL, '0c3c5259-70d2-407b-8a45-c9b81ac84a3a'), + ('2771231b-9b2d-46c0-ac2d-d34d71f16c09', 'Collaborate with the design and engineering teams to brainstorm innovative ideas for product features and improvements.', NULL, 0, 1, medium_priority_id, 'd1baf96f-07f4-4468-bb4f-44cdedfad965', NULL, '0c3c5259-70d2-407b-8a45-c9b81ac84a3a'), + ('bab7972e-5668-4fdd-9de2-11b6a6788703', 'Conduct market research to assess the viability of proposed product enhancements and innovations.', NULL, 0, 6, medium_priority_id, 'd1baf96f-07f4-4468-bb4f-44cdedfad965', NULL, '0c3c5259-70d2-407b-8a45-c9b81ac84a3a'), + ('75720af5-67cc-493d-88cf-4e373bc579e0', 'Develop detailed manufacturing processes, including workflow, quality control measures, and production schedules.', NULL, 0, 3, medium_priority_id, 'd1baf96f-07f4-4468-bb4f-44cdedfad965', NULL, '0c3c5259-70d2-407b-8a45-c9b81ac84a3a'), + ('3a57acd0-4c0a-4cc8-bf99-0c9f959eaed6', 'Plan and optimize the supply chain, including sourcing raw materials, logistics, and inventory management.', NULL, 0, 2, medium_priority_id, 'd1baf96f-07f4-4468-bb4f-44cdedfad965', NULL, '0c3c5259-70d2-407b-8a45-c9b81ac84a3a'), + ('fc721d7c-6a69-4241-8294-45122584b238', 'Create prototypes of the product to validate the design and manufacturing processes', NULL, 0, 4, medium_priority_id, 'd1baf96f-07f4-4468-bb4f-44cdedfad965', NULL, '0c3c5259-70d2-407b-8a45-c9b81ac84a3a'), + ('0264e8c3-d63f-40ba-906c-e3c090ce02ab', 'Create designs for the molds, tooling, and equipment needed in the manufacturing process.', NULL, 0, 7, medium_priority_id, 'd1baf96f-07f4-4468-bb4f-44cdedfad965', NULL, '0c3c5259-70d2-407b-8a45-c9b81ac84a3a'), + ('b17877ae-0ab0-4ffc-a4fb-49a8c80c0374', 'Perform rigorous testing and validation on prototypes to ensure they meet quality and performance standards.', NULL, 0, 5, medium_priority_id, 'd1baf96f-07f4-4468-bb4f-44cdedfad965', NULL, '0c3c5259-70d2-407b-8a45-c9b81ac84a3a'), + ('c2d154ca-4e56-4d88-9982-8e5587463b29', 'Delivering value to your customers and leads', NULL, 0, 6, medium_priority_id, 'c6917d57-f84a-40fc-aff0-70fd0aab951f', NULL, '1073754d-c498-4e6d-bcee-68bba526e572'), + ('5ccadffe-a860-49b2-be7c-73af5a6f81e9', 'Introducing new products or services', NULL, 0, 3, medium_priority_id, 'c6917d57-f84a-40fc-aff0-70fd0aab951f', NULL, '44d54c08-bce6-44e6-9cfc-23d1b4a9e011'), + ('6e563048-0891-418e-a5f5-28f228f0339b', 'Collecting feedback from customers', NULL, 0, 1, medium_priority_id, 'c6917d57-f84a-40fc-aff0-70fd0aab951f', NULL, '1ae23b11-5edf-485d-9217-f905dc33ddda'), + ('e83926a9-b618-4398-a4ec-9022314df3bd', 'Building marketing strategies and campaigns', NULL, 0, 2, medium_priority_id, 'c6917d57-f84a-40fc-aff0-70fd0aab951f', NULL, '1ae23b11-5edf-485d-9217-f905dc33ddda'), + ('08496b12-c259-49ad-9ceb-6669171c38c3', 'Tracking and monitoring marketing campaigns', NULL, 0, 4, medium_priority_id, 'c6917d57-f84a-40fc-aff0-70fd0aab951f', NULL, '1ae23b11-5edf-485d-9217-f905dc33ddda'), + ('eae2707f-4804-4691-80ba-4f86f71e7b32', 'Creating a strong and dependable brand', NULL, 0, 5, medium_priority_id, 'c6917d57-f84a-40fc-aff0-70fd0aab951f', NULL, '44d54c08-bce6-44e6-9cfc-23d1b4a9e011'), + ('709e726f-02d8-4549-b77a-c38d14054cf3', 'Boosting company sales', NULL, 0, 7, medium_priority_id, 'c6917d57-f84a-40fc-aff0-70fd0aab951f', NULL, '1073754d-c498-4e6d-bcee-68bba526e572'), + ('4f2eacd8-daaf-45f6-9986-de5a9c8e459d', 'Resource Management', NULL, 0, 5, medium_priority_id, '5d1ffd21-a7b7-4a5a-9d19-f7fd3ee253e7', NULL, 'd2bff598-5135-4cac-92aa-641b69ae1dab'), + ('483d6ba0-ce02-4f0f-8f27-d417289939a5', 'Budgeting and Fundraising', NULL, 0, 2, medium_priority_id, '5d1ffd21-a7b7-4a5a-9d19-f7fd3ee253e7', NULL, 'd2bff598-5135-4cac-92aa-641b69ae1dab'), + ('e70c332b-d0f6-403b-b3c0-cc7467487cae', 'Project Planning', NULL, 0, 3, medium_priority_id, '5d1ffd21-a7b7-4a5a-9d19-f7fd3ee253e7', NULL, 'da60606a-ced5-46bf-bf2f-4ee9d5627a9a'), + ('6686a09b-f767-466a-a355-fe8d9a16c28b', 'Task Management', NULL, 0, 4, medium_priority_id, '5d1ffd21-a7b7-4a5a-9d19-f7fd3ee253e7', NULL, '9ec60343-27fe-4af5-a35b-d356ebd61837'), + ('f819d4fc-d3d9-48a8-b06e-92e982686d25', 'Project Initiation', NULL, 0, 0, medium_priority_id, '5d1ffd21-a7b7-4a5a-9d19-f7fd3ee253e7', NULL, 'da60606a-ced5-46bf-bf2f-4ee9d5627a9a'), + ('a224c0dc-d86d-4fdb-9f69-bd59d26f9760', 'Communication and Collaboration', NULL, 0, 1, medium_priority_id, '5d1ffd21-a7b7-4a5a-9d19-f7fd3ee253e7', NULL, '9ec60343-27fe-4af5-a35b-d356ebd61837'), + ('b8766b9d-5ea3-4704-ad6e-88d5f94eb816', 'Home Renovation Project', NULL, 0, 0, medium_priority_id, 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', NULL, '2e14a9e9-4c1a-4596-a0f2-574d2e7b2fa4'), + ('f4a7de5a-7ffd-4ae3-8266-7fe12a75256f', 'Education and Self-Improvement', NULL, 0, 7, medium_priority_id, 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', NULL, '2e14a9e9-4c1a-4596-a0f2-574d2e7b2fa4'), + ('651c7ffe-58d4-4807-aef5-720e58d254d8', 'Event Planning', NULL, 0, 1, medium_priority_id, 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', NULL, '2e14a9e9-4c1a-4596-a0f2-574d2e7b2fa4'), + ('d3c78cb9-72b8-4277-ae12-094ce4be1bbc', 'Fitness and Health Goals', NULL, 0, 2, medium_priority_id, 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', NULL, '1a403c75-5d8c-4752-bf96-e486b8042df6'), + ('619f02b0-7639-42aa-ae39-2808289c713f', 'Vacation Planning', NULL, 0, 3, medium_priority_id, 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', NULL, '1a403c75-5d8c-4752-bf96-e486b8042df6'), + ('edc39164-215e-4a4c-9e91-a4ef1cc17709', 'Home Organization', NULL, 0, 4, medium_priority_id, 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', NULL, '70fd7f98-b278-440a-8a33-d3453bfd4b70'), + ('d7a2898a-887c-4832-af44-4c2bfa8c482f', 'Personal Blog', NULL, 0, 5, medium_priority_id, 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', NULL, '70fd7f98-b278-440a-8a33-d3453bfd4b70'), + ('1510b125-197d-4389-afa9-70d2ee2c610e', 'Financial Goals', NULL, 0, 6, medium_priority_id, 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', NULL, '70fd7f98-b278-440a-8a33-d3453bfd4b70'), + ('a2c8a7ca-8f5f-4fe0-ac47-a186dc8cea25', 'Integration with Existing Systems', NULL, 0, 5, medium_priority_id, 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', NULL, 'b450e432-63f4-46a9-bd4a-26b0bdd37507'), + ('fff9365e-e86f-46d4-8ac3-e90ac6c8f391', 'Initial Contact and Relationship Building', NULL, 0, 4, medium_priority_id, 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', NULL, '291095da-b3d9-40aa-bcd2-5cd6fb51b89f'), + ('8d3df58a-3b57-4269-9f23-a02d8630b001', 'Lead Qualification and Research', NULL, 0, 0, medium_priority_id, 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', NULL, 'c20816ac-fe57-4471-91b0-61fa060663ca'), + ('c1a6dc5c-e952-43e0-bd02-5505e4db72f0', 'Initial Outreach and Engagement', NULL, 0, 1, medium_priority_id, 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', NULL, 'c20816ac-fe57-4471-91b0-61fa060663ca'), + ('7b1ec3b2-5e72-4b4d-87f6-1b158e4b98a4', 'Lost Opportunity Analysis', NULL, 0, 2, medium_priority_id, 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', NULL, 'c20816ac-fe57-4471-91b0-61fa060663ca'), + ('2fd89257-98c7-4bae-9186-961259edd4fd', 'Competitor Analysis and Benchmarking', NULL, 0, 3, medium_priority_id, 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', NULL, '291095da-b3d9-40aa-bcd2-5cd6fb51b89f'), + ('d43541ae-bc9c-49ad-aa5f-097563ca3e2d', 'Post-Sale Follow-Up and Relationship Building', NULL, 0, 6, medium_priority_id, 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', NULL, 'b450e432-63f4-46a9-bd4a-26b0bdd37507'), + ('24ae6a1a-019d-4040-bacc-115be99f7982', 'Testing and Quality Assurance', NULL, 0, 7, medium_priority_id, 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', NULL, 'b450e432-63f4-46a9-bd4a-26b0bdd37507'), + ('1c78e9bd-e869-4486-a891-5dae020f35da', 'Client Onboarding', NULL, 0, 0, medium_priority_id, 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', NULL, '4da7cb15-9767-4522-9950-6a4b26e660a6'), + ('642b2621-16bb-4615-9b4c-4ea6cd32f7e9', 'Project Documentation', NULL, 0, 1, medium_priority_id, 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', NULL, '4da7cb15-9767-4522-9950-6a4b26e660a6'), + ('df901da9-9a6e-4d54-9fbd-e14065dadc40', 'Task Management', NULL, 0, 6, medium_priority_id, 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', NULL, '171e791d-6601-45e7-8c5e-80591a610ed8'), + ('643deb27-9521-4672-9ebc-9349e39607a4', 'Resource Allocation', NULL, 0, 8, medium_priority_id, 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', NULL, '4da7cb15-9767-4522-9950-6a4b26e660a6'), + ('77278afe-54fa-4174-b56f-bf2217aaa8d6', 'Risk Assessment and Management', NULL, 0, 9, medium_priority_id, 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', NULL, 'b4a409f7-4009-4633-b147-f4f99c73f30b'), + ('baa06fdf-495a-42ee-ba1c-026953301487', 'Time and Expense Tracking', NULL, 0, 2, medium_priority_id, 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', NULL, '4da7cb15-9767-4522-9950-6a4b26e660a6'), + ('f80ecf2c-4e52-4957-bb3a-3bd5f98b1017', 'Continuous Improvement', NULL, 0, 3, medium_priority_id, 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', NULL, '4da7cb15-9767-4522-9950-6a4b26e660a6'), + ('f3565c7f-39cd-46b4-bc44-2682d7b48cbc', 'Client Satisfaction and Feedback', NULL, 0, 5, medium_priority_id, 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', NULL, 'b4a409f7-4009-4633-b147-f4f99c73f30b'), + ('2080b4ac-4e00-4e24-9f93-05b54d72f2e9', 'Client Communication', NULL, 0, 4, medium_priority_id, 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', NULL, 'b4a409f7-4009-4633-b147-f4f99c73f30b'), + ('276bf24a-da04-4276-bbb8-206e9b276968', 'Project Planning', NULL, 0, 7, medium_priority_id, 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', NULL, '171e791d-6601-45e7-8c5e-80591a610ed8'), + ('5fd32a4a-4ab0-44ad-873d-459b9062ae6e', 'User Acceptance Testing', NULL, 0, 12, medium_priority_id, '0a769952-e9b8-4e48-b562-f4ebb0f5914e', NULL, '757e7655-22c1-4a40-8f3d-2754fbd564dc'), + ('8f54a3a2-38cc-48d6-85e1-d8fe47eea2f0', 'Requirement Gathering', NULL, 0, 11, medium_priority_id, '0a769952-e9b8-4e48-b562-f4ebb0f5914e', NULL, '757e7655-22c1-4a40-8f3d-2754fbd564dc'), + ('091b0fea-c089-499d-a9e4-8a0eba3bbe47', 'Coding', NULL, 0, 4, medium_priority_id, '0a769952-e9b8-4e48-b562-f4ebb0f5914e', NULL, '757e7655-22c1-4a40-8f3d-2754fbd564dc'), + ('a4b2ead5-9d73-4064-b261-35cfcd0e6f89', 'Unit Testing', NULL, 0, 6, medium_priority_id, '0a769952-e9b8-4e48-b562-f4ebb0f5914e', NULL, '7fae7fde-26a3-44aa-b694-b6d9228e9d6a'), + ('81cab85b-0272-40b2-9163-92973bc0fdfd', 'Continuous Integration/Continuous Deployment (CI/CD)', NULL, 0, 7, medium_priority_id, '0a769952-e9b8-4e48-b562-f4ebb0f5914e', NULL, 'e22f483a-4c87-417c-8c3d-2673c44b1079'), + ('c7b7ac95-5c5a-4dcd-8a59-c8060011f753', 'Code Review', NULL, 0, 10, medium_priority_id, '0a769952-e9b8-4e48-b562-f4ebb0f5914e', NULL, 'e22f483a-4c87-417c-8c3d-2673c44b1079'), + ('914b10d9-622a-4269-b6bc-07c1377fb512', 'Collaboration and Communication', NULL, 0, 9, medium_priority_id, '0a769952-e9b8-4e48-b562-f4ebb0f5914e', NULL, '7fae7fde-26a3-44aa-b694-b6d9228e9d6a'); + + INSERT INTO public.pt_task_phases (task_id, phase_id) + VALUES ('a75993d9-3fb3-4d0b-a5d4-cab53b60462c', '4b4a8fe0-4f35-464a-a337-848e5b432ab5'), + ('3fdb6801-bc09-4d71-8273-987cd3d1e0f6', '557b58ca-3335-4b41-9880-fdd0f990deb9'), + ('ca64f247-a186-4edb-affd-738f1c2a4d60', '62097027-979f-4b00-afb8-f70fba533f80'), + ('1e493de8-38cf-4e6e-8f0b-5e1f6f3b07f4', 'e3128891-4873-4795-ad8a-880474280045'), + ('67b2ab3c-53e5-428c-bbad-8bdc19dc88de', '77204bf3-fcb3-4e39-a843-14458b2f659d'), + ('9311ff84-1052-4989-8192-0fea20204fbe', '62097027-979f-4b00-afb8-f70fba533f80'), + ('7d0697cd-868c-4b41-9f4f-f9a8c1131b24', '62097027-979f-4b00-afb8-f70fba533f80'), + ('f994718d-3f1c-4880-99da-0b38ce90e0b4', '76356365-e420-46b7-b7ff-1747589cb7c9'), + ('5c5fac9e-5016-4e36-b812-1a9c164adca6', 'a0cb92e9-1e44-4fd6-9522-a1ec5d50fd5c'), + ('9127f591-e532-4f88-b61e-2a02fd6a654a', '76356365-e420-46b7-b7ff-1747589cb7c9'), + ('41a6a358-e603-4bf9-9db5-6595f8f00e00', 'a0cb92e9-1e44-4fd6-9522-a1ec5d50fd5c'), + ('c37db3ea-0be7-453b-8588-3672fff523fe', '6872d742-8e8e-43fe-8d3b-031ff23ba32f'), + ('6ecc497e-ce9a-4a5d-a2a6-a951f26223cf', '1bea49a2-ba94-4c3a-acd3-973edfc182c8'), + ('d53a405b-0bcf-4372-a7ea-d4bca9c13740', 'a0cb92e9-1e44-4fd6-9522-a1ec5d50fd5c'), + ('b73154b9-d335-4a3e-8593-84da85801720', '6872d742-8e8e-43fe-8d3b-031ff23ba32f'), + ('5caac736-3ccf-4b0d-ada4-e1cf4f6ef631', '702c5891-85bf-4b11-ab70-4f84557b65be'), + ('44346d7f-fbbd-468a-8c37-e439c268c3e1', '702c5891-85bf-4b11-ab70-4f84557b65be'), + ('870daf76-fedb-4198-b13a-9ac453a73dee', 'a6db279b-61e9-4e3b-9521-dfae7bb1c0bb'), + ('0ebb388d-c7e5-49ce-bf25-e2ce7b9511a1', 'a6db279b-61e9-4e3b-9521-dfae7bb1c0bb'), + ('5d09ae52-7e88-41d6-af82-4efdc55b4af0', 'a6db279b-61e9-4e3b-9521-dfae7bb1c0bb'), + ('675a8efc-17f2-4c9b-bd06-227020ffa688', '8c3d49a2-6057-42fe-a458-f4596dc31eb4'), + ('5403584c-57c4-40f6-8ac5-7c0b89d10370', '42df6764-069f-45e6-8b74-4f1e82e8c1e6'), + ('14de7fa7-e8ac-4d1d-8249-250c733f57c0', '42df6764-069f-45e6-8b74-4f1e82e8c1e6'), + ('188fcdab-ea71-4be3-96f1-cbe224b53310', 'b871bc4f-aa95-4bec-b943-0a518d29019a'), + ('a67b4800-b35d-4448-90ba-e6aa1053ea3f', 'b871bc4f-aa95-4bec-b943-0a518d29019a'), + ('1b5be3a3-ae6d-43f7-8a5a-fa48c96fd54a', '05148a01-26f4-40ef-966d-f7ec601f08e4'), + ('fbe32f14-a8e9-401b-8a39-ad53ebd520fa', '97248076-3eda-434e-8e9d-4b15a9faefca'), + ('1a9bedd1-dbfb-4d25-9b16-ec1eeb773bac', '05148a01-26f4-40ef-966d-f7ec601f08e4'), + ('3e7db894-b421-46dc-954e-5c38d6270483', '97248076-3eda-434e-8e9d-4b15a9faefca'), + ('744e8dff-76b0-4450-8ad3-1d38d137c4df', '37eb54c4-d10c-428b-a2e8-1842de245b56'), + ('f9b8c333-b8d2-45b8-aff1-d2686aecc23d', '37eb54c4-d10c-428b-a2e8-1842de245b56'), + ('97619dc4-8879-45cc-b17b-bc34db6a658f', '8cfe36b7-3d19-45f4-bf2b-ec5ea16efb23'), + ('2e277e0f-b389-41de-8caf-c577245be524', '8cfe36b7-3d19-45f4-bf2b-ec5ea16efb23'), + ('c3bb796c-cfe2-47a8-935e-a9176652279d', 'd642aacb-85ee-4f23-a0af-c1e4ea9f3128'), + ('c981c84d-bbe7-48a1-891b-8485b44ba6dc', 'd642aacb-85ee-4f23-a0af-c1e4ea9f3128'), + ('adbe95e7-a5df-4741-bf1d-79be33654d43', '0d5e5593-76f6-4fd7-b979-40b0ae306ff3'), + ('140792fb-d588-4928-8d8a-40641d8982f0', '290fb77c-b727-43ba-a31d-06cd73ead1cc'), + ('ae43bf0c-32b9-42e7-8c2d-e13921734669', '641997aa-f850-41d9-bbe6-1901cb439c42'), + ('c76b2ba9-a2f3-4acc-8b23-cbf89f9e858e', '641997aa-f850-41d9-bbe6-1901cb439c42'), + ('0079334e-6bbd-45df-8591-444d53df8195', '726d1785-3686-4ff9-bd95-f2f409d915ff'), + ('a656409f-48a2-473c-b53f-9431701e9641', '726d1785-3686-4ff9-bd95-f2f409d915ff'), + ('5312ae9f-378d-4aff-ac79-55b401750f82', '290fb77c-b727-43ba-a31d-06cd73ead1cc'), + ('d884b9e0-189a-4a2f-9467-92c132295e40', '290fb77c-b727-43ba-a31d-06cd73ead1cc'), + ('6e33e82f-ac08-4162-9b84-0f98f8153ec4', '56af064d-df42-49c5-b5e6-438316ee25d7'), + ('8f02befa-3671-4a18-bd01-817e2fb8f9c7', 'aa1888cc-5bc3-4f71-b409-4b5edf4a1627'), + ('f96e15cf-4a99-4f62-a19d-649d890dc364', 'b052092d-d5e2-4ac2-979e-6add568a6781'), + ('e1a78723-a98d-4fa9-ba11-3d5b1429796c', '4b1619ef-d86d-4c18-b163-b959c732830f'), + ('e7ca181d-d348-4745-a7d9-8e8f1abcda77', 'b052092d-d5e2-4ac2-979e-6add568a6781'), + ('55a7db50-f37d-46b5-be2b-b9938b892ad7', '23c1c050-af67-45ef-8a1e-7d7da60a23b9'), + ('52199ccf-bbea-465d-9e0a-192abd5b4ddf', 'f543d067-beff-4c1f-a2b8-203ffe70d4b6'), + ('87005eaf-d51c-4876-92ea-5071973de1f6', '01062648-d3db-4f52-a0bf-970f663f8349'), + ('64f5c488-7600-4d94-a85b-6fff17ffc746', '90670cac-e0c4-4928-a8a3-bda0a55dd8e1'), + ('ad7c0cf8-af9c-46e0-b717-492b29ce4411', '28c1e81c-5169-4f1f-b1ce-f7da05a446a0'), + ('71f423c7-0de0-445b-b84f-a507c2a56398', '01062648-d3db-4f52-a0bf-970f663f8349'), + ('e6a32779-9a8c-4e6c-bf25-0cc33de94f19', 'a2a92231-fb9e-4285-aa8a-abd1f20eaad0'), + ('43bec431-1290-420e-8dc6-906204a6d25f', 'e8275563-8dba-4172-99b9-7c9ab5187e39'), + ('59c54e42-163b-45ab-a3dc-ce0816f5ed9f', '0a49c05e-c642-4235-8a36-03c6f29b7be4'), + ('d82d92a3-54a5-41ac-b77b-c72535bffeb6', 'a2a92231-fb9e-4285-aa8a-abd1f20eaad0'), + ('20697a2d-6586-4ffb-901c-529f154c8b43', 'c51e1c30-a4db-4419-8f65-9fa94496a773'), + ('1f8826ea-60a9-4d93-a7f8-96fbe72aef6c', '0a49c05e-c642-4235-8a36-03c6f29b7be4'), + ('ae28eba7-c9d8-480c-ab42-20e348f0355d', 'c51e1c30-a4db-4419-8f65-9fa94496a773'), + ('049681c1-5a04-4cf3-aaff-fdea575daeb6', '0a49c05e-c642-4235-8a36-03c6f29b7be4'), + ('bc5d795c-4d2b-4fe2-938a-5f7758e8c90f', '590f0b13-461e-43ca-bc10-136f1eeb340c'), + ('2771231b-9b2d-46c0-ac2d-d34d71f16c09', '6ad92c84-c953-4077-b652-39474a8e83c6'), + ('bab7972e-5668-4fdd-9de2-11b6a6788703', '590f0b13-461e-43ca-bc10-136f1eeb340c'), + ('75720af5-67cc-493d-88cf-4e373bc579e0', '912e172c-3a21-4b16-a0da-bf86a1e01719'), + ('3a57acd0-4c0a-4cc8-bf99-0c9f959eaed6', '912e172c-3a21-4b16-a0da-bf86a1e01719'), + ('fc721d7c-6a69-4241-8294-45122584b238', 'a1124508-f464-46e9-85c6-33590d37c913'), + ('0264e8c3-d63f-40ba-906c-e3c090ce02ab', '590f0b13-461e-43ca-bc10-136f1eeb340c'), + ('b17877ae-0ab0-4ffc-a4fb-49a8c80c0374', 'a1124508-f464-46e9-85c6-33590d37c913'), + ('c2d154ca-4e56-4d88-9982-8e5587463b29', 'e36f2219-46be-44ab-8699-0d4c70d5d6d0'), + ('5ccadffe-a860-49b2-be7c-73af5a6f81e9', '9766b4ca-5599-48cb-9b12-83210b49e152'), + ('6e563048-0891-418e-a5f5-28f228f0339b', '3bdc66df-dae8-472b-8820-94326307b6ec'), + ('e83926a9-b618-4398-a4ec-9022314df3bd', '3bdc66df-dae8-472b-8820-94326307b6ec'), + ('08496b12-c259-49ad-9ceb-6669171c38c3', '9766b4ca-5599-48cb-9b12-83210b49e152'), + ('eae2707f-4804-4691-80ba-4f86f71e7b32', '01394483-9074-4ae3-86bc-61fb9e1a0886'), + ('709e726f-02d8-4549-b77a-c38d14054cf3', 'e36f2219-46be-44ab-8699-0d4c70d5d6d0'), + ('4f2eacd8-daaf-45f6-9986-de5a9c8e459d', 'e4c0558b-f275-464d-a531-e56f6728fd4d'), + ('483d6ba0-ce02-4f0f-8f27-d417289939a5', '5110d5e4-f4e8-4f4b-86e4-b93b921cc76b'), + ('e70c332b-d0f6-403b-b3c0-cc7467487cae', '5110d5e4-f4e8-4f4b-86e4-b93b921cc76b'), + ('6686a09b-f767-466a-a355-fe8d9a16c28b', '82361471-13cb-4d55-a48e-ae72d4269dec'), + ('f819d4fc-d3d9-48a8-b06e-92e982686d25', 'eca3fd1f-2c80-4983-92eb-a4969e16c5ec'), + ('a224c0dc-d86d-4fdb-9f69-bd59d26f9760', '5110d5e4-f4e8-4f4b-86e4-b93b921cc76b'), + ('b8766b9d-5ea3-4704-ad6e-88d5f94eb816', '217d01b8-dc31-4ba9-a6f2-ce32369f7797'), + ('f4a7de5a-7ffd-4ae3-8266-7fe12a75256f', '217d01b8-dc31-4ba9-a6f2-ce32369f7797'), + ('651c7ffe-58d4-4807-aef5-720e58d254d8', 'aceff9cb-e679-46b0-b148-a4d15154ddfe'), + ('d3c78cb9-72b8-4277-ae12-094ce4be1bbc', '114a8e9d-a7a6-4445-854c-469208dd9c10'), + ('619f02b0-7639-42aa-ae39-2808289c713f', '114a8e9d-a7a6-4445-854c-469208dd9c10'), + ('edc39164-215e-4a4c-9e91-a4ef1cc17709', 'fa8437aa-2131-4bb8-9361-25d7bab6bde6'), + ('d7a2898a-887c-4832-af44-4c2bfa8c482f', 'aceff9cb-e679-46b0-b148-a4d15154ddfe'), + ('1510b125-197d-4389-afa9-70d2ee2c610e', 'fa8437aa-2131-4bb8-9361-25d7bab6bde6'), + ('a2c8a7ca-8f5f-4fe0-ac47-a186dc8cea25', '7cdafda5-30f1-42c0-8fe2-edcf7522724b'), + ('fff9365e-e86f-46d4-8ac3-e90ac6c8f391', '7cdafda5-30f1-42c0-8fe2-edcf7522724b'), + ('8d3df58a-3b57-4269-9f23-a02d8630b001', '6810f956-cb7e-4689-b717-917246f2ae25'), + ('c1a6dc5c-e952-43e0-bd02-5505e4db72f0', '36b7080d-2875-4dcb-ae8e-eb06468cf2b0'), + ('7b1ec3b2-5e72-4b4d-87f6-1b158e4b98a4', '36b7080d-2875-4dcb-ae8e-eb06468cf2b0'), + ('2fd89257-98c7-4bae-9186-961259edd4fd', '36b7080d-2875-4dcb-ae8e-eb06468cf2b0'), + ('d43541ae-bc9c-49ad-aa5f-097563ca3e2d', '9bf13d98-1393-4e56-87bd-72ff466e6521'), + ('24ae6a1a-019d-4040-bacc-115be99f7982', '9bf13d98-1393-4e56-87bd-72ff466e6521'), + ('1c78e9bd-e869-4486-a891-5dae020f35da', '6dc18765-0b64-4e8d-8ba7-b99c64a3bbc3'), + ('642b2621-16bb-4615-9b4c-4ea6cd32f7e9', '9b0ba3d3-e244-429e-b309-27d24fe5f484'), + ('df901da9-9a6e-4d54-9fbd-e14065dadc40', '9b0ba3d3-e244-429e-b309-27d24fe5f484'), + ('643deb27-9521-4672-9ebc-9349e39607a4', '6dc18765-0b64-4e8d-8ba7-b99c64a3bbc3'), + ('77278afe-54fa-4174-b56f-bf2217aaa8d6', '6dc18765-0b64-4e8d-8ba7-b99c64a3bbc3'), + ('baa06fdf-495a-42ee-ba1c-026953301487', 'bce4184c-1317-454d-badc-da2549493f01'), + ('f80ecf2c-4e52-4957-bb3a-3bd5f98b1017', '1966e857-54d2-4e1b-a22e-e120c96724e6'), + ('f3565c7f-39cd-46b4-bc44-2682d7b48cbc', '1966e857-54d2-4e1b-a22e-e120c96724e6'), + ('2080b4ac-4e00-4e24-9f93-05b54d72f2e9', 'bce4184c-1317-454d-badc-da2549493f01'), + ('276bf24a-da04-4276-bbb8-206e9b276968', 'd33b2210-3ef5-4e3b-9f0e-bf2e83e22fff'), + ('5fd32a4a-4ab0-44ad-873d-459b9062ae6e', '4a7831cc-5b26-470c-8966-4093f3fd9a8a'), + ('8f54a3a2-38cc-48d6-85e1-d8fe47eea2f0', '4a7831cc-5b26-470c-8966-4093f3fd9a8a'), + ('091b0fea-c089-499d-a9e4-8a0eba3bbe47', '039d221e-b71a-4f55-b4a0-ac80e7a382da'), + ('a4b2ead5-9d73-4064-b261-35cfcd0e6f89', '5fe3ea1e-c1c5-4739-8d4f-5595fe47554a'), + ('81cab85b-0272-40b2-9163-92973bc0fdfd', '039d221e-b71a-4f55-b4a0-ac80e7a382da'), + ('c7b7ac95-5c5a-4dcd-8a59-c8060011f753', '74bae823-b725-4523-94d2-bfbfba901586'), + ('914b10d9-622a-4269-b6bc-07c1377fb512', '74bae823-b725-4523-94d2-bfbfba901586'); +END; +$$ LANGUAGE plpgsql; + + +SELECT sys_insert_task_priorities(); +SELECT sys_insert_project_access_levels(); +SELECT sys_insert_task_status_categories(); +SELECT sys_insert_project_statuses(); +SELECT sys_insert_project_healths(); +SELECT sys_insert_project_templates(); + +DROP FUNCTION sys_insert_task_priorities(); +DROP FUNCTION sys_insert_project_access_levels(); +DROP FUNCTION sys_insert_task_status_categories(); +DROP FUNCTION sys_insert_project_statuses(); +DROP FUNCTION sys_insert_project_healths(); +DROP FUNCTION sys_insert_project_templates(); + +INSERT INTO timezones (name, abbrev, utc_offset) +SELECT name, abbrev, utc_offset +FROM pg_timezone_names; diff --git a/worklenz-backend/database/3_system-data.sql b/worklenz-backend/database/3_system-data.sql deleted file mode 100644 index 9a417cb9..00000000 --- a/worklenz-backend/database/3_system-data.sql +++ /dev/null @@ -1,77 +0,0 @@ -CREATE OR REPLACE FUNCTION sys_insert_task_priorities() RETURNS VOID AS -$$ -BEGIN - INSERT INTO task_priorities (name, value, color_code) VALUES ('Low', 0, '#75c997'); - INSERT INTO task_priorities (name, value, color_code) VALUES ('Medium', 1, '#fbc84c'); - INSERT INTO task_priorities (name, value, color_code) VALUES ('High', 2, '#f37070'); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION sys_insert_project_access_levels() RETURNS VOID AS -$$ -BEGIN - INSERT INTO project_access_levels (name, key) - VALUES ('Admin', 'ADMIN'); - INSERT INTO project_access_levels (name, key) - VALUES ('Member', 'MEMBER'); - INSERT INTO project_access_levels (name, key) - VALUES ('Project Manager', 'PROJECT_MANAGER'); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION sys_insert_task_status_categories() RETURNS VOID AS -$$ -BEGIN - INSERT INTO sys_task_status_categories (name, color_code, index, is_todo) - VALUES ('To do', '#a9a9a9', 0, TRUE); - INSERT INTO sys_task_status_categories (name, color_code, index, is_doing) - VALUES ('Doing', '#70a6f3', 1, TRUE); - INSERT INTO sys_task_status_categories (name, color_code, index, is_done) - VALUES ('Done', '#75c997', 2, TRUE); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION sys_insert_project_statuses() RETURNS VOID AS -$$ -BEGIN - INSERT INTO sys_project_statuses (name, color_code, icon, sort_order, is_default) - VALUES ('Cancelled', '#f37070', 'close-circle', 0, FALSE), - ('Blocked', '#cbc8a1', 'stop', 1, FALSE), - ('On Hold', '#cbc8a1', 'stop', 2, FALSE), - ('Proposed', '#cbc8a1', 'clock-circle', 3, TRUE), - ('In Planning', '#cbc8a1', 'clock-circle', 4, FALSE), - ('In Progress', '#80ca79', 'clock-circle', 5, FALSE), - ('Completed', '#80ca79', 'check-circle', 6, FALSE); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION sys_insert_project_healths() RETURNS VOID AS -$$ -BEGIN - INSERT INTO sys_project_healths (name, color_code, sort_order, is_default) - VALUES ('Not Set', '#a9a9a9', 0, TRUE); - INSERT INTO sys_project_healths (name, color_code, sort_order, is_default) - VALUES ('Needs Attention', '#fbc84c', 1, FALSE); - INSERT INTO sys_project_healths (name, color_code, sort_order, is_default) - VALUES ('At Risk', '#f37070', 2, FALSE); - INSERT INTO sys_project_healths (name, color_code, sort_order, is_default) - VALUES ('Good', '#75c997', 3, FALSE); -END; -$$ LANGUAGE plpgsql; - - -SELECT sys_insert_task_priorities(); -SELECT sys_insert_project_access_levels(); -SELECT sys_insert_task_status_categories(); -SELECT sys_insert_project_statuses(); -SELECT sys_insert_project_healths(); - -DROP FUNCTION sys_insert_task_priorities(); -DROP FUNCTION sys_insert_project_access_levels(); -DROP FUNCTION sys_insert_task_status_categories(); -DROP FUNCTION sys_insert_project_statuses(); -DROP FUNCTION sys_insert_project_healths(); - -INSERT INTO timezones (name, abbrev, utc_offset) -SELECT name, abbrev, utc_offset -FROM pg_timezone_names; diff --git a/worklenz-backend/database/3_views.sql b/worklenz-backend/database/3_views.sql new file mode 100644 index 00000000..15e36e23 --- /dev/null +++ b/worklenz-backend/database/3_views.sql @@ -0,0 +1,34 @@ +CREATE OR REPLACE VIEW task_labels_view(name, task_id, label_id) AS +SELECT (SELECT team_labels.name + FROM team_labels + WHERE team_labels.id = task_labels.label_id) AS name, + task_labels.task_id, + task_labels.label_id +FROM task_labels; + +CREATE OR REPLACE VIEW tasks_with_status_view(task_id, parent_task_id, is_todo, is_doing, is_done) AS +SELECT tasks.id AS task_id, + tasks.parent_task_id, + stsc.is_todo, + stsc.is_doing, + stsc.is_done +FROM tasks + JOIN task_statuses ts ON tasks.status_id = ts.id + JOIN sys_task_status_categories stsc ON ts.category_id = stsc.id +WHERE tasks.archived IS FALSE; + +CREATE OR REPLACE VIEW team_member_info_view(avatar_url, email, name, user_id, team_member_id, team_id, active) AS +SELECT u.avatar_url, + COALESCE(u.email, (SELECT email_invitations.email + FROM email_invitations + WHERE email_invitations.team_member_id = team_members.id)) AS email, + COALESCE(u.name, (SELECT email_invitations.name + FROM email_invitations + WHERE email_invitations.team_member_id = team_members.id)) AS name, + u.id AS user_id, + team_members.id AS team_member_id, + team_members.team_id, + team_members.active +FROM team_members + LEFT JOIN users u ON team_members.user_id = u.id; + diff --git a/worklenz-backend/database/4_functions.sql b/worklenz-backend/database/4_functions.sql new file mode 100644 index 00000000..b3af256b --- /dev/null +++ b/worklenz-backend/database/4_functions.sql @@ -0,0 +1,6149 @@ +CREATE OR REPLACE FUNCTION accept_invitation(_email text, _team_member_id uuid, _user_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + IF _team_member_id IS NOT NULL + THEN + UPDATE team_members SET user_id = _user_id WHERE id = _team_member_id; + DELETE FROM email_invitations WHERE email = _email AND team_member_id = _team_member_id; + END IF; + + RETURN JSON_BUILD_OBJECT( + 'email', _email, + 'id', (SELECT id FROM teams WHERE id = (SELECT team_id FROM team_members WHERE id = _team_member_id)) + ); +END; +$$; + +CREATE OR REPLACE FUNCTION activate_team(_team_id uuid, _user_id uuid) RETURNS void + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + UPDATE users + SET active_team =_team_id + WHERE id = _user_id + AND EXISTS(SELECT id FROM team_members WHERE team_id = _team_id AND user_id = _user_id); + + DELETE + FROM email_invitations + WHERE team_id = _team_id + AND team_member_id = + (SELECT id FROM team_members WHERE user_id = _user_id AND team_members.team_id = _team_id); +END +$$; + +CREATE OR REPLACE FUNCTION add_or_remove_pt_task_label(_task_id uuid, _label_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; +BEGIN + IF EXISTS(SELECT task_id FROM cpt_task_labels WHERE task_id = _task_id AND label_id = _label_id) + THEN + DELETE FROM cpt_task_labels WHERE task_id = _task_id AND label_id = _label_id; + ELSE + INSERT INTO cpt_task_labels (task_id, label_id) VALUES (_task_id, _label_id); + END IF; + + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _result + FROM (SELECT cpt_task_labels.label_id AS id, + (SELECT name FROM team_labels WHERE id = cpt_task_labels.label_id) AS name, + (SELECT color_code FROM team_labels WHERE id = cpt_task_labels.label_id) + FROM cpt_task_labels + WHERE task_id = _task_id + ORDER BY name) rec; + + RETURN _result; +END +$$; + +CREATE OR REPLACE FUNCTION add_or_remove_task_label(_task_id uuid, _label_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; +BEGIN + IF EXISTS(SELECT task_id FROM task_labels WHERE task_id = _task_id AND label_id = _label_id) + THEN + DELETE FROM task_labels WHERE task_id = _task_id AND label_id = _label_id; + ELSE + INSERT INTO task_labels (task_id, label_id) VALUES (_task_id, _label_id); + END IF; + + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _result + FROM (SELECT task_labels.label_id AS id, + (SELECT name FROM team_labels WHERE id = task_labels.label_id) AS name, + (SELECT color_code FROM team_labels WHERE id = task_labels.label_id) + FROM task_labels + WHERE task_id = _task_id + ORDER BY name) rec; + + RETURN _result; +END +$$; + +CREATE OR REPLACE FUNCTION assign_or_create_label(_team_id uuid, _task_id uuid, _name text, _color_code text) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _label_id UUID; + _is_new BOOLEAN; +BEGIN + SELECT id FROM team_labels WHERE team_id = _team_id AND LOWER(name) = TRIM(LOWER(_name)) INTO _label_id; + + IF (is_null_or_empty(_label_id) IS TRUE) + THEN + INSERT INTO team_labels (name, team_id, color_code) + VALUES (TRIM(_name), _team_id, _color_code) + RETURNING id INTO _label_id; + _is_new = TRUE; + END IF; + + INSERT INTO task_labels (task_id, label_id) VALUES (_task_id, _label_id); + + RETURN JSON_BUILD_OBJECT('id', _label_id, 'is_new', _is_new); +END; +$$; + +CREATE OR REPLACE FUNCTION assign_or_create_pt_label(_team_id uuid, _task_id uuid, _name text, _color_code text) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _label_id UUID; + _is_new BOOLEAN; +BEGIN + SELECT id FROM team_labels WHERE team_id = _team_id AND LOWER(name) = TRIM(LOWER(_name)) INTO _label_id; + + IF (is_null_or_empty(_label_id) IS TRUE) + THEN + INSERT INTO team_labels (name, team_id, color_code) + VALUES (TRIM(_name), _team_id, _color_code) + RETURNING id INTO _label_id; + _is_new = TRUE; + END IF; + + INSERT INTO cpt_task_labels (task_id, label_id) VALUES (_task_id, _label_id); + + RETURN JSON_BUILD_OBJECT('id', _label_id, 'is_new', _is_new); +END; +$$; + +CREATE OR REPLACE FUNCTION bulk_archive_tasks(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _task JSON; + _output JSON; +BEGIN + FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) + LOOP + -- Archive the parent task + UPDATE tasks + SET archived = ((_body ->> 'type')::TEXT = 'archive') + WHERE id = (_task ->> 'id')::UUID + AND parent_task_id IS NULL; + -- Prevent archiving subtasks + + -- Archive its sub-tasks + UPDATE tasks + SET archived = ((_body ->> 'type')::TEXT = 'archive') + WHERE parent_task_id = (_task ->> 'id')::UUID; + END LOOP; + + RETURN _output; +END; +$$; + +CREATE OR REPLACE FUNCTION bulk_assign_label(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _task JSON; + _label JSON; + _output JSON; +BEGIN + FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) + LOOP + FOR _label IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'labels')::JSON) + LOOP + DELETE + FROM task_labels + WHERE task_id = (_task ->> 'id')::UUID + AND label_id = (_label ->> 'id')::UUID; + INSERT INTO task_labels (task_id, label_id) + VALUES ((_task ->> 'id')::UUID, (_label ->> 'id')::UUID); + END LOOP; + END LOOP; + + RETURN _output; +END; +$$; + +CREATE OR REPLACE FUNCTION bulk_assign_or_create_label(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _task JSON; + _output JSON; +BEGIN + FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) + LOOP + PERFORM assign_or_create_label((_body ->> 'team_id')::UUID, (_task ->> 'id')::UUID, + (_body ->> 'text')::TEXT, (_body ->> 'color')::TEXT); + END LOOP; + RETURN _output; +END; +$$; + +CREATE OR REPLACE FUNCTION bulk_assign_to_me(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _task JSON; + _output JSON; + _project_member JSON; + _team_member_id UUID; + _project_member_id UUID; +BEGIN + SELECT id + FROM team_members + WHERE team_id = (_body ->> 'team_id')::UUID + AND user_id = (_body ->> 'user_id')::UUID + INTO _team_member_id; + + FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) + LOOP + + SELECT id + FROM project_members + WHERE project_id = (_body ->> 'project_id')::UUID + AND project_members.team_member_id = _team_member_id + INTO _project_member_id; + + IF is_null_or_empty(_project_member_id) + THEN + SELECT create_project_member(JSON_BUILD_OBJECT( + 'team_member_id', _team_member_id, + 'team_id', (_body ->> 'team_id')::UUID, + 'project_id', (_body ->> 'project_id')::UUID, + 'user_id', (_body ->> 'user_id')::UUID, + 'access_level', 'MEMBER'::TEXT + )) + INTO _project_member; + _project_member_id = (_project_member ->> 'id')::UUID; + END IF; + + IF NOT EXISTS(SELECT task_id + FROM tasks_assignees + WHERE task_id = (_task ->> 'id')::UUID + AND project_member_id = _project_member_id + AND team_member_id = _team_member_id) + THEN + INSERT INTO tasks_assignees (task_id, project_member_id, team_member_id, assigned_by) + VALUES ((_task ->> 'id')::UUID, _project_member_id, _team_member_id, (_body ->> 'user_id')::UUID); + END IF; + END LOOP; + + RETURN _output; +END; +$$; + +CREATE OR REPLACE FUNCTION bulk_change_tasks_status(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _task JSON; + _output JSON; +BEGIN + FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) + LOOP + UPDATE tasks SET status_id = (_body ->> 'status_id')::UUID WHERE id = (_task ->> 'id')::UUID; + END LOOP; + + RETURN _output; +END; +$$; + +CREATE OR REPLACE FUNCTION bulk_delete_pt_tasks(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _task JSON; + _output JSON; +BEGIN + FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) + LOOP + DELETE FROM cpt_tasks WHERE id = (_task ->> 'id')::UUID; + END LOOP; + + RETURN _output; +END; +$$; + +CREATE OR REPLACE FUNCTION bulk_delete_tasks(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _task JSON; + _output JSON; +BEGIN + FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) + LOOP + DELETE FROM tasks WHERE id = (_task ->> 'id')::UUID; + END LOOP; + + RETURN _output; +END; +$$; + +CREATE OR REPLACE FUNCTION complete_account_setup(_user_id uuid, _team_id uuid, _body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _project_id UUID; + _default_status_id UUID; + _task_id UUID; + _task TEXT; + _members JSON; + _sort_order INT; + _team_member_id UUID; + _project_member_id UUID; +BEGIN + + -- Update team name + UPDATE teams SET name = TRIM((_body ->> 'team_name')::TEXT) WHERE id = _team_id AND user_id = _user_id; + + -- Create the project + INSERT INTO projects (name, team_id, owner_id, color_code, status_id, key) + VALUES ((_body ->> 'project_name')::TEXT, _team_id, _user_id, '#3b7ad4', + (SELECT id FROM sys_project_statuses WHERE is_default IS TRUE), (_body ->> 'key')::TEXT) + RETURNING id INTO _project_id; + + -- Insert task's statuses + INSERT INTO task_statuses (name, project_id, team_id, category_id) + VALUES ('To do', _project_id, _team_id, (SELECT id FROM sys_task_status_categories WHERE is_todo IS TRUE)) + RETURNING id INTO _default_status_id; + + INSERT INTO task_statuses (name, project_id, team_id, category_id) + VALUES ('Doing', _project_id, _team_id, (SELECT id FROM sys_task_status_categories WHERE is_doing IS TRUE)); + + INSERT INTO task_statuses (name, project_id, team_id, category_id) + VALUES ('Done', _project_id, _team_id, (SELECT id FROM sys_task_status_categories WHERE is_done IS TRUE)); + + SELECT id FROM team_members WHERE user_id = _user_id AND team_id = _team_id INTO _team_member_id; + + INSERT INTO project_members (team_member_id, project_access_level_id, project_id, role_id) + VALUES (_team_member_id, (SELECT id FROM project_access_levels WHERE key = 'PROJECT_MANAGER'), + _project_id, + (SELECT id FROM roles WHERE team_id = _team_id AND default_role IS TRUE)) + RETURNING id INTO _project_member_id; + + -- Insert tasks + _sort_order = 1; + FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) + LOOP + INSERT INTO tasks (name, priority_id, project_id, reporter_id, status_id, sort_order) + VALUES (TRIM('"' FROM _task)::TEXT, (SELECT id FROM task_priorities WHERE value = 1), _project_id, _user_id, + _default_status_id, _sort_order) + RETURNING id INTO _task_id; + _sort_order = _sort_order + 1; + + INSERT INTO tasks_assignees (task_id, project_member_id, team_member_id, assigned_by) + VALUES (_task_id, _project_member_id, _team_member_id, _user_id); + END LOOP; + + -- Insert team members if available + IF is_null_or_empty((_body ->> 'team_members')) IS FALSE + THEN + SELECT create_team_member(JSON_BUILD_OBJECT('team_id', _team_id, 'emails', (_body ->> 'team_members'))) + INTO _members; + END IF; + + -- insert default columns for task list + PERFORM insert_task_list_columns(_project_id); + + UPDATE users SET setup_completed = TRUE WHERE id = _user_id; + + -- Update organization name + UPDATE organizations SET organization_name = TRIM((_body ->> 'team_name')::TEXT) WHERE user_id = _user_id; + + --insert user data + INSERT INTO users_data (user_id, organization_name, contact_number, contact_number_secondary, trial_in_progress, + trial_expire_date, subscription_status) + VALUES (_user_id, TRIM((_body ->> 'team_name')::TEXT), NULL, NULL, TRUE, CURRENT_DATE + INTERVAL '14 days', + 'trialing') + ON CONFLICT (user_id) DO UPDATE SET organization_name = TRIM((_body ->> 'team_name')::TEXT); + + RETURN JSON_BUILD_OBJECT('id', _project_id, 'members', _members); +END; +$$; + +CREATE OR REPLACE FUNCTION create_bulk_task_assignees(_team_member_id uuid, _project_id uuid, _task_id uuid, _reporter_user_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _project_member JSON; + _project_member_id UUID; + _team_id UUID; + _user_id UUID; +BEGIN + SELECT id + FROM project_members + WHERE team_member_id = _team_member_id + AND project_id = _project_id + INTO _project_member_id; + + SELECT team_id FROM team_members WHERE id = _team_member_id INTO _team_id; + SELECT user_id FROM team_members WHERE id = _team_member_id INTO _user_id; + + IF is_null_or_empty(_project_member_id) + THEN + SELECT create_project_member(JSON_BUILD_OBJECT( + 'team_member_id', _team_member_id, + 'team_id', _team_id, + 'project_id', _project_id, + 'user_id', _reporter_user_id, + 'access_level', 'MEMBER'::TEXT + )) + INTO _project_member; + _project_member_id = (_project_member ->> 'id')::UUID; + END IF; + + IF NOT EXISTS (SELECT 1 FROM tasks_assignees WHERE task_id = _task_id AND project_member_id = _project_member_id) + THEN + INSERT INTO tasks_assignees (task_id, project_member_id, team_member_id, assigned_by) + VALUES (_task_id, _project_member_id, _team_member_id, _reporter_user_id); + + INSERT INTO task_activity_logs (task_id, team_id, attribute_type, user_id, log_type, old_value, new_value, project_id) + VALUES ( + _task_id, + (SELECT team_id FROM projects WHERE id = (SELECT project_id FROM tasks WHERE id = _task_id)), + 'assignee', + _reporter_user_id, + 'assign', + NULL, + _user_id, + (SELECT project_id FROM tasks WHERE id = _task_id) + ); + + END IF; + + RETURN JSON_BUILD_OBJECT( + 'task_id', _task_id, + 'project_member_id', _project_member_id, + 'team_member_id', _team_member_id, + 'team_id', _team_id, + 'user_id', _user_id + ); +END +$$; + +CREATE OR REPLACE FUNCTION create_home_task(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _task_id UUID; +BEGIN + + INSERT INTO tasks (name, end_date, priority_id, project_id, reporter_id, status_id, sort_order) + VALUES (TRIM((_body ->> 'name')::TEXT), + (_body ->> 'end_date')::TIMESTAMP, + (SELECT id FROM task_priorities WHERE value = 1), + (_body ->> 'project_id')::UUID, + (_body ->> 'reporter_id')::UUID, + + -- This should be came from client side later + (SELECT id + FROM task_statuses + WHERE project_id = (_body ->> 'project_id')::UUID + AND category_id IN (SELECT id FROM sys_task_status_categories WHERE is_todo IS TRUE) + LIMIT 1), + COALESCE((SELECT MAX(sort_order) + 1 FROM tasks WHERE project_id = (_body ->> 'project_id')::UUID), 0)) + RETURNING id INTO _task_id; + + -- RETURN(SELECT id FROM team_members WHERE user_id=(_body ->> 'reporter_id')::UUID AND team_id=(_body ->> 'team_id')::UUID); + + RETURN home_task_form_view_model((_body ->> 'reporter_id')::UUID, (_body ->> 'team_id')::UUID, _task_id, + (_body ->> 'project_id')::UUID); +END; +$$; + +CREATE OR REPLACE FUNCTION create_new_team(_name text, _user_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _owner_id UUID; + _team_id UUID; + _organization_id UUID; + _admin_role_id UUID; + _owner_role_id UUID; + _trimmed_name TEXT; + _trimmed_team_name TEXT; +BEGIN + + _trimmed_team_name = TRIM(_name); + -- get owner id + SELECT user_id INTO _owner_id FROM teams WHERE id = (SELECT active_team FROM users WHERE id = _user_id); + SELECT id INTO _organization_id FROM organizations WHERE user_id = _user_id; + + -- insert team + INSERT INTO teams (name, user_id, organization_id) + VALUES (_trimmed_team_name, _owner_id, _organization_id) + RETURNING id INTO _team_id; + + -- insert default roles + INSERT INTO roles (name, team_id, default_role) VALUES ('Member', _team_id, TRUE); + INSERT INTO roles (name, team_id, admin_role) VALUES ('Admin', _team_id, TRUE) RETURNING id INTO _admin_role_id; + INSERT INTO roles (name, team_id, owner) VALUES ('Owner', _team_id, TRUE) RETURNING id INTO _owner_role_id; + + -- insert team member + INSERT INTO team_members (user_id, team_id, role_id) + VALUES (_owner_id, _team_id, _owner_role_id); + + IF (_user_id <> _owner_id) + THEN + INSERT INTO team_members (user_id, team_id, role_id) + VALUES (_user_id, _team_id, _admin_role_id); + END IF; + + RETURN JSON_BUILD_OBJECT( + 'id', _user_id, + 'name', _trimmed_name, + 'team_id', _team_id + ); +END; +$$; + +CREATE OR REPLACE FUNCTION create_new_team(_name text, _user_id uuid, _current_team_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _owner_id UUID; + _team_id UUID; + _role_id UUID; + _trimmed_team_name TEXT; +BEGIN + + _trimmed_team_name = TRIM(_name); + + -- get owner id + SELECT user_id INTO _owner_id FROM teams WHERE id = (SELECT active_team FROM users WHERE id = _user_id); + + -- insert team + INSERT INTO teams (name, user_id, organization_id) + VALUES (_trimmed_team_name, _owner_id, (SELECT id FROM organizations WHERE user_id = _owner_id)::UUID) + RETURNING id INTO _team_id; + + -- insert default roles + INSERT INTO roles (name, team_id, default_role) VALUES ('Member', _team_id, TRUE); + INSERT INTO roles (name, team_id, admin_role) VALUES ('Admin', _team_id, TRUE); + INSERT INTO roles (name, team_id, owner) VALUES ('Owner', _team_id, TRUE) RETURNING id INTO _role_id; + + -- insert team member + INSERT INTO team_members (user_id, team_id, role_id) + VALUES (_user_id, _team_id, _role_id); + + RETURN JSON_BUILD_OBJECT( + 'id', _user_id, + 'name', _trimmed_team_name, + 'team_id', _team_id + ); +END; +$$; + +CREATE OR REPLACE FUNCTION create_notification(_user_id uuid, _team_id uuid, _task_id uuid, _project_id uuid, _message text) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + IF (_user_id IS NOT NULL AND _team_id IS NOT NULL AND is_null_or_empty(_message) IS FALSE) + THEN + INSERT INTO user_notifications (message, user_id, team_id, task_id, project_id) + VALUES (TRIM(_message), _user_id, _team_id, _task_id, _project_id); + END IF; + + RETURN JSON_BUILD_OBJECT( + 'project', (SELECT name FROM projects WHERE id = _project_id), + 'project_color', (SELECT color_code FROM projects WHERE id = _project_id), + 'team', (SELECT name FROM teams WHERE id = _team_id) + ); +END +$$; + +CREATE OR REPLACE FUNCTION create_project(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _user_id UUID; + _team_id UUID; + _client_id UUID; + _project_id UUID; + _client_name TEXT; + _project_name TEXT; + _team_member_id UUID; +BEGIN + -- need a test, can be throw errors + _client_name = TRIM((_body ->> 'client_name')::TEXT); + _project_name = TRIM((_body ->> 'name')::TEXT); + + -- add inside the controller + _user_id = (_body ->> 'user_id')::UUID; + _team_id = (_body ->> 'team_id')::UUID; + + -- cache exists client if exists + SELECT id FROM clients WHERE LOWER(name) = LOWER(_client_name) AND team_id = _team_id INTO _client_id; + SELECT id FROM team_members WHERE team_id = _team_id AND user_id = _user_id INTO _team_member_id; + + -- check whether the project name is already in + IF EXISTS(SELECT name + FROM projects + WHERE LOWER(name) = LOWER(_project_name) + AND team_id = _team_id) + THEN + RAISE 'PROJECT_EXISTS_ERROR:%', _project_name; + END IF; + + -- insert client if not exists + IF is_null_or_empty(_client_id) IS TRUE AND is_null_or_empty(_client_name) IS FALSE + THEN + INSERT INTO clients (name, team_id) VALUES (_client_name, _team_id) RETURNING id INTO _client_id; + END IF; + + -- insert project + INSERT INTO projects (name, key, notes, color_code, team_id, client_id, owner_id, status_id, health_id, start_date, + end_date, + folder_id, category_id, estimated_working_days, estimated_man_days, hours_per_day) + VALUES (_project_name, (_body ->> 'key')::TEXT, (_body ->> 'notes')::TEXT, (_body ->> 'color_code')::TEXT, _team_id, + _client_id, + _user_id, (_body ->> 'status_id')::UUID, (_body ->> 'health_id')::UUID, + (_body ->> 'start_date')::TIMESTAMPTZ, + (_body ->> 'end_date')::TIMESTAMPTZ, (_body ->> 'folder_id')::UUID, (_body ->> 'category_id')::UUID, + (_body ->> 'working_days')::INTEGER, (_body ->> 'man_days')::INTEGER, (_body ->> 'hours_per_day')::INTEGER) + RETURNING id INTO _project_id; + + -- log record + INSERT INTO project_logs (team_id, project_id, description) + VALUES (_team_id, _project_id, + REPLACE((_body ->> 'project_created_log')::TEXT, '@user', + (SELECT name FROM users WHERE id = _user_id))); + + -- insert the project creator as a project member + INSERT INTO project_members (team_member_id, project_access_level_id, project_id, role_id) + VALUES (_team_member_id, (SELECT id FROM project_access_levels WHERE key = 'ADMIN'), + _project_id, + (SELECT id FROM roles WHERE team_id = _team_id AND default_role IS TRUE)); + + -- insert statuses + INSERT INTO task_statuses (name, project_id, team_id, category_id, sort_order) + VALUES ('To Do', _project_id, _team_id, (SELECT id FROM sys_task_status_categories WHERE is_todo IS TRUE), 0); + INSERT INTO task_statuses (name, project_id, team_id, category_id, sort_order) + VALUES ('Doing', _project_id, _team_id, (SELECT id FROM sys_task_status_categories WHERE is_doing IS TRUE), 1); + INSERT INTO task_statuses (name, project_id, team_id, category_id, sort_order) + VALUES ('Done', _project_id, _team_id, (SELECT id FROM sys_task_status_categories WHERE is_done IS TRUE), 2); + + -- insert default columns for task list + PERFORM insert_task_list_columns(_project_id); + + RETURN JSON_BUILD_OBJECT( + 'id', _project_id, + 'name', (_body ->> 'name')::TEXT + ); +END; +$$; + +CREATE OR REPLACE FUNCTION create_project_comment(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _project_id UUID; + _created_by UUID; + _comment_id UUID; + _team_id UUID; + _user_name TEXT; + _project_name TEXT; + _content TEXT; + _mention_index INT := 0; + _mention JSON; +BEGIN + _project_id = (_body ->> 'project_id'); + _created_by = (_body ->> 'created_by'); + _content = (_body ->> 'content'); + _team_id = (_body ->> 'team_id'); + + SELECT name FROM users WHERE id = _created_by LIMIT 1 INTO _user_name; + SELECT name FROM projects WHERE id = _project_id INTO _project_name; + + INSERT INTO project_comments (content, created_by, project_id) + VALUES (_content, _created_by, _project_id) + RETURNING id INTO _comment_id; + + FOR _mention IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'mentions')::JSON) + LOOP + + INSERT INTO project_comment_mentions (comment_id, mentioned_index, mentioned_by, informed_by) + VALUES (_comment_id, _mention_index, _created_by, (_mention ->> 'id')::UUID); + + PERFORM create_notification( + (SELECT id FROM users WHERE id = (_mention ->> 'id')::UUID), + (_team_id)::UUID, + null, + (_project_id)::UUID, + CONCAT('', _user_name, ' has mentioned you in a comment on ', _project_name, '') + ); + _mention_index := _mention_index + 1; + + END LOOP; + + RETURN JSON_BUILD_OBJECT( + 'id', (_comment_id)::UUID, + 'content', (_content)::TEXT, + 'project_name', (_project_name)::TEXT, + 'team_name', (SELECT name FROM teams WHERE id = (_team_id)::UUID) + ); +END +$$; + +CREATE OR REPLACE FUNCTION create_project_member(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _id UUID; + _team_member_id UUID; + _team_id UUID; + _project_id UUID; + _user_id UUID; + _member_user_id UUID; + _notification TEXT; + _access_level TEXT; +BEGIN + _team_member_id = (_body ->> 'team_member_id')::UUID; + _team_id = (_body ->> 'team_id')::UUID; + _project_id = (_body ->> 'project_id')::UUID; + _user_id = (_body ->> 'user_id')::UUID; + _access_level = (_body ->> 'access_level')::TEXT; + + SELECT user_id FROM team_members WHERE id = _team_member_id INTO _member_user_id; + + INSERT INTO project_members (team_member_id, project_access_level_id, project_id, role_id) + VALUES (_team_member_id, (SELECT id FROM project_access_levels WHERE key = _access_level)::UUID, + _project_id, + (SELECT id FROM roles WHERE team_id = _team_id AND default_role IS TRUE)) + RETURNING id INTO _id; + + IF (_member_user_id != _user_id) + THEN + _notification = CONCAT('You have been added to the ', + (SELECT name FROM projects WHERE id = _project_id), + ' by ', + (SELECT name FROM users WHERE id = _user_id), ''); + PERFORM create_notification( + (SELECT user_id FROM team_members WHERE id = _team_member_id), + _team_id, + NULL, + _project_id, + _notification + ); + END IF; + + RETURN JSON_BUILD_OBJECT( + 'id', _id, + 'notification', _notification, + 'socket_id', (SELECT socket_id FROM users WHERE id = _member_user_id), + 'project', (SELECT name FROM projects WHERE id = _project_id), + 'project_id', _project_id, + 'project_color', (SELECT color_code FROM projects WHERE id = _project_id), + 'team', (SELECT name FROM teams WHERE id = _team_id), + 'member_user_id', _member_user_id + ); +END +$$; + +CREATE OR REPLACE FUNCTION create_project_template(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _template_id UUID; +BEGIN + -- check whether the project name is already in + IF EXISTS(SELECT name + FROM custom_project_templates + WHERE LOWER(name) = LOWER((_body ->> 'name')::TEXT) + AND team_id = (_body ->> 'team_id')::uuid) + THEN + RAISE 'TEMPLATE_EXISTS_ERROR:%', (_body ->> 'name')::TEXT; + END IF; + + -- insert client if not exists + INSERT INTO custom_project_templates(name, phase_label, color_code, notes, team_id) + VALUES ((_body ->> 'name')::TEXT, (_body ->> 'phase_label')::TEXT, (_body ->> 'color_code')::TEXT, + (_body ->> 'notes')::TEXT, + (_body ->> 'team_id')::uuid) + RETURNING id INTO _template_id; + + RETURN JSON_BUILD_OBJECT('id', _template_id); +END; +$$; + +CREATE OR REPLACE FUNCTION create_pt_task_status(_body json, _team_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _status_id UUID; + _group_status JSON; +BEGIN + INSERT INTO cpt_task_statuses (name, template_id, team_id, category_id, sort_order) + VALUES (TRIM((_body ->> 'name')::TEXT), + (_body ->> 'template_id')::UUID, + _team_id, + (_body ->> 'category_id')::UUID, + COALESCE((SELECT MAX(sort_order) + 1 + FROM cpt_task_statuses + WHERE template_id = (_body ->> 'template_id') ::UUID), + 0)) RETURNING id INTO _status_id; + SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))) + FROM (SELECT id, + name, + template_id, + team_id, + category_id, + sort_order, + (SELECT color_code + FROM sys_task_status_categories + WHERE id = + (SELECT category_id FROM cpt_task_statuses WHERE id = _status_id)) AS color_code + FROM cpt_task_statuses + WHERE id = _status_id) rec INTO _group_status; + RETURN _group_status; +END; +$$; + +CREATE OR REPLACE FUNCTION create_quick_pt_task(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _task_id UUID; + _parent_task UUID; + _status_id UUID; + _priority_id UUID; +BEGIN + + _parent_task = (_body ->> 'parent_task_id')::UUID; + _status_id = COALESCE( + (_body ->> 'status_id')::UUID, + (SELECT id + FROM cpt_task_statuses + WHERE template_id = (_body ->> 'template_id')::UUID + AND category_id IN (SELECT id FROM sys_task_status_categories WHERE is_todo IS TRUE) + LIMIT 1) + ); + _priority_id = COALESCE((_body ->> 'priority_id')::UUID, (SELECT id FROM task_priorities WHERE value = 1)); + + INSERT INTO cpt_tasks(name, priority_id, template_id, status_id, parent_task_id, sort_order, task_no) + VALUES (TRIM((_body ->> 'name')::TEXT), + _priority_id, + (_body ->> 'template_id')::UUID, + + -- This should be came from client side later + _status_id, _parent_task, + COALESCE((SELECT MAX(sort_order) + 1 FROM cpt_tasks WHERE template_id = (_body ->> 'template_id')::UUID), + 0), ((SELECT COUNT(*) FROM cpt_tasks WHERE template_id = (_body ->> 'template_id')::UUID) + 1)) + RETURNING id INTO _task_id; + + PERFORM handle_on_pt_task_phase_change(_task_id, (_body ->> 'phase_id')::UUID); + + RETURN get_single_pt_task(_task_id); +END; +$$; + +CREATE OR REPLACE FUNCTION create_quick_task(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _task_id UUID; + _parent_task UUID; + _status_id UUID; + _priority_id UUID; + _start_date TIMESTAMP; + _end_date TIMESTAMP; +BEGIN + + _parent_task = (_body ->> 'parent_task_id')::UUID; + _status_id = COALESCE( + (_body ->> 'status_id')::UUID, + (SELECT id + FROM task_statuses + WHERE project_id = (_body ->> 'project_id')::UUID + AND category_id IN (SELECT id FROM sys_task_status_categories WHERE is_todo IS TRUE) + LIMIT 1) + ); + _priority_id = COALESCE((_body ->> 'priority_id')::UUID, (SELECT id FROM task_priorities WHERE value = 1)); + _start_date = (_body ->> 'start_date')::TIMESTAMP; + _end_date = (_body ->> 'end_date')::TIMESTAMP; + + INSERT INTO tasks (name, priority_id, project_id, reporter_id, status_id, parent_task_id, sort_order, start_date, end_date) + VALUES (TRIM((_body ->> 'name')::TEXT), + _priority_id, + (_body ->> 'project_id')::UUID, + (_body ->> 'reporter_id')::UUID, + + -- This should be came from client side later + _status_id, _parent_task, + COALESCE((SELECT MAX(sort_order) + 1 FROM tasks WHERE project_id = (_body ->> 'project_id')::UUID), 0), + (_body ->> 'start_date')::TIMESTAMP, + (_body ->> 'end_date')::TIMESTAMP) + RETURNING id INTO _task_id; + + PERFORM handle_on_task_phase_change(_task_id, (_body ->> 'phase_id')::UUID); + + RETURN get_single_task(_task_id); +END; +$$; + +CREATE OR REPLACE FUNCTION create_task(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _assignee TEXT; + _attachment_id TEXT; + _assignee_id UUID; + _task_id UUID; + _label JSON; +BEGIN + INSERT INTO tasks (name, done, priority_id, project_id, reporter_id, start_date, end_date, total_minutes, + description, parent_task_id, status_id, sort_order) + VALUES (TRIM((_body ->> 'name')::TEXT), (FALSE), + COALESCE((_body ->> 'priority_id')::UUID, (SELECT id FROM task_priorities WHERE value = 1)), + (_body ->> 'project_id')::UUID, + (_body ->> 'reporter_id')::UUID, + (_body ->> 'start')::TIMESTAMPTZ, + (_body ->> 'end')::TIMESTAMPTZ, + (_body ->> 'total_minutes')::NUMERIC, + (_body ->> 'description')::TEXT, + (_body ->> 'parent_task_id')::UUID, + (_body ->> 'status_id')::UUID, + COALESCE((SELECT MAX(sort_order) + 1 FROM tasks WHERE project_id = (_body ->> 'project_id')::UUID), 0)) + RETURNING id INTO _task_id; + + -- insert task assignees + FOR _assignee IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'assignees')::JSON) + LOOP + _assignee_id = TRIM('"' FROM _assignee)::UUID; + PERFORM create_task_assignee(_assignee_id, (_body ->> 'project_id')::UUID, _task_id, + (_body ->> 'reporter_id')::UUID); + END LOOP; + + FOR _attachment_id IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'attachments')::JSON) + LOOP + UPDATE task_attachments SET task_id = _task_id WHERE id = TRIM('"' FROM _attachment_id)::UUID; + END LOOP; + + FOR _label IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'labels')::JSON) + LOOP + PERFORM assign_or_create_label((_body ->> 'team_id')::UUID, _task_id, (_label ->> 'name')::TEXT, + (_label ->> 'color')::TEXT); + END LOOP; + + RETURN get_task_form_view_model((_body ->> 'reporter_id')::UUID, (_body ->> 'team_id')::UUID, _task_id, + (_body ->> 'project_id')::UUID); +END; +$$; + +CREATE OR REPLACE FUNCTION create_task_assignee(_team_member_id uuid, _project_id uuid, _task_id uuid, _reporter_user_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _project_member JSON; + _project_member_id UUID; + _team_id UUID; + _user_id UUID; +BEGIN + SELECT id + FROM project_members + WHERE team_member_id = _team_member_id + AND project_id = _project_id + INTO _project_member_id; + + SELECT team_id FROM team_members WHERE id = _team_member_id INTO _team_id; + SELECT user_id FROM team_members WHERE id = _team_member_id INTO _user_id; + + IF is_null_or_empty(_project_member_id) + THEN + SELECT create_project_member(JSON_BUILD_OBJECT( + 'team_member_id', _team_member_id, + 'team_id', _team_id, + 'project_id', _project_id, + 'user_id', _reporter_user_id, + 'access_level', 'MEMBER'::TEXT + )) + INTO _project_member; + _project_member_id = (_project_member ->> 'id')::UUID; + END IF; + + INSERT INTO tasks_assignees (task_id, project_member_id, team_member_id, assigned_by) + VALUES (_task_id, _project_member_id, _team_member_id, _reporter_user_id); + + RETURN JSON_BUILD_OBJECT( + 'task_id', _task_id, + 'project_member_id', _project_member_id, + 'team_member_id', _team_member_id, + 'team_id', _team_id, + 'user_id', _user_id + ); +END +$$; + +CREATE OR REPLACE FUNCTION create_task_comment(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _task_id UUID; + _user_id UUID; + _comment_id UUID; + _team_member_id UUID; + _mentioned_member_id TEXT; + _user_name TEXT; + _task_name TEXT; + _mention_index INT := 0; + _mention JSON; +BEGIN + + _task_id = (_body ->> 'task_id')::UUID; + _user_id = (_body ->> 'user_id')::UUID; + + SELECT name FROM users WHERE id = _user_id LIMIT 1 INTO _user_name; + SELECT name FROM tasks WHERE id = _task_id INTO _task_name; + + SELECT id + FROM team_members + WHERE user_id = _user_id + AND team_id = (_body ->> 'team_id')::UUID + INTO _team_member_id; + + INSERT INTO task_comments (user_id, team_member_id, task_id) + VALUES (_user_id, _team_member_id, _task_id) + RETURNING id INTO _comment_id; + + INSERT INTO task_comment_contents (index, comment_id, text_content) + VALUES (0, _comment_id, (_body ->> 'content')::TEXT); + + -- notify mentions + FOR _mention IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'mentions')::JSON) + LOOP + INSERT INTO task_comment_mentions (comment_id, mentioned_index, mentioned_by, informed_by) + VALUES (_comment_id, _mention_index, _user_id, (_mention ->> 'team_member_id')::UUID); + PERFORM create_notification( + (SELECT user_id FROM team_members WHERE id = TRIM(BOTH '"' FROM (_mention ->> 'team_member_id'))::UUID), + (_body ->> 'team_id')::UUID, + _task_id, + (SELECT project_id FROM tasks WHERE id = _task_id), + CONCAT('', _user_name, ' has mentioned you in a comment on ', _task_name, '') + ); + _mention_index := _mention_index + 1; + END LOOP; + + RETURN JSON_BUILD_OBJECT( + 'id', _comment_id, + 'content', (_body ->> 'content')::TEXT, + 'task_name', _task_name, + 'project_id', (SELECT project_id FROM tasks WHERE id = _task_id), + 'project_name', (SELECT name FROM projects WHERE id = (SELECT project_id FROM tasks WHERE id = _task_id)), + 'team_name', (SELECT name FROM teams WHERE id = (_body ->> 'team_id')::UUID) + ); +END +$$; + +CREATE OR REPLACE FUNCTION create_task_status(_body json, _team_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _status_id UUID; + _group_status JSON; +BEGIN + + INSERT INTO task_statuses (name, project_id, team_id, category_id, sort_order) + VALUES (TRIM((_body ->> 'name')::TEXT), + (_body ->> 'project_id')::UUID, + _team_id, + (_body ->> 'category_id')::UUID, + COALESCE((SELECT MAX(sort_order) + 1 FROM task_statuses WHERE project_id = (_body ->> 'project_id')::UUID), + 0)) + RETURNING id INTO _status_id; + + SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))) + FROM (SELECT id, + name, + project_id, + team_id, + category_id, + sort_order, + (SELECT color_code + FROM sys_task_status_categories + WHERE id = + (SELECT category_id FROM task_statuses WHERE id = _status_id)) AS color_code + FROM task_statuses + WHERE id = _status_id) rec + INTO _group_status; + + RETURN _group_status; + +END; +$$; + +CREATE OR REPLACE FUNCTION create_task_template(_name text, _team_id uuid, _tasks json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _template_id UUID; + _task JSON; +BEGIN + + -- check whether the project name is already in + IF EXISTS( + SELECT name FROM task_templates WHERE LOWER(name) = LOWER(_name) + AND team_id = _team_id + ) + THEN + RAISE 'TASK_TEMPLATE_EXISTS_ERROR:%', _name; + END IF; + + INSERT INTO task_templates (name, team_id) VALUES (_name, _team_id) RETURNING id INTO _template_id; + + -- insert tasks for task templates + FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS(_tasks) + LOOP + INSERT INTO task_templates_tasks (template_id, name, total_minutes) VALUES (_template_id, (_task ->> 'name')::TEXT, (SELECT total_minutes FROM tasks WHERE id = (_task ->> 'id')::UUID)::NUMERIC); + END LOOP; + + RETURN JSON_BUILD_OBJECT( + 'id', _template_id, + 'template_name', _name + ); +END +$$; + +CREATE OR REPLACE FUNCTION create_team_member(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _team_id UUID; + _user_id UUID; + _job_title_id UUID; + _team_member_id UUID; + _role_id UUID; + _email TEXT; + _output JSON; +BEGIN + _team_id = (_body ->> 'team_id')::UUID; + + IF ((_body ->> 'is_admin')::BOOLEAN IS TRUE) + THEN + SELECT id FROM roles WHERE team_id = _team_id AND admin_role IS TRUE INTO _role_id; + ELSE + SELECT id FROM roles WHERE team_id = _team_id AND default_role IS TRUE INTO _role_id; + END IF; + + IF is_null_or_empty((_body ->> 'job_title')) IS FALSE + THEN + SELECT insert_job_title((_body ->> 'job_title')::TEXT, _team_id) INTO _job_title_id; + ELSE + _job_title_id = NULL; + END IF; + + CREATE TEMPORARY TABLE temp_new_team_members ( + name TEXT, + email TEXT, + is_new BOOLEAN, + team_member_id UUID, + team_member_user_id UUID + ); + + FOR _email IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'emails')::JSON) + LOOP + + _email = LOWER(TRIM('"' FROM _email)::TEXT); + + SELECT id FROM users WHERE email = _email INTO _user_id; + + INSERT INTO team_members (job_title_id, user_id, team_id, role_id) + VALUES (_job_title_id, _user_id, _team_id, _role_id) + RETURNING id INTO _team_member_id; + + IF EXISTS(SELECT id + FROM email_invitations + WHERE email = _email + AND team_id = _team_id) + THEN + -- DELETE +-- FROM team_members +-- WHERE id = (SELECT team_member_id +-- FROM email_invitations +-- WHERE email = _email +-- AND team_id = _team_id); +-- DELETE FROM email_invitations WHERE team_id = _team_id AND email = _email; + + DELETE FROM email_invitations WHERE email = _email AND team_id = _team_id; + +-- RAISE 'ERROR_EMAIL_INVITATION_EXISTS:%', _email; + END IF; + + INSERT INTO email_invitations(team_id, team_member_id, email, name) + VALUES (_team_id, _team_member_id, _email, SPLIT_PART(_email, '@', 1)); + + INSERT INTO temp_new_team_members (is_new, team_member_id, team_member_user_id, name, email) + VALUES ((is_null_or_empty(_user_id)), _team_member_id, _user_id, + (SELECT name FROM users WHERE id = _user_id), _email); + END LOOP; + + SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))) + FROM (SELECT * FROM temp_new_team_members) rec + INTO _output; + + DROP TABLE temp_new_team_members; + + RETURN _output; +END; +$$; + +CREATE OR REPLACE FUNCTION delete_user(_id uuid) RETURNS void + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN +-- SET SESSION_REPLICATION_ROLE = replica; + + UPDATE users SET active_team = NULL WHERE id = _id; + DELETE FROM notification_settings WHERE user_id = _id; + DELETE FROM teams WHERE user_id = _id; + DELETE FROM roles WHERE team_id IN (SELECT id FROM teams WHERE user_id = _id); + DELETE + FROM tasks_assignees + WHERE project_member_id IN + (SELECT id FROM project_members WHERE team_member_id IN (SELECT id FROM team_members WHERE user_id = _id)); + DELETE FROM project_members WHERE team_member_id IN (SELECT id FROM team_members WHERE user_id = _id); + DELETE FROM team_members WHERE user_id = _id; + DELETE FROM job_titles WHERE team_id = (SELECT id FROM teams WHERE user_id = _id); + DELETE + FROM tasks + WHERE project_id IN (SELECT id FROM projects WHERE team_id = (SELECT id FROM teams WHERE user_id = _id)); + DELETE FROM projects WHERE team_id = (SELECT id FROM teams WHERE user_id = _id); + DELETE FROM clients WHERE team_id = (SELECT id FROM teams WHERE user_id = _id); + DELETE FROM teams WHERE user_id = _id; + DELETE FROM personal_todo_list WHERE user_id = _id; + DELETE FROM user_notifications WHERE user_id = _id; + UPDATE users SET active_team = NULL WHERE id = _id; + DELETE FROM users WHERE id = _id; + +-- SET SESSION_REPLICATION_ROLE = default; +END; +$$; + +CREATE OR REPLACE FUNCTION deserialize_user(_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; + _team_id UUID; +BEGIN + + SELECT active_team FROM users WHERE id = _id INTO _team_id; + IF NOT EXISTS(SELECT 1 FROM notification_settings WHERE team_id = _team_id AND user_id = _id) + THEN + INSERT INTO notification_settings (popup_notifications_enabled, show_unread_items_count, user_id, team_id) + VALUES (TRUE, TRUE, _id, _team_id); + END IF; + + SELECT ROW_TO_JSON(rec) + INTO _result + FROM (SELECT users.id, + users.name, + users.email, + users.timezone_id AS timezone, + (SELECT name FROM timezones WHERE id = users.timezone_id) AS timezone_name, + users.avatar_url, + users.user_no, + users.socket_id, + users.created_at AS joined_date, + users.updated_at AS last_updated, + + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM (SELECT description, type FROM worklenz_alerts WHERE active is TRUE) rec) AS alerts, + + (SELECT email_notifications_enabled + FROM notification_settings + WHERE user_id = users.id + AND team_id = t.id) AS email_notifications_enabled, + (CASE + WHEN is_owner(users.id, users.active_team) THEN users.setup_completed + ELSE TRUE END) AS setup_completed, + users.setup_completed AS my_setup_completed, + (is_null_or_empty(users.google_id) IS FALSE) AS is_google, + t.name AS team_name, + t.id AS team_id, + (SELECT id + FROM team_members + WHERE team_members.user_id = _id + AND team_id = users.active_team + AND active IS TRUE) AS team_member_id, + is_owner(users.id, users.active_team) AS owner, + is_admin(users.id, users.active_team) AS is_admin, + t.user_id AS owner_id, + ud.subscription_status, + (SELECT CASE + WHEN (ud.subscription_status) = 'trialing' + THEN (trial_expire_date)::DATE + WHEN (EXISTS(SELECT id FROM licensing_custom_subs WHERE user_id = t.user_id)) + THEN (SELECT end_date FROM licensing_custom_subs lcs WHERE lcs.user_id = t.user_id)::DATE + WHEN EXISTS (SELECT 1 + FROM licensing_user_subscriptions + WHERE user_id = t.user_id AND active IS TRUE) + THEN (SELECT (next_bill_date)::DATE - INTERVAL '1 day' + FROM licensing_user_subscriptions + WHERE user_id = t.user_id)::DATE + END) AS valid_till_date + FROM users + INNER JOIN teams t + ON t.id = COALESCE(users.active_team, + (SELECT id FROM teams WHERE teams.user_id = users.id LIMIT 1)) + LEFT JOIN organizations ud ON ud.user_id = t.user_id + WHERE users.id = _id) rec; + + RETURN _result; +END +$$; + +CREATE OR REPLACE FUNCTION get_activity_logs_by_task(_task_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; +BEGIN + SELECT ROW_TO_JSON(rec) + INTO _result + FROM (SELECT (SELECT tasks.created_at FROM tasks WHERE tasks.id = _task_id), + (SELECT name + FROM users + WHERE id = (SELECT reporter_id FROM tasks WHERE id = _task_id)), + (SELECT avatar_url + FROM users + WHERE id = (SELECT reporter_id FROM tasks WHERE id = _task_id)), + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec2))), '[]'::JSON) + FROM (SELECT task_id, + created_at, + attribute_type, + log_type, + + -- new case, + (CASE + WHEN (attribute_type = 'status') + THEN (SELECT name FROM task_statuses WHERE id = old_value::UUID) + WHEN (attribute_type = 'priority') + THEN (SELECT name FROM task_priorities WHERE id = old_value::UUID) + WHEN (attribute_type = 'phase' AND old_value <> 'Unmapped') + THEN (SELECT name FROM project_phases WHERE id = old_value::UUID) + ELSE (old_value) END) AS previous, + + -- new case + (CASE + WHEN (attribute_type = 'assignee') + THEN (SELECT name FROM users WHERE id = new_value::UUID) + WHEN (attribute_type = 'label') + THEN (SELECT name FROM team_labels WHERE id = new_value::UUID) + WHEN (attribute_type = 'status') + THEN (SELECT name FROM task_statuses WHERE id = new_value::UUID) + WHEN (attribute_type = 'priority') + THEN (SELECT name FROM task_priorities WHERE id = new_value::UUID) + WHEN (attribute_type = 'phase' AND new_value <> 'Unmapped') + THEN (SELECT name FROM project_phases WHERE id = new_value::UUID) + ELSE (new_value) END) AS current, + + -- new case + (CASE + WHEN (attribute_type = 'assignee') + THEN (SELECT ROW_TO_JSON(rec) + FROM (SELECT (CASE + WHEN (new_value IS NOT NULL) + THEN (SELECT name FROM users WHERE users.id = new_value::UUID) + ELSE (next_string) END) AS name, + (SELECT avatar_url FROM users WHERE users.id = new_value::UUID)) rec) + ELSE (NULL) END) AS assigned_user, + + -- new case + (CASE + WHEN (attribute_type = 'label') + THEN (SELECT ROW_TO_JSON(rec) + FROM (SELECT (SELECT name FROM team_labels WHERE id = new_value::UUID), + (SELECT color_code FROM team_labels WHERE id = new_value::UUID)) rec) + ELSE (NULL) END) AS label_data, + + -- new case + (CASE + WHEN (attribute_type = 'status') + THEN (SELECT ROW_TO_JSON(rec) + FROM (SELECT (SELECT name FROM task_statuses WHERE id = old_value::UUID), + (SELECT color_code + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = old_value::UUID)), + (SELECT color_code_dark + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = old_value::UUID))) rec) + ELSE (NULL) END) AS previous_status, + + -- new case + (CASE + WHEN (attribute_type = 'status') + THEN (SELECT ROW_TO_JSON(rec) + FROM (SELECT (SELECT name FROM task_statuses WHERE id = new_value::UUID), + (SELECT color_code + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = new_value::UUID)), + (SELECT color_code_dark + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = new_value::UUID))) rec) + ELSE (NULL) END) AS next_status, + + -- new case + (CASE + WHEN (attribute_type = 'priority') + THEN (SELECT ROW_TO_JSON(rec) + FROM (SELECT (SELECT name FROM task_priorities WHERE id = old_value::UUID), + (SELECT color_code FROM task_priorities WHERE id = old_value::UUID)) rec) + ELSE (NULL) END) AS previous_priority, + + -- new case + (CASE + WHEN (attribute_type = 'priority') + THEN (SELECT ROW_TO_JSON(rec) + FROM (SELECT (SELECT name FROM task_priorities WHERE id = new_value::UUID), + (SELECT color_code FROM task_priorities WHERE id = new_value::UUID)) rec) + ELSE (NULL) END) AS next_priority, + + -- new case + (CASE + WHEN (attribute_type = 'phase' AND old_value <> 'Unmapped') + THEN (SELECT ROW_TO_JSON(rec) + FROM (SELECT (SELECT name FROM project_phases WHERE id = old_value::UUID), + (SELECT color_code FROM project_phases WHERE id = old_value::UUID)) rec) + ELSE (NULL) END) AS previous_phase, + + -- new case + (CASE + WHEN (attribute_type = 'phase' AND new_value <> 'Unmapped') + THEN (SELECT ROW_TO_JSON(rec) + FROM (SELECT (SELECT name FROM project_phases WHERE id = new_value::UUID), + (SELECT color_code FROM project_phases WHERE id = new_value::UUID)) rec) + ELSE (NULL) END) AS next_phase, + + -- new case + (SELECT ROW_TO_JSON(rec) + FROM (SELECT (SELECT name FROM users WHERE users.id = tal.user_id), + (SELECT avatar_url FROM users WHERE users.id = tal.user_id)) rec) AS done_by + + + FROM task_activity_logs tal + WHERE task_id = _task_id + ORDER BY created_at DESC) rec2) AS logs) rec; + RETURN _result; +END; +$$; + +CREATE OR REPLACE FUNCTION get_billing_info(_user_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _is_custom BOOLEAN := FALSE; + _is_ltd BOOLEAN := FALSE; + _result JSON; +BEGIN + SELECT EXISTS(SELECT id FROM licensing_custom_subs WHERE user_id = _user_id) INTO _is_custom; + SELECT EXISTS(SELECT 1 FROM licensing_coupon_codes WHERE redeemed_by = _user_id) INTO _is_ltd; + + SELECT ROW_TO_JSON(rec) + INTO _result + FROM (SELECT (SELECT name FROM users WHERE ud.user_id = users.id), + (SELECT email FROM users WHERE ud.user_id = users.id), + contact_number, + contact_number_secondary, + trial_in_progress, + trial_expire_date, + unit_price::NUMERIC, + cancel_url, + subscription_status AS status, + lus.cancellation_effective_date, + lus.paused_at, + lus.paused_from::DATE, + lus.paused_reason, + _is_custom AS is_custom, + _is_ltd AS is_ltd_user, + (SELECT SUM(team_members_limit) FROM licensing_coupon_codes WHERE redeemed_by = _user_id) AS ltd_users, + (CASE + WHEN (_is_custom) THEN 'Custom Plan' + WHEN (_is_ltd) THEN 'Life Time Deal' + ELSE + (SELECT name FROM licensing_pricing_plans WHERE id = lus.plan_id) END) AS plan_name, + (SELECT key FROM sys_license_types WHERE id = ud.license_type_id) AS subscription_type, + (SELECT id AS plan_id FROM licensing_pricing_plans WHERE id = lus.plan_id), + (SELECT default_currency AS default_currency FROM licensing_pricing_plans WHERE id = lus.plan_id), + (SELECT billing_type FROM licensing_pricing_plans WHERE id = lus.plan_id), + (CASE + WHEN ud.subscription_status = 'trialing' THEN ud.trial_expire_date::DATE + WHEN EXISTS (SELECT 1 FROM licensing_custom_subs lcs WHERE lcs.user_id = ud.user_id) THEN + (SELECT end_date FROM licensing_custom_subs lcs WHERE lcs.user_id = ud.user_id)::DATE + WHEN EXISTS (SELECT 1 FROM licensing_user_subscriptions lus WHERE lus.user_id = ud.user_id) THEN + (SELECT next_bill_date::DATE - INTERVAL '1 day' + FROM licensing_user_subscriptions lus + WHERE lus.user_id = ud.user_id)::DATE + END) AS valid_till_date, + is_lkr_billing + FROM organizations ud + LEFT JOIN licensing_user_subscriptions lus ON ud.user_id = lus.user_id + WHERE ud.user_id = _user_id) rec; + RETURN _result; +END; +$$; + +CREATE OR REPLACE FUNCTION get_daily_digest() RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; +BEGIN + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _result + FROM ( + -- + SELECT name, + email, + (SELECT get_daily_digest_recently_assigned(u.id)) AS recently_assigned, + (SELECT get_daily_digest_overdue(u.id)) AS overdue, + (SELECT get_daily_digest_recently_completed(u.id)) AS recently_completed + FROM users u + -- + ) rec; + RETURN _result; +END; +$$; + +CREATE OR REPLACE FUNCTION get_daily_digest_overdue(_user_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; +BEGIN + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _result + FROM ( + -- + SELECT id, + name, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) AS projects + FROM ( + -- + SELECT id, + name, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) AS tasks + FROM ( + -- + SELECT t.id, + t.name, + (SELECT STRING_AGG(DISTINCT + (SELECT name + FROM team_member_info_view + WHERE team_member_id = tasks_assignees.team_member_id), + ', ') + FROM tasks_assignees + WHERE task_id = t.id) AS members + FROM tasks_assignees + INNER JOIN tasks t ON tasks_assignees.task_id = t.id + INNER JOIN team_members tm ON tasks_assignees.team_member_id = tm.id + WHERE tm.user_id = _user_id + AND t.project_id = projects.id + AND t.end_date IS NOT NULL + AND t.end_date < CURRENT_DATE + AND EXISTS(SELECT id + FROM task_statuses + WHERE id = t.status_id + AND category_id IN + (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) + LIMIT 10 + -- + ) r) + FROM projects + WHERE projects.team_id IN + (SELECT team_id + FROM team_members + WHERE user_id = _user_id + AND team_members.team_id = teams.id) + -- + ) r) + FROM teams + WHERE (SELECT daily_digest_enabled + FROM notification_settings + WHERE team_id = teams.id + AND user_id = _user_id) IS TRUE + AND EXISTS(SELECT 1 + FROM team_members + WHERE team_id = teams.id + AND team_members.user_id = _user_id) + -- + ) rec; + + RETURN _result; +END +$$; + +CREATE OR REPLACE FUNCTION get_daily_digest_recently_assigned(_user_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; +BEGIN + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _result + FROM ( + -- + + -- + SELECT id, + name, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) AS projects + FROM ( + -- + SELECT id, + name, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) AS tasks + FROM ( + -- + SELECT t.id, + t.name, + (SELECT STRING_AGG(DISTINCT + (SELECT name + FROM team_member_info_view + WHERE team_member_id = tasks_assignees.team_member_id), + ', ') + FROM tasks_assignees + WHERE task_id = t.id) AS members + FROM tasks_assignees + INNER JOIN tasks t ON tasks_assignees.task_id = t.id + INNER JOIN team_members tm ON tasks_assignees.team_member_id = tm.id + WHERE tm.user_id = _user_id + AND t.project_id = projects.id + AND TO_CHAR(tasks_assignees.created_at, 'yyyy-mm-dd') = + TO_CHAR(CURRENT_DATE, 'yyyy-mm-dd') + -- + ) r) + FROM projects + WHERE projects.team_id IN + (SELECT team_id + FROM team_members + WHERE user_id = _user_id + AND team_members.team_id = teams.id) + -- + ) r) + FROM teams + WHERE (SELECT daily_digest_enabled + FROM notification_settings + WHERE team_id = teams.id + AND user_id = _user_id) IS TRUE + AND EXISTS(SELECT 1 + FROM team_members + WHERE team_id = teams.id + AND team_members.user_id = _user_id) + -- + ) rec; + + RETURN _result; +END +$$; + +CREATE OR REPLACE FUNCTION get_daily_digest_recently_completed(_user_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; +BEGIN + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _result + FROM ( + -- + SELECT id, + name, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) AS projects + FROM ( + -- + SELECT name, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) AS tasks + FROM ( + -- + SELECT t.id, + t.name, + (SELECT STRING_AGG(DISTINCT + (SELECT name + FROM team_member_info_view + WHERE team_member_id = tasks_assignees.team_member_id), + ', ') + FROM tasks_assignees + WHERE task_id = t.id) AS members + FROM tasks_assignees + INNER JOIN tasks t ON tasks_assignees.task_id = t.id + INNER JOIN team_members tm ON tasks_assignees.team_member_id = tm.id + WHERE tm.user_id = _user_id + AND t.project_id = projects.id + AND t.completed_at IS NOT NULL + AND TO_CHAR(t.completed_at, 'yyyy-mm-dd') = + TO_CHAR(CURRENT_DATE, 'yyyy-mm-dd') + LIMIT 10 + -- + ) r) + FROM projects + WHERE projects.team_id IN + (SELECT team_id + FROM team_members + WHERE user_id = _user_id + AND team_members.team_id = teams.id) + -- + ) r) + FROM teams + WHERE (SELECT daily_digest_enabled + FROM notification_settings + WHERE team_id = teams.id + AND user_id = _user_id) IS TRUE + AND EXISTS(SELECT 1 + FROM team_members + WHERE team_id = teams.id + AND team_members.user_id = _user_id) + -- + ) rec; + + RETURN _result; +END +$$; + +CREATE OR REPLACE FUNCTION get_last_updated_tasks_by_project(_project_id uuid, _limit integer, _offset integer, _archived boolean) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _tasks JSON; +BEGIN + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _tasks + FROM (SELECT id, + name, + (SELECT name FROM task_statuses WHERE status_id = task_statuses.id) AS status, + status_id, + end_date, + priority_id AS priority, + updated_at, + (SELECT color_code + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = status_id)) AS status_color + FROM tasks + WHERE project_id = _project_id + AND CASE + WHEN (_archived IS TRUE) THEN project_id IS NOT NULL + ELSE archived IS FALSE END + ORDER BY updated_at DESC + LIMIT _limit OFFSET _offset) rec; + RETURN _tasks; +END +$$; + +CREATE OR REPLACE FUNCTION get_my_tasks(_team_id uuid, _user_id uuid, _size numeric, _offset numeric, _filter text) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; +BEGIN + -- _filter = '0' is tasks due today + IF _filter = '0' + THEN + SELECT ROW_TO_JSON(rec) + INTO _result + FROM (SELECT COUNT(*) AS total, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM (SELECT t.id, + t.name, + t.project_id, + t.status_id, + t.start_date, + t.end_date, + t.created_at, + p.team_id, + p.name AS project_name, + p.color_code AS project_color, + (SELECT name FROM task_statuses WHERE id = t.status_id) AS status, + (SELECT color_code + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = t.status_id)) AS status_color, + TRUE AS is_task + FROM tasks t + CROSS JOIN projects p + WHERE t.project_id = p.id + AND t.archived IS FALSE + AND t.end_date::DATE = CURRENT_DATE::DATE + AND t.status_id NOT IN (SELECT id + FROM task_statuses + WHERE category_id NOT IN + (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) + AND t.id IN + (SELECT task_id + FROM tasks_assignees + WHERE team_member_id = (SELECT id + FROM team_members + WHERE user_id = _user_id + AND team_id = _team_id)) + ORDER BY p.updated_at, created_at + LIMIT _size OFFSET _offset) rec) AS data + FROM tasks t + CROSS JOIN projects p + WHERE t.project_id = p.id + AND t.archived IS FALSE + AND t.end_date::DATE = CURRENT_DATE::DATE + AND t.status_id NOT IN (SELECT id + FROM task_statuses + WHERE category_id NOT IN + (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) + AND t.id IN + (SELECT task_id + FROM tasks_assignees + WHERE team_member_id = (SELECT id + FROM team_members + WHERE user_id = _user_id + AND team_id = _team_id))) rec; + RETURN _result; + END IF; + + -- _filter = '1' is upcoming tasks + IF _filter = '1' + THEN + SELECT ROW_TO_JSON(rec) + INTO _result + FROM (SELECT COUNT(*) AS total, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM (SELECT t.id, + t.name, + t.project_id, + t.status_id, + t.start_date, + t.end_date, + t.created_at, + p.team_id, + p.name AS project_name, + p.color_code AS project_color, + (SELECT name FROM task_statuses WHERE id = t.status_id) AS status, + (SELECT color_code + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = t.status_id)) AS status_color, + TRUE AS is_task + FROM tasks t + CROSS JOIN projects p + WHERE t.project_id = p.id + AND t.archived IS FALSE + AND t.end_date::DATE > CURRENT_DATE::DATE + AND t.status_id NOT IN (SELECT id + FROM task_statuses + WHERE category_id NOT IN + (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) + AND t.id IN + (SELECT task_id + FROM tasks_assignees + WHERE team_member_id = (SELECT id + FROM team_members + WHERE user_id = _user_id + AND team_id = _team_id)) + ORDER BY p.updated_at, created_at + LIMIT _size OFFSET _offset) rec) AS data + FROM tasks t + CROSS JOIN projects p + WHERE t.project_id = p.id + AND t.archived IS FALSE + AND t.end_date::DATE > CURRENT_DATE::DATE + AND t.status_id NOT IN (SELECT id + FROM task_statuses + WHERE category_id NOT IN + (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) + AND t.id IN + (SELECT task_id + FROM tasks_assignees + WHERE team_member_id = (SELECT id + FROM team_members + WHERE user_id = _user_id + AND team_id = _team_id))) rec; + RETURN _result; + END IF; + + -- _filter = '2' is overdue tasks + IF _filter = '2' + THEN + SELECT ROW_TO_JSON(rec) + INTO _result + FROM (SELECT COUNT(*) AS total, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM (SELECT t.id, + t.name, + t.project_id, + t.status_id, + t.start_date, + t.end_date, + t.created_at, + p.team_id, + p.name AS project_name, + p.color_code AS project_color, + (SELECT name FROM task_statuses WHERE id = t.status_id) AS status, + (SELECT color_code + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = t.status_id)) AS status_color, + TRUE AS is_task + FROM tasks t + CROSS JOIN projects p + WHERE t.project_id = p.id + AND t.archived IS FALSE + AND t.end_date::DATE < CURRENT_DATE::DATE + AND t.status_id NOT IN (SELECT id + FROM task_statuses + WHERE category_id NOT IN + (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) + AND t.id IN + (SELECT task_id + FROM tasks_assignees + WHERE team_member_id = (SELECT id + FROM team_members + WHERE user_id = _user_id + AND team_id = _team_id)) + ORDER BY p.updated_at, created_at + LIMIT _size OFFSET _offset) rec) AS data + FROM tasks t + CROSS JOIN projects p + WHERE t.project_id = p.id + AND t.archived IS FALSE + AND t.end_date::DATE < CURRENT_DATE::DATE + AND t.status_id NOT IN (SELECT id + FROM task_statuses + WHERE category_id NOT IN + (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) + AND t.id IN + (SELECT task_id + FROM tasks_assignees + WHERE team_member_id = (SELECT id + FROM team_members + WHERE user_id = _user_id + AND team_id = _team_id))) rec; + RETURN _result; + END IF; + + -- _filter = '3' is todo list + IF _filter = '3' + THEN + SELECT ROW_TO_JSON(rec) + INTO _result + FROM (SELECT COUNT(*) AS total, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM (SELECT id, name, created_at, color_code, done + FROM personal_todo_list + WHERE user_id = _user_id + ORDER BY updated_at, created_at + LIMIT _size OFFSET _offset) rec) AS data) rec; + RETURN _result; + END IF; + +END; +$$; + +CREATE OR REPLACE FUNCTION get_project_daily_digest() RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; +BEGIN + + SELECT COALESCE(JSON_AGG(rec), '[]'::JSON) + INTO _result + FROM (SELECT id, + name, + (SELECT name FROM teams WHERE id = projects.team_id) AS team_name, + + (SELECT COALESCE(JSON_AGG(rec), '[]'::JSON) + FROM (SELECT id, + name, + (SELECT STRING_AGG(DISTINCT + (SELECT name + FROM team_member_info_view + WHERE team_member_id = tasks_assignees.team_member_id), + ', ') + FROM tasks_assignees + WHERE task_id = tasks.id) AS members + FROM tasks + WHERE project_id = projects.id + AND TO_CHAR(tasks.completed_at, 'yyyy-mm-dd') = + TO_CHAR(CURRENT_DATE, 'yyyy-mm-dd')) rec) AS today_completed, + + (SELECT COALESCE(JSON_AGG(rec), '[]'::JSON) + FROM (SELECT id, + name, + (SELECT STRING_AGG(DISTINCT + (SELECT name + FROM team_member_info_view + WHERE team_member_id = tasks_assignees.team_member_id), + ', ') + FROM tasks_assignees + WHERE task_id = tasks.id) AS members + FROM tasks + WHERE project_id = projects.id + AND TO_CHAR(tasks.created_at, 'yyyy-mm-dd') = + TO_CHAR(CURRENT_DATE, 'yyyy-mm-dd')) rec) AS today_new, + + (SELECT COALESCE(JSON_AGG(rec), '[]'::JSON) + FROM (SELECT id, + name, + (SELECT STRING_AGG(DISTINCT + (SELECT name + FROM team_member_info_view + WHERE team_member_id = tasks_assignees.team_member_id), + ', ') + FROM tasks_assignees + WHERE task_id = tasks.id) AS members + FROM tasks + WHERE project_id = projects.id + AND TO_CHAR(tasks.end_date, 'yyyy-mm-dd') = + TO_CHAR(CURRENT_DATE + INTERVAL '1 day', 'yyyy-mm-dd')) rec) AS due_tomorrow, + + (SELECT COALESCE(JSON_AGG(rec), '[]'::JSON) + FROM (SELECT name, email + FROM users + WHERE id = (SELECT user_id + FROM project_subscribers + WHERE project_id = projects.id + AND user_id = users.id)) rec) AS subscribers + + FROM projects + WHERE EXISTS(SELECT 1 FROM project_subscribers WHERE project_id = projects.id) + ORDER BY team_id, name) rec; + + RETURN _result; +END +$$; + +CREATE OR REPLACE FUNCTION get_project_deadline_tasks(_project_id uuid, _archived boolean) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; + +BEGIN + SELECT COALESCE(ROW_TO_JSON(rec), '[]'::JSON) + INTO _result + FROM (SELECT (SELECT COUNT(*) + FROM tasks + WHERE project_id = _project_id + AND CASE + WHEN (_archived IS TRUE) THEN project_id IS NOT NULL + ELSE archived IS FALSE END + AND end_date::DATE > (SELECT end_date + FROM projects + WHERE id = _project_id)::DATE) AS deadline_tasks_count, + (SELECT SUM(twl.time_spent) + FROM tasks t + CROSS JOIN task_work_log twl + WHERE twl.task_id = t.id + AND t.project_id = _project_id + AND twl.created_at::DATE > (SELECT end_date + FROM projects + WHERE id = _project_id)::DATE + AND CASE + WHEN (_archived IS TRUE) THEN t.project_id IS NOT NULL + ELSE t.archived IS FALSE END) AS deadline_logged_hours, + (SELECT end_date FROM projects WHERE id = _project_id) AS project_end_date, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) AS tasks + FROM (SELECT id, + name, + status_id, + start_date, + end_date, + (SELECT name FROM task_statuses WHERE id = tasks.status_id) AS status, + (SELECT color_code + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = status_id)) AS status_color + + FROM tasks + WHERE project_id = _project_id + AND CASE + WHEN (_archived IS TRUE) THEN project_id IS NOT NULL + ELSE archived IS FALSE END + AND end_date::DATE > (SELECT end_date + FROM projects + WHERE id = _project_id)::DATE) r)) rec; + RETURN _result; + +END; +$$; + +CREATE OR REPLACE FUNCTION get_project_gantt_tasks(_project_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _tasks JSON; +BEGIN + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _tasks + FROM (SELECT p.id, + p.name, + 0 AS level, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM (SELECT pm.id, + pm.team_member_id, + pm.project_access_level_id, + (SELECT name + FROM job_titles + WHERE job_titles.id = tm.job_title_id) AS job_title, + (SELECT name + FROM team_member_info_view + WHERE team_member_info_view.team_member_id = tm.id), + u.avatar_url, + (SELECT email + FROM team_member_info_view + WHERE team_member_info_view.team_member_id = tm.id), + (SELECT name + FROM project_access_levels + WHERE project_access_levels.id = pm.project_access_level_id) AS access_level, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM (SELECT t.id, + t.name, + t.start_date AS start, + t.project_id, + t.priority_id, + t.done, + t.end_date AS "end", + (SELECT color_code + FROM projects + WHERE projects.id = t.project_id) AS color_code, + t.status_id, + (SELECT name FROM task_statuses WHERE id = t.status_id) AS status, + (SELECT ARRAY_AGG(ROW_TO_JSON(rec)) + FROM (SELECT project_member_id AS id, + (SELECT name + FROM team_member_info_view + WHERE team_member_info_view.team_member_id = tm.id), + u2.avatar_url, + (SELECT team_member_info_view.email + FROM team_member_info_view + WHERE team_member_info_view.team_member_id = tm.id) + FROM tasks_assignees + INNER JOIN project_members pm ON pm.id = tasks_assignees.project_member_id + INNER JOIN team_members tm2 ON pm.team_member_id = tm2.id + LEFT JOIN users u2 ON tm2.user_id = u2.id + WHERE project_id = _project_id::UUID + AND project_member_id = pm.id + AND t.id = tasks_assignees.task_id + ORDER BY name) rec) AS assignees + FROM tasks_assignees ta, + tasks t + WHERE t.archived IS FALSE + AND ta.project_member_id = pm.id + AND t.id = ta.task_id + ORDER BY start_date) rec) AS tasks + FROM project_members pm + INNER JOIN team_members tm ON pm.team_member_id = tm.id + LEFT JOIN users u ON tm.user_id = u.id + WHERE project_id = p.id) rec) AS team_members + FROM projects p + WHERE p.id = _project_id::UUID) rec; + + RETURN _tasks; +END; +$$; + +CREATE OR REPLACE FUNCTION get_project_member_insights(_project_id uuid, _archived boolean) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; + +BEGIN + SELECT COALESCE(ROW_TO_JSON(rec), '[]'::JSON) + INTO _result + FROM (SELECT (SELECT COUNT(*) FROM project_members WHERE project_id = _project_id) AS total_members_count, + (SELECT COUNT(*) + FROM project_members + WHERE project_id = _project_id + AND team_member_id NOT IN + (SELECT team_member_id + FROM tasks_assignees + WHERE task_id IN (SELECT id + FROM tasks + WHERE tasks.project_id = _project_id + AND CASE + WHEN (_archived IS TRUE) THEN project_id IS NOT NULL + ELSE archived IS FALSE END))) AS unassigned_members, + (SELECT COUNT(*) + FROM project_members + WHERE project_id = _project_id + AND team_member_id IN + (SELECT team_member_id + FROM tasks_assignees + WHERE task_id IN (SELECT id + FROM tasks + WHERE tasks.project_id = _project_id + AND CASE + WHEN (_archived IS TRUE) THEN project_id IS NOT NULL + ELSE archived IS FALSE END + AND tasks.end_date::DATE < NOW()::DATE))) AS overdue_members) rec; + RETURN _result; + +END; +$$; + +CREATE OR REPLACE FUNCTION get_project_members(_project_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _output JSON; +BEGIN + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM ( + -- + SELECT (SELECT name FROM team_member_info_view WHERE team_member_info_view.team_member_id = tm.id), + u.avatar_url + FROM project_members + INNER JOIN team_members tm ON project_members.team_member_id = tm.id + LEFT JOIN users u ON tm.user_id = u.id + WHERE project_id = _project_id + -- + ) rec + INTO _output; + + RETURN _output; +END; +$$; + +CREATE OR REPLACE FUNCTION get_project_overview_data(_project_id uuid, _archived boolean) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; + +BEGIN + SELECT COALESCE(ROW_TO_JSON(rec), '[]'::JSON) + INTO _result + FROM (SELECT (SELECT COUNT(*) FROM tasks WHERE project_id = _project_id) AS total_tasks_count, + (SELECT COUNT(*) + FROM tasks + WHERE project_id = _project_id + AND archived IS TRUE) AS archived_tasks_count, + (SELECT COUNT(*) + FROM tasks + WHERE completed_at > CURRENT_DATE - INTERVAL '7 days' + AND project_id = _project_id + AND CASE + WHEN (_archived IS TRUE) THEN project_id IS NOT NULL + ELSE archived IS FALSE END) AS last_week_count, + (SELECT COUNT(*) + FROM tasks + WHERE project_id = _project_id + AND parent_task_id IS NOT NULL + AND CASE + WHEN (_archived IS TRUE) THEN project_id IS NOT NULL + ELSE archived IS FALSE END) AS sub_tasks_count, + (SELECT COUNT(*) + FROM tasks + WHERE project_id = _project_id + AND CASE + WHEN (_archived IS TRUE) THEN project_id IS NOT NULL + ELSE archived IS FALSE END + AND status_id IN (SELECT id + FROM task_statuses + WHERE project_id = _project_id + AND category_id IN + (SELECT id + FROM sys_task_status_categories + WHERE sys_task_status_categories.is_done IS TRUE))) AS completed_tasks_count, + (SELECT color_code + FROM sys_task_status_categories + WHERE sys_task_status_categories.is_done IS TRUE) AS completed_tasks_color_code, + (SELECT COUNT(*) + FROM tasks + WHERE project_id = _project_id + AND CASE + WHEN (_archived IS TRUE) THEN project_id IS NOT NULL + ELSE archived IS FALSE END + AND status_id IN (SELECT id + FROM task_statuses + WHERE project_id = _project_id + AND category_id IN + (SELECT id + FROM sys_task_status_categories + WHERE sys_task_status_categories.is_doing IS TRUE))) AS pending_tasks_count, + (SELECT color_code + FROM sys_task_status_categories + WHERE sys_task_status_categories.is_doing IS TRUE) AS pending_tasks_color_code, + (SELECT COUNT(*) + FROM tasks + WHERE project_id = _project_id + AND CASE + WHEN (_archived IS TRUE) THEN project_id IS NOT NULL + ELSE archived IS FALSE END + AND is_completed(status_id, project_id) IS FALSE) AS todo_tasks_count, + (SELECT color_code + FROM sys_task_status_categories + WHERE sys_task_status_categories.is_todo IS TRUE) AS todo_tasks_color_code, + (SELECT COUNT(*) + FROM tasks + WHERE project_id = _project_id + AND end_date::DATE < NOW()::DATE + AND CASE + WHEN (_archived IS TRUE) THEN project_id IS NOT NULL + ELSE archived IS FALSE END + AND status_id IN (SELECT id + FROM task_statuses + WHERE project_id = _project_id + AND category_id IN + (SELECT id + FROM sys_task_status_categories + WHERE sys_task_status_categories.is_done IS FALSE))) AS overdue_count, + (SELECT SUM(total_minutes) + FROM tasks + WHERE project_id = _project_id + AND CASE + WHEN (_archived IS TRUE) THEN project_id IS NOT NULL + ELSE archived IS FALSE END) AS total_minutes_sum, + (SELECT SUM(time_spent) + FROM task_work_log + CROSS JOIN tasks t + WHERE task_id = t.id + AND CASE + WHEN (_archived IS TRUE) THEN project_id IS NOT NULL + ELSE archived IS FALSE END + AND t.project_id = _project_id) AS time_spent_sum) rec; + RETURN _result; + +END; +$$; + +CREATE OR REPLACE FUNCTION get_project_wise_resources(_start_date date, _end_date date, _team_id uuid) RETURNS text + LANGUAGE plpgsql +AS +$$ +DECLARE + _projects JSON; + +BEGIN + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM (SELECT id, + name, + color_code, + FALSE AS collapsed, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM (SELECT date_series, + project_id, + SUM(total_minutes / 60), + JSON_AGG(JSON_BUILD_OBJECT('id', tasks_.id, + 'name', tasks_.name + )) AS scheduled_tasks + FROM GENERATE_SERIES( + _start_date::DATE, + _end_date::DATE, + '1 day' + ) AS date_series + CROSS JOIN (SELECT id, name, project_id, total_minutes, start_date, end_date + FROM tasks) AS tasks_ + WHERE (date_series >= tasks_.start_date::DATE AND date_series <= tasks_.end_date::DATE) + AND tasks_.project_id = projects.id + GROUP BY date_series, project_id) rec) AS schedule, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM (SELECT team_member_id, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM (SELECT date_series, + project_id, + SUM(total_minutes / 60), + JSON_AGG(JSON_BUILD_OBJECT('id', tasks_.id, + 'name', tasks_.name + )) AS scheduled_tasks + FROM GENERATE_SERIES( + _start_date::DATE, + _end_date::DATE, + '1 day' + ) AS date_series + CROSS JOIN (SELECT id, name, project_id, total_minutes, start_date, end_date + FROM tasks, + tasks_assignees + WHERE task_id = tasks.id + AND tasks_assignees.team_member_id = project_members.team_member_id) AS tasks_ + WHERE (date_series >= tasks_.start_date::DATE AND + date_series <= tasks_.end_date::DATE) + AND tasks_.project_id = projects.id + GROUP BY date_series, project_id) rec) AS tasks, + (SELECT name + FROM users + WHERE users.id = + (SELECT user_id + FROM team_members + WHERE team_members.id = project_members.team_member_id)), + (SELECT email + FROM email_invitations + WHERE project_members.team_member_id = email_invitations.team_member_id) AS invitee_email, + (SELECT avatar_url + FROM users + WHERE users.id = + (SELECT user_id + FROM team_members + WHERE team_members.id = project_members.team_member_id)) + FROM project_members + WHERE project_id = projects.id) rec) AS project_members, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM (SELECT date_series, + project_id, + SUM(total_minutes / 60), + JSON_AGG(JSON_BUILD_OBJECT('id', tasks_.id, + 'name', tasks_.name + )) AS scheduled_tasks + FROM GENERATE_SERIES( + _start_date::DATE, + _end_date, + '1 day' + ) AS date_series + CROSS JOIN (SELECT id, name, project_id, total_minutes, start_date, end_date + FROM tasks + WHERE tasks.project_id = project_id + AND tasks.id NOT IN (SELECT task_id FROM tasks_assignees)) AS tasks_ + WHERE (date_series >= tasks_.start_date::DATE AND date_series <= tasks_.end_date::DATE) + AND tasks_.project_id = projects.id + GROUP BY date_series, project_id) rec) AS unassigned_tasks + FROM projects + WHERE team_id = _team_id + AND id NOT IN (SELECT project_id FROM archived_projects WHERE project_id = projects.id) + ORDER BY updated_at DESC) rec + INTO _projects; + + RETURN _projects; +END; +$$; + +CREATE OR REPLACE FUNCTION get_reporting_member_current_doing_tasks(_team_member_id uuid, _user_id uuid, _include_archived boolean, _limit numeric, _offset numeric) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; + +BEGIN + SELECT ROW_TO_JSON(rec) + INTO _result + FROM (SELECT COUNT(*) AS total, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM ((SELECT t.id, + t.name AS task, + p.name AS project, + p.id AS project_id, + (SELECT name FROM teams WHERE id = p.team_id) AS team_name, + (SELECT name + FROM task_statuses + WHERE id = t.status_id) AS status, + (SELECT color_code + FROM sys_task_status_categories + WHERE id = + (SELECT category_id FROM task_statuses WHERE id = t.status_id)) AS status_color, + t.end_date, + t.updated_at AS last_updated + FROM tasks t + LEFT JOIN tasks_assignees ta ON t.id = ta.task_id + LEFT JOIN projects p ON t.project_id = p.id + WHERE ta.team_member_id = _team_member_id + AND p.team_id IN (SELECT team_id + FROM team_members + WHERE user_id = _user_id + AND role_id IN (SELECT id + FROM roles + WHERE (admin_role IS TRUE OR owner IS TRUE))) + AND CASE + WHEN (_include_archived IS TRUE) THEN team_id IS NOT NULL + ELSE NOT EXISTS(SELECT project_id + FROM archived_projects + WHERE project_id = p.id) END + AND t.status_id IN + (SELECT id + FROM task_statuses + WHERE category_id IN + (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) + ORDER BY end_date DESC + LIMIT _limit OFFSET _offset)) rec) AS data + FROM tasks t + LEFT JOIN tasks_assignees ta ON t.id = ta.task_id + LEFT JOIN projects p ON t.project_id = p.id + WHERE ta.team_member_id = _team_member_id + AND p.team_id IN (SELECT team_id + FROM team_members + WHERE user_id = _user_id + AND role_id IN (SELECT id + FROM roles + WHERE (admin_role IS TRUE OR owner IS TRUE))) + AND CASE + WHEN (_include_archived IS TRUE) THEN team_id IS NOT NULL + ELSE NOT EXISTS(SELECT project_id + FROM archived_projects + WHERE project_id = p.id) END + AND t.status_id IN + (SELECT id + FROM task_statuses + WHERE category_id IN + (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE))) rec; + RETURN _result; +END; +$$; + +CREATE OR REPLACE FUNCTION get_reporting_member_overdue_tasks(_team_member_id uuid, _user_id uuid, _include_archived boolean, _limit numeric, _offset numeric) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; + +BEGIN + SELECT ROW_TO_JSON(rec) + INTO _result + FROM (SELECT COUNT(*) AS total, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM ((SELECT t.id, + t.name AS task, + p.name AS project, + p.id AS project_id, + (SELECT name FROM teams WHERE id = p.team_id) AS team_name, + (SELECT name + FROM task_statuses + WHERE id = t.status_id) AS status, + (SELECT color_code + FROM sys_task_status_categories + WHERE id = + (SELECT category_id FROM task_statuses WHERE id = t.status_id)) AS status_color, + t.end_date, + t.updated_at AS last_updated, + t.end_date AS due_date + FROM tasks t + LEFT JOIN tasks_assignees ta ON t.id = ta.task_id + LEFT JOIN projects p ON t.project_id = p.id + WHERE ta.team_member_id = _team_member_id + AND p.team_id IN (SELECT team_id + FROM team_members + WHERE user_id = _user_id + AND role_id IN (SELECT id + FROM roles + WHERE (admin_role IS TRUE OR owner IS TRUE))) + AND CASE + WHEN (_include_archived IS TRUE) THEN team_id IS NOT NULL + ELSE NOT EXISTS(SELECT project_id + FROM archived_projects + WHERE project_id = p.id) END + AND t.status_id IN + (SELECT id + FROM task_statuses + WHERE category_id IN + (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) + AND t.end_date::DATE < NOW()::DATE + ORDER BY end_date DESC + LIMIT _limit OFFSET _offset)) rec) AS data + FROM tasks t + LEFT JOIN tasks_assignees ta ON t.id = ta.task_id + LEFT JOIN projects p ON t.project_id = p.id + WHERE ta.team_member_id = _team_member_id + AND p.team_id IN (SELECT team_id + FROM team_members + WHERE user_id = _user_id + AND role_id IN (SELECT id + FROM roles + WHERE (admin_role IS TRUE OR owner IS TRUE))) + AND CASE + WHEN (_include_archived IS TRUE) THEN team_id IS NOT NULL + ELSE NOT EXISTS(SELECT project_id + FROM archived_projects + WHERE project_id = p.id) END + AND t.status_id IN + (SELECT id + FROM task_statuses + WHERE category_id IN + (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) + AND t.end_date::DATE < NOW()::DATE) rec; + RETURN _result; +END; +$$; + +CREATE OR REPLACE FUNCTION get_reporting_member_recently_logged_tasks(_team_member_id uuid, _user_id uuid, _include_archived boolean) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; + +BEGIN + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _result + FROM ((SELECT DISTINCT twl.task_id AS id, + t.name, + (SELECT SUM(twl.time_spent)) AS logged_time, + p.name AS project_name, + p.id AS project_id, + (SELECT name FROM teams WHERE id = p.team_id) AS team_name, + (SELECT name + FROM task_statuses + WHERE id = (SELECT status_id FROM tasks WHERE id = twl.task_id)) AS status, + (SELECT color_code + FROM sys_task_status_categories + WHERE id = + (SELECT category_id FROM task_statuses WHERE id = t.status_id)) AS status_color, + t.end_date::DATE, + (SELECT MAX(created_at) + FROM task_work_log + WHERE task_work_log.task_id = twl.task_id) AS logged_timestamp + FROM task_work_log twl + LEFT JOIN tasks t ON twl.task_id = t.id + LEFT JOIN projects p ON t.project_id = p.id + WHERE user_id = (SELECT user_id FROM team_members WHERE id = _team_member_id) + + -- check if the team is a team that the user is either an admin or owner + AND p.team_id IN + (SELECT team_id + FROM team_members + WHERE user_id = _user_id + AND role_id IN (SELECT id + FROM roles + WHERE (admin_role IS TRUE OR owner IS TRUE))) + + -- check if the include_archived flag is true or false + AND CASE + WHEN (_include_archived IS TRUE) THEN team_id IS NOT NULL + ELSE NOT EXISTS(SELECT project_id + FROM archived_projects + WHERE project_id = p.id) END + + GROUP BY task_id, t.name, project_id, t.end_date, p.name, p.team_id, t.status_id, p.id + ORDER BY (SELECT MAX(created_at) FROM task_work_log WHERE task_work_log.task_id = twl.task_id) DESC + LIMIT 10)) rec; + RETURN _result; + +END; +$$; + +CREATE OR REPLACE FUNCTION get_reporting_members_stats(_team_member_id uuid, _include_archived boolean, _user_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; + _teams UUID[]; + +BEGIN + -- SELECT the team_id to select into the _teams variable so the function doesn't have to run it multiple times + SELECT ARRAY_AGG(team_id) + INTO _teams + FROM team_members + WHERE user_id = _user_id + AND role_id IN (SELECT id + FROM roles + WHERE (admin_role IS TRUE OR owner IS TRUE)); + /* + ##### id IN (SELECT UNNEST(_teams)) ##### + + the above piece of query is added to all the queries below to ensure that the statistics that are shown are related + to the teams that the user is either an admin or owner of the team + */ + + SELECT COALESCE(ROW_TO_JSON(rec), '[]'::JSON) + INTO _result + FROM ((SELECT (SELECT name FROM team_member_info_view WHERE team_member_id = _team_member_id), + + -- get total teams that the user is added to + (SELECT COUNT(*) + FROM teams + WHERE id IN (SELECT team_id + FROM team_members + WHERE team_members.user_id = + (SELECT user_id + FROM team_members + WHERE id = _team_member_id)) + AND id IN (SELECT UNNEST(_teams))) AS total_teams, + + -- select total projects the user is added to + (SELECT COUNT(*) + FROM project_members + LEFT JOIN projects p ON project_members.project_id = p.id + WHERE team_member_id IN (SELECT id + FROM team_members + WHERE user_id = + (SELECT user_id FROM team_members WHERE id = _team_member_id)) + + -- check if the team is a team that the user is either an admin or owner + AND p.team_id IN (SELECT UNNEST(_teams)) + AND p.team_id IN (SELECT team_id + FROM projects + WHERE id IN (SELECT project_id + FROM tasks + WHERE id IN (SELECT task_id + FROM tasks_assignees + WHERE tasks.id = tasks_assignees.task_id + AND team_member_id IN + (SELECT id + FROM team_members + WHERE user_id = (SELECT user_id + FROM team_members + WHERE id = _team_member_id))))) + AND CASE + WHEN (_include_archived IS TRUE) THEN project_members.project_id IS NOT NULL + ELSE NOT EXISTS(SELECT project_id + FROM archived_projects + WHERE project_id = project_members.project_id) END) AS project_members, + + -- select the total number of seconds estimated for the tasks that the user is assigned to + (SELECT SUM(total_minutes * 60) + FROM tasks + LEFT JOIN projects p ON tasks.project_id = p.id + WHERE EXISTS(SELECT 1 + FROM tasks_assignees + WHERE tasks.id = tasks_assignees.task_id + AND team_member_id IN + (SELECT id + FROM team_members + WHERE user_id = _user_id)) + + -- check if the team is a team that the user is either an admin or owner + AND p.team_id IN (SELECT UNNEST(_teams)) + AND CASE + WHEN (_include_archived IS TRUE) THEN tasks.project_id IS NOT NULL + ELSE NOT EXISTS(SELECT project_id + FROM archived_projects + WHERE project_id = tasks.project_id) END) AS total_estimated, + + -- select the total logged time for the tasks that the user is assigned to + (SELECT SUM(time_spent) + FROM tasks + LEFT JOIN task_work_log twl ON tasks.id = twl.task_id + LEFT JOIN projects p ON tasks.project_id = p.id + WHERE EXISTS(SELECT 1 + FROM tasks_assignees + WHERE tasks.id = tasks_assignees.task_id + AND team_member_id IN + (SELECT id + FROM team_members + WHERE user_id = _user_id)) + + -- check if the team is a team that the user is either an admin or owner + AND p.team_id IN (SELECT UNNEST(_teams)) + + AND CASE + WHEN (_include_archived IS TRUE) THEN tasks.project_id IS NOT NULL + ELSE NOT EXISTS(SELECT project_id + FROM archived_projects + WHERE project_id = tasks.project_id) END) AS total_logged, + + -- select the total tasks that the user is assigned to + (SELECT COUNT(*) + FROM tasks + LEFT JOIN projects p ON tasks.project_id = p.id + WHERE EXISTS(SELECT 1 + FROM tasks_assignees + WHERE tasks.id = tasks_assignees.task_id + AND team_member_id IN + (SELECT id + FROM team_members + WHERE user_id = _user_id)) + + -- check if the team is a team that the user is either an admin or owner + AND p.team_id IN (SELECT UNNEST(_teams)) + + AND CASE + WHEN (_include_archived IS TRUE) THEN tasks.project_id IS NOT NULL + ELSE NOT EXISTS(SELECT project_id + FROM archived_projects + WHERE project_id = tasks.project_id) END) AS total_tasks, + + -- select the total tasks that the user has completed + (SELECT COUNT(*) + FROM tasks + LEFT JOIN projects p ON tasks.project_id = p.id + + WHERE EXISTS(SELECT 1 + FROM tasks_assignees + WHERE tasks.id = tasks_assignees.task_id + AND team_member_id IN + (SELECT id + FROM team_members + WHERE user_id = _user_id)) + + -- check if the team is a team that the user is either an admin or owner + AND p.team_id IN (SELECT UNNEST(_teams)) + + AND tasks.status_id IN + (SELECT id + FROM task_statuses + WHERE category_id IN + (SELECT id FROM sys_task_status_categories WHERE is_done IS TRUE)) + AND CASE + WHEN (_include_archived IS TRUE) THEN tasks.project_id IS NOT NULL + ELSE NOT EXISTS(SELECT project_id + FROM archived_projects + WHERE project_id = tasks.project_id) END) AS total_tasks_completed, + + -- select the total tasks that are overdue + (SELECT COUNT(*) + FROM tasks + LEFT JOIN projects p ON tasks.project_id = p.id + + WHERE EXISTS(SELECT 1 + FROM tasks_assignees + WHERE tasks.id = tasks_assignees.task_id + AND team_member_id IN + (SELECT id + FROM team_members + WHERE user_id = _user_id)) + + -- check if the team is a team that the user is either an admin or owner + AND p.team_id IN (SELECT UNNEST(_teams)) + + AND tasks.end_date::DATE < NOW()::DATE + AND tasks.status_id IN + (SELECT id + FROM task_statuses + WHERE category_id IN + (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) + AND CASE + WHEN (_include_archived IS TRUE) THEN tasks.project_id IS NOT NULL + ELSE NOT EXISTS(SELECT project_id + FROM archived_projects + WHERE project_id = tasks.project_id) END) AS overdue_tasks)) rec; + RETURN _result; + +END; +$$; + +CREATE OR REPLACE FUNCTION get_reporting_overview_stats(_user_id uuid, _include_archived boolean) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; + +BEGIN + SELECT COALESCE(ROW_TO_JSON(rec), '[]'::JSON) + INTO _result + FROM ((SELECT (SELECT COUNT(*) + FROM team_members + WHERE user_id = _user_id + AND role_id IN (SELECT id + FROM roles + WHERE (admin_role IS TRUE OR owner IS TRUE))) AS total_teams, + (SELECT COUNT(*) + FROM projects + WHERE team_id IN + (SELECT team_id + FROM team_members + WHERE user_id = _user_id + AND role_id IN (SELECT id + FROM roles + WHERE (admin_role IS TRUE OR owner IS TRUE))) + AND CASE + WHEN (_include_archived IS TRUE) THEN team_id IS NOT NULL + ELSE NOT EXISTS(SELECT project_id + FROM archived_projects + WHERE project_id = projects.id) END) AS total_projects, + (SELECT COUNT(*) + FROM projects + WHERE team_id IN + (SELECT team_id + FROM team_members + WHERE user_id = _user_id + AND role_id IN (SELECT id + FROM roles + WHERE (admin_role IS TRUE OR owner IS TRUE))) + AND CASE + WHEN (_include_archived IS TRUE) THEN team_id IS NOT NULL + ELSE NOT EXISTS(SELECT project_id + FROM archived_projects + WHERE project_id = projects.id) END + AND status_id IN (SELECT ID + FROM sys_project_statuses + WHERE sys_project_statuses.name NOT IN ('Completed', 'Cancelled'))) AS active_projects, + (SELECT COUNT(*) + FROM (SELECT DISTINCT user_id + FROM team_members tm + LEFT JOIN email_invitations ei ON tm.id = ei.team_member_id + WHERE tm.team_id IN (SELECT team_id + FROM team_members + WHERE user_id = _user_id + AND role_id IN (SELECT id + FROM roles + WHERE (admin_role IS TRUE OR owner IS TRUE)))) AS members) AS total_members, + (SELECT COUNT(*) + FROM (SELECT DISTINCT user_id + FROM team_members tm + LEFT JOIN email_invitations ei ON tm.id = ei.team_member_id + WHERE tm.team_id IN (SELECT team_id + FROM team_members + WHERE user_id = _user_id + AND role_id IN (SELECT id + FROM roles + WHERE (admin_role IS TRUE OR owner IS TRUE))) + AND team_member_id NOT IN + (SELECT team_member_id + FROM project_members pm + WHERE pm.project_id IN + (SELECT id FROM projects WHERE projects.team_id = tm.team_id))) AS members) AS unassigned_members, + (SELECT COUNT(*) + FROM (SELECT DISTINCT user_id + FROM team_members tm + LEFT JOIN email_invitations ei ON tm.id = ei.team_member_id + WHERE tm.team_id IN (SELECT team_id + FROM team_members + WHERE user_id = _user_id + AND role_id IN (SELECT id + FROM roles + WHERE (admin_role IS TRUE OR owner IS TRUE))) + AND tm.id IN (SELECT ta.team_member_id + FROM tasks_assignees ta + LEFT JOIN tasks t + ON t.id = ta.task_id AND t.end_date::DATE < NOW()::DATE AND + t.status_id IN + (SELECT id + FROM task_statuses + WHERE category_id NOT IN + (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)))) AS members) AS overdue_task_members, + (SELECT COUNT(*) + FROM projects + WHERE team_id IN + (SELECT team_id + FROM team_members + WHERE user_id = _user_id + AND role_id IN (SELECT id + FROM roles + WHERE (admin_role IS TRUE OR owner IS TRUE))) + AND end_date::DATE < NOW()::DATE + AND CASE + WHEN (_include_archived IS TRUE) THEN team_id IS NOT NULL + ELSE NOT EXISTS(SELECT project_id + FROM archived_projects + WHERE project_id = projects.id) END) AS overdue_projects)) rec; + RETURN _result; + +END; +$$; + +CREATE OR REPLACE FUNCTION get_reporting_projects_stats(_user_id uuid, _include_archived boolean) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; + _teams UUID[]; + +BEGIN + -- SELECT the team_id to select into the _teams variable so the function doesn't have to run it multiple times + SELECT ARRAY_AGG(team_id) + INTO _teams + FROM team_members + WHERE user_id = _user_id + AND role_id IN (SELECT id + FROM roles + WHERE (admin_role IS TRUE OR owner IS TRUE)); + + SELECT COALESCE(ROW_TO_JSON(rec), '[]'::JSON) + INTO _result + FROM ((SELECT (SELECT COUNT(*) + FROM projects + WHERE team_id IN (SELECT UNNEST(_teams)) + AND CASE + WHEN (_include_archived IS TRUE) THEN team_id IS NOT NULL + ELSE NOT EXISTS(SELECT project_id + FROM archived_projects + WHERE project_id = projects.id) END + AND status_id IN (SELECT ID + FROM sys_project_statuses + WHERE sys_project_statuses.name NOT IN ('Completed', 'Cancelled'))) AS active_projects, + (SELECT COUNT(*) + FROM projects + WHERE team_id IN (SELECT UNNEST(_teams)) + AND end_date::DATE < NOW()::DATE + AND CASE + WHEN (_include_archived IS TRUE) THEN team_id IS NOT NULL + ELSE NOT EXISTS(SELECT project_id + FROM archived_projects + WHERE project_id = projects.id) END) AS overdue_projects, + (SELECT SUM(time_spent) + FROM task_work_log + WHERE task_id IN (SELECT id + FROM tasks + WHERE project_id IN (SELECT id + FROM projects + WHERE team_id IN (SELECT UNNEST(_teams)) + AND CASE + WHEN (_include_archived IS TRUE) + THEN team_id IS NOT NULL + ELSE NOT EXISTS(SELECT project_id + FROM archived_projects + WHERE project_id = projects.id + AND user_id = _user_id) END)))::INT AS total_logged, + (SELECT SUM(total_minutes * 60) + FROM tasks + WHERE project_id IN (SELECT id + FROM projects + WHERE team_id IN (SELECT UNNEST(_teams)) + AND CASE + WHEN (TRUE IS TRUE) THEN team_id IS NOT NULL + ELSE NOT EXISTS(SELECT project_id + FROM archived_projects + WHERE project_id = projects.id + AND user_id = _user_id) END))::INT AS total_estimated, + (SELECT COUNT(*) + FROM tasks + WHERE archived IS FALSE + AND project_id IN (SELECT id + FROM projects + WHERE team_id IN (SELECT UNNEST(_teams)) + AND CASE + WHEN (TRUE IS TRUE) THEN team_id IS NOT NULL + ELSE NOT EXISTS(SELECT project_id + FROM archived_projects + WHERE project_id = projects.id + AND user_id = _user_id) END))::INT AS all_tasks_count, + (SELECT COUNT(*) + FROM tasks + WHERE archived IS FALSE + AND project_id IN (SELECT id + FROM projects + WHERE team_id IN (SELECT UNNEST(_teams)) + AND CASE + WHEN (TRUE IS TRUE) THEN team_id IS NOT NULL + ELSE NOT EXISTS(SELECT project_id + FROM archived_projects + WHERE project_id = projects.id + AND user_id = _user_id) END) + AND status_id IN (SELECT id + FROM task_statuses + WHERE project_id = tasks.project_id + AND category_id IN + (SELECT id FROM sys_task_status_categories WHERE is_done IS TRUE)))::INT AS completed_tasks_count)) rec; + RETURN _result; + +END; +$$; + +CREATE OR REPLACE FUNCTION get_resource_gantt_tasks(_user_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _tasks JSON; + _team_id UUID; +BEGIN + + SELECT active_team FROM users WHERE id = _user_id::UUID INTO _team_id; + + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _tasks + FROM (SELECT projects.id, + projects.name, + 0 AS level, + FALSE AS collapsed, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM (SELECT pm.id, + pm.team_member_id, + pm.project_access_level_id, + FALSE AS overdue, + (SELECT name + FROM job_titles + WHERE job_titles.id = tm.job_title_id) AS job_title, + (SELECT name + FROM team_member_info_view + WHERE team_member_info_view.team_member_id = tm.id), + u.avatar_url, + (SELECT name + FROM project_access_levels + WHERE project_access_levels.id = pm.project_access_level_id) AS access_level, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM (SELECT t.id, + t.id AS task_id, + t.name, + t.start_date AS start, + t.project_id, + t.priority_id, + t.done, + t.end_date AS "end", + 0 AS progress, + 2 AS level, + TRUE AS "showOnGraph", + t.status_id, + (SELECT name FROM task_statuses WHERE id = t.status_id) AS status, + (SELECT color_code + FROM projects + WHERE projects.id = t.project_id) AS color_code, + (SELECT ARRAY_AGG(ROW_TO_JSON(rec)) + FROM (SELECT project_member_id AS id, + (SELECT name + FROM team_member_info_view + WHERE team_member_info_view.team_member_id = tm.id) + FROM tasks_assignees + INNER JOIN project_members pm ON pm.id = tasks_assignees.project_member_id + INNER JOIN team_members tm2 ON pm.team_member_id = tm2.id + LEFT JOIN users u2 ON tm2.user_id = u2.id + WHERE project_id = t.project_id + AND project_member_id = pm.id + AND t.id = tasks_assignees.task_id + ORDER BY name) rec) AS assignees + FROM tasks_assignees ta, + tasks t + WHERE t.archived IS FALSE + AND ta.project_member_id = pm.id + AND t.id = ta.task_id + ORDER BY start_date) rec) AS tasks + FROM project_members pm + INNER JOIN team_members tm ON pm.team_member_id = tm.id + LEFT JOIN users u ON tm.user_id = u.id + WHERE project_id = projects.id) rec) AS team_members + FROM projects + WHERE team_id = _team_id + AND (CASE + WHEN (is_owner(_user_id, _team_id) OR is_admin(_user_id, _team_id)) THEN TRUE + ELSE is_member_of_project(projects.id, _user_id, _team_id) END) + ORDER BY NAME) rec; + + RETURN _tasks; +END; +$$; + +CREATE OR REPLACE FUNCTION get_selected_tasks(_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _tasks JSON; +BEGIN + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _tasks + FROM (SELECT t.id, + t.name, + t.start_date AS start, + t.end_date AS "end", + t.project_id, + (SELECT name FROM task_priorities WHERE task_priorities.id = t.priority_id) AS priority, + t.priority_id, + t.done, + t.created_at, + t.status_id, + (SELECT ARRAY_AGG(ROW_TO_JSON(rec)) + FROM (SELECT project_member_id AS id, + u2.name AS name, + (SELECT avatar_url FROM users WHERE id = tm2.user_id), + COALESCE((u2.email), (SELECT email + FROM email_invitations + WHERE email_invitations.team_member_id = tm2.id)) AS email + FROM tasks_assignees + INNER JOIN project_members pm ON pm.id = tasks_assignees.project_member_id + INNER JOIN team_members tm2 ON pm.team_member_id = tm2.id + LEFT JOIN users u2 ON tm2.user_id = u2.id + WHERE project_id = _id::UUID + AND project_member_id = pm.id + AND t.id = tasks_assignees.task_id + ORDER BY name) rec) AS assignees + FROM tasks t + WHERE archived IS FALSE + AND project_id = _id::UUID + AND (t.start_date IS NOT NULL + OR t.end_date IS NOT NULL + OR t.id IN (SELECT task_id FROM tasks_assignees))) rec; + + RETURN _tasks; +END; +$$; + +CREATE OR REPLACE FUNCTION get_single_pt_task(_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; +BEGIN + SELECT ROW_TO_JSON(rec) + INTO _result + FROM (SELECT id, + name, + cpt_tasks.template_id AS template_id, + cpt_tasks.parent_task_id, + cpt_tasks.parent_task_id IS NOT NULL AS is_sub_task, + (SELECT name FROM cpt_tasks WHERE id = cpt_tasks.parent_task_id) AS parent_task_name, + (SELECT COUNT('*') + FROM cpt_tasks + WHERE parent_task_id = cpt_tasks.id) AS sub_tasks_count, + cpt_tasks.status_id AS status, + (SELECT name FROM cpt_task_statuses WHERE id = cpt_tasks.status_id) AS status_name, + (SELECT color_code + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM cpt_task_statuses WHERE id = cpt_tasks.status_id)) AS status_color, + (SELECT COALESCE(ROW_TO_JSON(r), '{}'::JSON) + FROM (SELECT is_done, is_doing, is_todo + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM cpt_task_statuses WHERE id = cpt_tasks.status_id)) r) AS status_category, + (SELECT name + FROM cpt_phases + WHERE id = (SELECT phase_id FROM cpt_task_phases WHERE task_id = cpt_tasks.id)) AS phase_name, + (SELECT phase_id FROM cpt_task_phases WHERE task_id = cpt_tasks.id) AS phase_id, + (SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))) + FROM (SELECT cpt_task_labels.label_id AS id, + (SELECT name FROM team_labels WHERE id = cpt_task_labels.label_id) AS name, + (SELECT color_code FROM team_labels WHERE id = cpt_task_labels.label_id) + FROM cpt_task_labels + WHERE task_id = cpt_tasks.id + ORDER BY name) r) AS labels, + (SELECT id FROM task_priorities WHERE id = cpt_tasks.priority_id) AS priority, + (SELECT name FROM task_priorities WHERE id = cpt_tasks.priority_id) AS priority_name, + (SELECT value FROM task_priorities WHERE id = cpt_tasks.priority_id) AS priority_value, + total_minutes, + sort_order, + (SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))) + FROM (SELECT cpt_task_statuses.id AS id, + cpt_task_statuses.name AS name, + (SELECT color_code + FROM sys_task_status_categories + WHERE id = cpt_task_statuses.category_id) + FROM cpt_task_statuses + WHERE cpt_task_statuses.template_id = cpt_tasks.template_id) r) AS template_statuses + FROM cpt_tasks + WHERE id = _id) rec; + RETURN _result; +END +$$; + +CREATE OR REPLACE FUNCTION get_single_task(_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; +BEGIN + + SELECT ROW_TO_JSON(rec) + INTO _result + FROM (SELECT id, + name, + (SELECT name FROM projects WHERE project_id = projects.id) AS project_name, + CONCAT((SELECT key FROM projects WHERE id = project_id), '-', task_no) AS task_key, + tasks.project_id AS project_id, + tasks.parent_task_id, + tasks.parent_task_id IS NOT NULL AS is_sub_task, + (SELECT name FROM tasks WHERE id = tasks.parent_task_id) AS parent_task_name, + (SELECT COUNT('*') + FROM tasks + WHERE parent_task_id = tasks.id + AND archived IS FALSE) AS sub_tasks_count, + + tasks.status_id AS status, + tasks.archived, + + (SELECT name FROM task_statuses WHERE id = tasks.status_id) AS status_name, + (SELECT name FROM task_priorities WHERE id = tasks.priority_id) AS priority_name, + + (SELECT color_code + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = tasks.status_id)) AS status_color, + + (SELECT color_code_dark + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = tasks.status_id)) AS status_color_dark, + + (SELECT get_task_assignees(tasks.id)) AS assignees, + + (SELECT name + FROM project_phases + WHERE id = (SELECT phase_id FROM task_phase WHERE task_id = tasks.id)) AS phase_name, + (SELECT color_code + FROM project_phases + WHERE id = (SELECT phase_id FROM task_phase WHERE task_id = tasks.id)) AS phase_color_code, + (SELECT phase_id FROM task_phase WHERE task_id = tasks.id) AS phase_id, + + (SELECT COALESCE(ROW_TO_JSON(r), '{}'::JSON) + FROM (SELECT is_done, is_doing, is_todo + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = tasks.status_id)) r) AS status_category, + + (SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))) + FROM (SELECT task_labels.label_id AS id, + (SELECT name FROM team_labels WHERE id = task_labels.label_id) AS name, + (SELECT color_code FROM team_labels WHERE id = task_labels.label_id) + FROM task_labels + WHERE task_id = tasks.id + ORDER BY name) r) AS labels, + + (SELECT name FROM users WHERE id = reporter_id) AS reporter, + (SELECT id FROM task_priorities WHERE id = tasks.priority_id) AS priority, + (SELECT value FROM task_priorities WHERE id = tasks.priority_id) AS priority_value, + total_minutes, + (SELECT SUM(time_spent) FROM task_work_log WHERE task_id = tasks.id) AS total_minutes_spent, + start_date, + end_date, + sort_order, + (SELECT color_code FROM projects WHERE projects.id = tasks.project_id) AS project_color, + (SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))) + FROM (SELECT task_statuses.id AS id, + task_statuses.name AS name, + (SELECT color_code + FROM sys_task_status_categories + WHERE id = task_statuses.category_id) + FROM task_statuses + WHERE task_statuses.project_id = tasks.project_id) r) AS project_statuses + FROM tasks + WHERE id = _id) rec; + + RETURN _result; +END +$$; + +CREATE OR REPLACE FUNCTION get_task_assignees(_task_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _output JSON; +BEGIN + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM (SELECT team_member_id, + project_member_id, + (SELECT name FROM team_member_info_view WHERE team_member_info_view.team_member_id = tm.id), + (SELECT email_notifications_enabled + FROM notification_settings + WHERE team_id = tm.team_id + AND notification_settings.user_id = u.id) AS email_notifications_enabled, + u.avatar_url, + u.id AS user_id, + u.email, + u.socket_id AS socket_id, + tm.team_id AS team_id + FROM tasks_assignees + INNER JOIN team_members tm ON tm.id = tasks_assignees.team_member_id + LEFT JOIN users u ON tm.user_id = u.id + WHERE task_id = _task_id) rec + INTO _output; + + RETURN _output; +END; +$$; + +CREATE OR REPLACE FUNCTION get_task_complete_info(_task_id uuid, _status_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _color_code TEXT; + _color_code_dark TEXT; + _members JSON; +BEGIN + SELECT color_code + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = _status_id) + INTO _color_code; + + SELECT color_code_dark + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = _status_id) + INTO _color_code_dark; + + SELECT get_task_assignees(_task_id) INTO _members; + + RETURN JSON_BUILD_OBJECT( + 'color_code', _color_code, + 'color_code_dark', _color_code_dark, + 'members', _members + ); +END; +$$; + +CREATE OR REPLACE FUNCTION get_task_complete_ratio(_task_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _parent_task_done FLOAT = 0; + _sub_tasks_done FLOAT = 0; + _sub_tasks_count FLOAT = 0; + _total_completed FLOAT = 0; + _total_tasks FLOAT = 0; + _ratio FLOAT = 0; +BEGIN + SELECT (CASE + WHEN EXISTS(SELECT 1 + FROM tasks_with_status_view + WHERE tasks_with_status_view.task_id = _task_id + AND is_done IS TRUE) THEN 1 + ELSE 0 END) + INTO _parent_task_done; + SELECT COUNT(*) FROM tasks WHERE parent_task_id = _task_id AND archived IS FALSE INTO _sub_tasks_count; + + SELECT COUNT(*) + FROM tasks_with_status_view + WHERE parent_task_id = _task_id + AND is_done IS TRUE + INTO _sub_tasks_done; + + _total_completed = _parent_task_done + _sub_tasks_done; +-- _total_tasks = _sub_tasks_count + 1; -- +1 for the parent task + _total_tasks = _sub_tasks_count; -- +1 for the parent task + _ratio = (_total_completed / _total_tasks) * 100; + + RETURN JSON_BUILD_OBJECT( + 'ratio', _ratio, + 'total_completed', _total_completed, + 'total_tasks', _total_tasks + ); +END +$$; + +CREATE OR REPLACE FUNCTION get_task_form_view_model(_user_id uuid, _team_id uuid, _task_id uuid, _project_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _task JSON; + _priorities JSON; + _projects JSON; + _statuses JSON; + _team_members JSON; + _assignees JSON; + _phases JSON; +BEGIN + + -- Select task info + SELECT COALESCE(ROW_TO_JSON(rec), '{}'::JSON) + INTO _task + FROM (SELECT id, + name, + description, + start_date, + end_date, + done, + total_minutes, + priority_id, + project_id, + created_at, + updated_at, + status_id, + parent_task_id, + sort_order, + (SELECT phase_id FROM task_phase WHERE task_id = tasks.id) AS phase_id, + CONCAT((SELECT key FROM projects WHERE id = tasks.project_id), '-', task_no) AS task_key, + (SELECT start_time + FROM task_timers + WHERE task_id = tasks.id + AND user_id = _user_id) AS timer_start_time, + parent_task_id IS NOT NULL AS is_sub_task, + (SELECT COUNT('*') + FROM tasks + WHERE parent_task_id = tasks.id + AND archived IS FALSE) AS sub_tasks_count, + (SELECT COUNT(*) + FROM tasks_with_status_view tt + WHERE (tt.parent_task_id = tasks.id OR tt.task_id = tasks.id) + AND tt.is_done IS TRUE) + AS completed_count, + (SELECT COUNT(*) FROM task_attachments WHERE task_id = tasks.id) AS attachments_count, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) + FROM (SELECT task_labels.label_id AS id, + (SELECT name FROM team_labels WHERE id = task_labels.label_id), + (SELECT color_code FROM team_labels WHERE id = task_labels.label_id) + FROM task_labels + WHERE task_id = tasks.id + ORDER BY name) r) AS labels, + (SELECT color_code + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = tasks.status_id)) AS status_color, + (SELECT color_code_dark + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = tasks.status_id)) AS status_color_dark, + (SELECT COUNT(*) FROM tasks WHERE parent_task_id = _task_id) AS sub_tasks_count, + (SELECT name FROM users WHERE id = tasks.reporter_id) AS reporter, + (SELECT get_task_assignees(tasks.id)) AS assignees, + (SELECT id FROM team_members WHERE user_id = _user_id AND team_id = _team_id) AS team_member_id, + billable, + schedule_id + FROM tasks + WHERE id = _task_id) rec; + + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _priorities + FROM (SELECT id, name FROM task_priorities ORDER BY value) rec; + + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _phases + FROM (SELECT id, name FROM project_phases WHERE project_id = _project_id ORDER BY name) rec; + + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _projects + FROM (SELECT id, name + FROM projects + WHERE team_id = _team_id + AND (CASE + WHEN (is_owner(_user_id, _team_id) OR is_admin(_user_id, _team_id) IS TRUE) THEN TRUE + ELSE is_member_of_project(projects.id, _user_id, _team_id) END) + ORDER BY name) rec; + + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _statuses + FROM (SELECT id, name FROM task_statuses WHERE project_id = _project_id) rec; + + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _team_members + FROM (SELECT team_members.id, + (SELECT name FROM team_member_info_view WHERE team_member_info_view.team_member_id = team_members.id), + (SELECT email FROM team_member_info_view WHERE team_member_info_view.team_member_id = team_members.id), + (SELECT avatar_url + FROM team_member_info_view + WHERE team_member_info_view.team_member_id = team_members.id) + FROM team_members + LEFT JOIN users u ON team_members.user_id = u.id + WHERE team_id = _team_id AND team_members.active IS TRUE) rec; + + SELECT get_task_assignees(_task_id) INTO _assignees; + + RETURN JSON_BUILD_OBJECT( + 'task', _task, + 'priorities', _priorities, + 'projects', _projects, + 'statuses', _statuses, + 'team_members', _team_members, + 'assignees', _assignees, + 'phases', _phases + ); +END; +$$; + +CREATE OR REPLACE FUNCTION get_task_updates() RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; +BEGIN + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _result + FROM (SELECT name, + email, + (SELECT id + FROM team_members + WHERE team_id = users.active_team + AND user_id = users.id) AS team_member_id, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) AS teams + FROM (SELECT id, + name, + (SELECT team_member_id + FROM team_member_info_view + WHERE team_id = teams.id + AND user_id = users.id) AS team_member_id, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) AS projects + FROM (SELECT id, + name, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) AS tasks + FROM (SELECT t.id, + t.name AS name, + (SELECT name FROM users WHERE id = task_updates.reporter_id) AS updater_name, + (SELECT STRING_AGG(DISTINCT + (SELECT name + FROM team_member_info_view + WHERE team_member_id = tasks_assignees.team_member_id), + ', ') + FROM tasks_assignees + WHERE task_id = task_updates.task_id) AS members + FROM task_updates + INNER JOIN tasks t ON task_updates.task_id = t.id + WHERE task_updates.user_id = users.id + AND task_updates.project_id = projects.id + AND task_updates.type = 'ASSIGN' + AND is_sent IS FALSE + ORDER BY task_updates.created_at) r) + FROM projects + WHERE team_id = teams.id + AND EXISTS(SELECT 1 + FROM task_updates + WHERE project_id = projects.id + AND type = 'ASSIGN' + AND is_sent IS FALSE)) r) + FROM teams + WHERE EXISTS(SELECT 1 FROM team_members WHERE team_id = teams.id AND user_id = users.id) + AND (SELECT email_notifications_enabled + FROM notification_settings + WHERE team_id = teams.id + AND user_id = users.id) IS TRUE) r) + FROM users + WHERE EXISTS(SELECT 1 FROM task_updates WHERE user_id = users.id)) rec; + + UPDATE task_updates SET is_sent = TRUE; + + RETURN _result; +END +$$; + +CREATE OR REPLACE FUNCTION get_tasks_by_project_member(_project_id uuid, _team_member_id uuid, _archived boolean) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; +BEGIN + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) + INTO _result + FROM (SELECT id, + name, + status_id, + (SELECT name FROM task_statuses WHERE status_id = task_statuses.id) AS status, + start_date, + end_date, + completed_at, + total_minutes, + (SELECT color_code + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = status_id)) AS status_color, + CASE + WHEN CURRENT_DATE::DATE > end_date::DATE + AND status_id NOT IN (SELECT id + FROM task_statuses + WHERE project_id = _project_id + AND category_id IN + (SELECT id + FROM sys_task_status_categories + WHERE sys_task_status_categories.is_done IS FALSE)) + THEN FALSE + ELSE + CASE + WHEN CURRENT_DATE::DATE > end_date::DATE + THEN TRUE + ELSE FALSE + END END AS is_overdue, + CASE + WHEN status_id + NOT IN (SELECT id + FROM task_statuses + WHERE project_id = _project_id + AND category_id IN + (SELECT id + FROM sys_task_status_categories + WHERE sys_task_status_categories.is_done IS FALSE)) + THEN 0 + ELSE + CASE + WHEN CURRENT_DATE::DATE > end_date::DATE + THEN NOW()::DATE - end_date::DATE + ELSE 0 + END END AS days_overdue, + + (SELECT SUM(time_spent) FROM task_work_log WHERE task_id = tasks.id) - (total_minutes * 60) AS overlogged_time, + + COALESCE(completed_at::DATE - end_date::DATE, 0) AS late_days + FROM tasks + WHERE project_id = _project_id + AND CASE + WHEN (_archived IS TRUE) THEN project_id IS NOT NULL + ELSE archived IS FALSE END + AND id IN + (SELECT task_id FROM tasks_assignees WHERE team_member_id = _team_member_id)) r; + RETURN _result; +END +$$; + +CREATE OR REPLACE FUNCTION get_tasks_by_status(_id uuid, _status text) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _tasks JSON; + _team_id UUID; +BEGIN + SELECT team_id FROM projects WHERE id = _id INTO _team_id; + + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _tasks + FROM (SELECT id, + name, + + t.parent_task_id, + t.parent_task_id IS NOT NULL AS is_sub_task, + (SELECT name FROM tasks WHERE id = t.parent_task_id) AS parent_task_name, + (SELECT COUNT('*') FROM tasks WHERE parent_task_id = t.id) AS sub_tasks_count, + + status_id AS status, + sort_order, + + (SELECT COUNT(*) + FROM tasks_with_status_view tt + WHERE (tt.parent_task_id = t.id OR tt.task_id = t.id) + AND tt.is_done IS TRUE) + AS completed_count, + + (SELECT get_task_assignees(t.id)) AS assignees, + + (SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))) + FROM (SELECT task_labels.label_id AS id, + (SELECT name FROM team_labels WHERE id = task_labels.label_id), + (SELECT color_code FROM team_labels WHERE id = task_labels.label_id) + FROM task_labels + WHERE task_id = t.id + ORDER BY name) r) AS labels, + + (SELECT name FROM users WHERE id = reporter_id) AS reporter, + (SELECT task_priorities.name FROM task_priorities WHERE id = t.priority_id) AS priority, + start_date, + end_date + FROM tasks t + WHERE archived IS FALSE + AND (project_id = _id) + AND (t.status_id IN (SELECT id FROM task_statuses WHERE name = _status)) + ORDER BY sort_order) rec; + + RETURN _tasks; +END; +$$; + +CREATE OR REPLACE FUNCTION get_tasks_total_and_completed_counts(_task_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _total_tasks INT; + _total_completed INT; +BEGIN + SELECT COUNT(*) + FROM tasks + WHERE (parent_task_id = _task_id OR id = _task_id) + AND archived IS FALSE + INTO _total_tasks; + + SELECT COUNT(*) + FROM tasks_with_status_view tt + WHERE (tt.parent_task_id = _task_id + OR tt.task_id = _task_id) + AND tt.is_done IS TRUE + INTO _total_completed; + + RETURN JSON_BUILD_OBJECT( + 'total_tasks', _total_tasks, + 'total_completed', _total_completed + ); +END +$$; + +CREATE OR REPLACE FUNCTION get_team_id_from_task(_id uuid) RETURNS uuid + LANGUAGE plpgsql +AS +$$ +DECLARE + _team_id UUID; +BEGIN + SELECT team_id INTO _team_id FROM projects WHERE id = (SELECT project_id FROM tasks WHERE tasks.id = _id); + RETURN _team_id; +END +$$; + +CREATE OR REPLACE FUNCTION get_team_members(_team_id uuid, _project_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _result JSON; +BEGIN + + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _result + FROM ( + -- + WITH mbers AS (SELECT team_members.id, + tmiv.name AS name, + tmiv.email, + tmiv.avatar_url, + team_members.user_id, + EXISTS(SELECT 1 + FROM project_members + WHERE project_id = _project_id + AND project_members.team_member_id = team_members.id) AS exists_in_project, + 0 AS usage, + (CASE + WHEN EXISTS (SELECT 1 + FROM email_invitations + WHERE team_member_id = team_members.id) THEN TRUE + ELSE FALSE + END) AS is_pending + FROM team_members + LEFT JOIN users u ON team_members.user_id = u.id + LEFT JOIN team_member_info_view tmiv ON team_members.id = tmiv.team_member_id + WHERE team_members.team_id = _team_id + AND team_members.active IS TRUE + ORDER BY tmiv.name) + SELECT id, name, user_id, email, avatar_url, usage, is_pending + FROM mbers + ORDER BY exists_in_project DESC + -- + ) rec; + + RETURN _result; +END; +$$; + +CREATE OR REPLACE FUNCTION get_team_wise_resources(_start_date date, _end_date date, _team_id uuid) RETURNS text + LANGUAGE plpgsql +AS +$$ +DECLARE + _projects JSON; + +BEGIN + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM (SELECT id, + FALSE AS collapsed, + (SELECT name FROM users WHERE user_id = users.id), + (SELECT email + FROM email_invitations + WHERE team_members.id = email_invitations.team_member_id) AS invitee_email, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM (SELECT date_series, + SUM(total_minutes / 60), + JSON_AGG(JSON_BUILD_OBJECT('id', tasks_.id, + 'name', tasks_.name, + 'project_id', tasks_.project_id, + 'project_name', (SELECT projects.name + FROM projects + WHERE projects.id = tasks_.project_id) + )) AS scheduled_tasks + FROM GENERATE_SERIES( + _start_date::DATE, + _end_date::DATE, + '1 day' + ) AS date_series + CROSS JOIN (SELECT id, name, project_id, total_minutes, start_date, end_date + FROM tasks) AS tasks_, + tasks_assignees + WHERE (date_series BETWEEN tasks_.start_date::DATE AND tasks_.end_date::DATE) + AND tasks_assignees.team_member_id = team_members.id + AND tasks_.id = tasks_assignees.task_id + GROUP BY date_series) rec) AS schedule, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM (SELECT projects.id, + name, + projects.color_code, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM (SELECT date_series, + project_id, + SUM(total_minutes / 60), + JSON_AGG(JSON_BUILD_OBJECT('id', tasks_.id, + 'name', tasks_.name + )) AS scheduled_tasks + FROM GENERATE_SERIES( + _start_date::DATE, + _end_date::DATE, + '1 day' + ) AS date_series + CROSS JOIN (SELECT id, name, project_id, total_minutes, start_date, end_date + FROM tasks) AS tasks_, + tasks_assignees + WHERE (date_series BETWEEN tasks_.start_date::DATE AND tasks_.end_date::DATE) + AND tasks_assignees.team_member_id = team_members.id + AND tasks_.id = tasks_assignees.task_id + AND tasks_.project_id = projects.id + GROUP BY date_series, project_id) rec) AS tasks + FROM projects, + project_members + WHERE project_id = projects.id + AND team_members.id IN (project_members.team_member_id) + ORDER BY updated_at DESC) rec) AS project_members + FROM team_members + WHERE team_id = _team_id + AND user_id IS NOT NULL + ORDER BY (SELECT name FROM users WHERE user_id = users.id)) rec + INTO _projects; + + RETURN _projects; +END; +$$; + +CREATE OR REPLACE FUNCTION get_unselected_tasks(_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _tasks JSON; +BEGIN + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _tasks + FROM (SELECT t.id, + t.name, + t.start_date AT TIME ZONE 'Asia/Colombo' AS start, + t.end_date AS "end", + t.project_id, + (SELECT name FROM task_priorities WHERE task_priorities.id = t.priority_id) AS priority, + t.priority_id, + t.done, + t.created_at, + t.status_id, + (SELECT ARRAY_AGG(ROW_TO_JSON(rec)) + FROM (SELECT project_member_id AS id, + u2.name AS name, + (SELECT avatar_url FROM users WHERE id = tm2.user_id), + COALESCE((u2.email), (SELECT email + FROM email_invitations + WHERE email_invitations.team_member_id = tm2.id)) AS email + FROM tasks_assignees + INNER JOIN project_members pm ON pm.id = tasks_assignees.project_member_id + INNER JOIN team_members tm2 ON pm.team_member_id = tm2.id + LEFT JOIN users u2 ON tm2.user_id = u2.id + WHERE project_id = _id::UUID + AND project_member_id = pm.id + AND t.id = tasks_assignees.task_id + ORDER BY name) rec) AS assignees + FROM tasks t + WHERE archived IS FALSE + AND project_id = _id::UUID + AND (t.start_date IS NULL + OR t.end_date IS NULL + OR t.id NOT IN (SELECT task_id FROM tasks_assignees))) rec; + + RETURN _tasks; +END; +$$; + +CREATE OR REPLACE FUNCTION handle_on_pt_task_phase_change(_task_id uuid, _phase_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _name TEXT; + _color_code TEXT; +BEGIN + IF NOT EXISTS(SELECT 1 FROM cpt_task_phases WHERE task_id = _task_id) + THEN + IF (is_null_or_empty(_phase_id) IS FALSE) + THEN + INSERT INTO cpt_task_phases (task_id, phase_id) VALUES (_task_id, _phase_id); + END IF; + ELSE + IF (is_null_or_empty(_phase_id) IS TRUE) + THEN + DELETE FROM cpt_task_phases WHERE task_id = _task_id; + ELSE + UPDATE cpt_task_phases SET phase_id = _phase_id WHERE task_id = _task_id; + END IF; + END IF; + IF (is_null_or_empty(_phase_id) IS FALSE) + THEN + SELECT name FROM cpt_phases WHERE id = _phase_id INTO _name; + SELECT color_code FROM cpt_phases WHERE id = _phase_id INTO _color_code; + END IF; + RETURN JSON_BUILD_OBJECT('name', _name, 'color_code', _color_code); +END +$$; + +CREATE OR REPLACE FUNCTION handle_on_pt_task_status_change(_task_id uuid, _status_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _task_info JSON; + _status_category JSON; + _task_name TEXT; + _previous_status_name TEXT; + _new_status_name TEXT; +BEGIN + SELECT name FROM cpt_tasks WHERE id = _task_id INTO _task_name; + + SELECT name + FROM cpt_task_statuses + WHERE id = (SELECT status_id FROM cpt_tasks WHERE id = _task_id) + INTO _previous_status_name; + + SELECT name FROM cpt_task_statuses WHERE id = _status_id INTO _new_status_name; + + IF (_previous_status_name != _new_status_name) + THEN + UPDATE cpt_tasks SET status_id = _status_id WHERE id = _task_id; + END IF; + + SELECT COALESCE(ROW_TO_JSON(r), '{}'::JSON) + FROM (SELECT is_done, is_doing, is_todo + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM cpt_task_statuses WHERE id = _status_id)) r + INTO _status_category; + + RETURN JSON_BUILD_OBJECT( + 'template_id', (SELECT template_id FROM cpt_tasks WHERE id = _task_id), + 'color_code', (SELECT color_code FROM sys_task_status_categories WHERE id = (SELECT category_id FROM cpt_task_statuses WHERE id = _status_id))::TEXT, + 'status_category', _status_category + ); +END +$$; + +CREATE OR REPLACE FUNCTION handle_on_task_phase_change(_task_id uuid, _phase_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _name TEXT; + _color_code TEXT; +BEGIN + IF NOT EXISTS(SELECT 1 FROM task_phase WHERE task_id = _task_id) + THEN + IF (is_null_or_empty(_phase_id) IS FALSE) + THEN + INSERT INTO task_phase (task_id, phase_id) VALUES (_task_id, _phase_id); + END IF; + ELSE + IF (is_null_or_empty(_phase_id) IS TRUE) + THEN + DELETE FROM task_phase WHERE task_id = _task_id; + ELSE + UPDATE task_phase SET phase_id = _phase_id WHERE task_id = _task_id; + END IF; + END IF; + + IF (is_null_or_empty(_phase_id) IS FALSE) + THEN + SELECT name FROM project_phases WHERE id = _phase_id INTO _name; + SELECT color_code FROM project_phases WHERE id = _phase_id INTO _color_code; + END IF; + + RETURN JSON_BUILD_OBJECT('name', _name, 'color_code', _color_code); +END +$$; + +CREATE OR REPLACE FUNCTION handle_on_task_status_change(_user_id uuid, _task_id uuid, _status_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _updater_name TEXT; + _task_name TEXT; + _previous_status_name TEXT; + _new_status_name TEXT; + _message TEXT; + _task_info JSON; + _status_category JSON; + _schedule_id JSON; + _task_completed_at TIMESTAMPTZ; +BEGIN + SELECT name FROM tasks WHERE id = _task_id INTO _task_name; + + SELECT name + FROM task_statuses + WHERE id = (SELECT status_id FROM tasks WHERE id = _task_id) + INTO _previous_status_name; + + SELECT name FROM task_statuses WHERE id = _status_id INTO _new_status_name; + + IF (_previous_status_name != _new_status_name) + THEN + UPDATE tasks SET status_id = _status_id WHERE id = _task_id; + + SELECT get_task_complete_info(_task_id, _status_id) INTO _task_info; + + SELECT name FROM users WHERE id = _user_id INTO _updater_name; + + _message = CONCAT(_updater_name, ' transitioned "', _task_name, '" from ', _previous_status_name, ' ⟶ ', + _new_status_name); + END IF; + + SELECT completed_at FROM tasks WHERE id = _task_id INTO _task_completed_at; + SELECT schedule_id FROM tasks WHERE id = _task_id INTO _schedule_id; + + SELECT COALESCE(ROW_TO_JSON(r), '{}'::JSON) + FROM (SELECT is_done, is_doing, is_todo + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = _status_id)) r + INTO _status_category; + + RETURN JSON_BUILD_OBJECT( + 'message', _message, + 'project_id', (SELECT project_id FROM tasks WHERE id = _task_id), + 'parent_done', (CASE + WHEN EXISTS(SELECT 1 + FROM tasks_with_status_view + WHERE tasks_with_status_view.task_id = _task_id + AND is_done IS TRUE) THEN 1 + ELSE 0 END), + 'color_code', (_task_info ->> 'color_code')::TEXT, + 'color_code_dark', (_task_info ->> 'color_code_dark')::TEXT, + 'total_tasks', (_task_info ->> 'total_tasks')::INT, + 'total_completed', (_task_info ->> 'total_completed')::INT, + 'members', (_task_info ->> 'members')::JSON, + 'completed_at', _task_completed_at, + 'status_category', _status_category, + 'schedule_id', _schedule_id + ); +END +$$; + +CREATE OR REPLACE FUNCTION handle_pt_task_list_sort_between_groups(_from_index integer, _to_index integer, _task_id uuid, _template_id uuid) RETURNS void + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + + IF (_to_index = -1) + THEN + _to_index = COALESCE((SELECT MAX(sort_order) + 1 FROM cpt_tasks WHERE template_id = _template_id), 0); + END IF; + + IF _to_index > _from_index + THEN + UPDATE cpt_tasks + SET sort_order = sort_order - 1 + WHERE template_id = _template_id + AND sort_order > _from_index + AND sort_order < _to_index; + + UPDATE cpt_tasks SET sort_order = _to_index - 1 WHERE id = _task_id AND template_id = _template_id; + END IF; + + IF _to_index < _from_index + THEN + UPDATE cpt_tasks + SET sort_order = sort_order + 1 + WHERE template_id = _template_id + AND sort_order > _to_index + AND sort_order < _from_index; + + UPDATE cpt_tasks SET sort_order = _to_index + 1 WHERE id = _task_id AND template_id = _template_id; + END IF; +END +$$; + +CREATE OR REPLACE FUNCTION handle_pt_task_list_sort_inside_group(_from_index integer, _to_index integer, _task_id uuid, _template_id uuid) RETURNS void + LANGUAGE plpgsql +AS +$$ +BEGIN + IF _to_index > _from_index + THEN + UPDATE cpt_tasks + SET sort_order = sort_order - 1 + WHERE template_id = _template_id + AND sort_order > _from_index + AND sort_order <= _to_index; + END IF; + + IF _to_index < _from_index + THEN + UPDATE cpt_tasks + SET sort_order = sort_order + 1 + WHERE template_id = _template_id + AND sort_order >= _to_index + AND sort_order < _from_index; + END IF; + + UPDATE cpt_tasks SET sort_order = _to_index WHERE id = _task_id AND template_id = _template_id; +END +$$; + +CREATE OR REPLACE FUNCTION handle_pt_task_list_sort_order_change(_body json) RETURNS void + LANGUAGE plpgsql +AS +$$ +DECLARE + _from_index INT; + _to_index INT; + _task_id UUID; + _template_id UUID; + _from_group UUID; + _to_group UUID; + _group_by TEXT; +BEGIN + _template_id = (_body ->> 'template_id')::UUID; + _task_id = (_body ->> 'task_id')::UUID; + _from_index = (_body ->> 'from_index')::INT; + _to_index = (_body ->> 'to_index')::INT; + _from_group = (_body ->> 'from_group')::UUID; + _to_group = (_body ->> 'to_group')::UUID; + _group_by = (_body ->> 'group_by')::TEXT; + + IF (_from_group <> _to_group OR (_from_group <> _to_group) IS NULL) + THEN + IF (_group_by = 'status') + THEN + UPDATE cpt_tasks SET status_id = _to_group WHERE id = _task_id AND status_id = _from_group; + END IF; + + IF (_group_by = 'priority') + THEN + UPDATE cpt_tasks SET priority_id = _to_group WHERE id = _task_id AND priority_id = _from_group; + END IF; + + IF (_group_by = 'phase') + THEN + IF (is_null_or_empty(_to_group) IS FALSE) + THEN + INSERT INTO cpt_task_phases (task_id, phase_id) + VALUES (_task_id, _to_group) + ON CONFLICT (task_id) DO UPDATE SET phase_id = _to_group; + END IF; + IF (is_null_or_empty(_to_group) IS TRUE) + THEN + DELETE + FROM cpt_task_phases + WHERE task_id = _task_id; + END IF; + + END IF; + + IF ((_body ->> 'to_last_index')::BOOLEAN IS TRUE AND _from_index < _to_index) + THEN + PERFORM handle_pt_task_list_sort_inside_group(_from_index, _to_index, _task_id, _template_id); + ELSE + PERFORM handle_pt_task_list_sort_between_groups(_from_index, _to_index, _task_id, _template_id); + END IF; + ELSE + PERFORM handle_pt_task_list_sort_inside_group(_from_index, _to_index, _task_id, _template_id); + END IF; +END +$$; + +CREATE OR REPLACE FUNCTION handle_task_list_sort_between_groups(_from_index integer, _to_index integer, _task_id uuid, _project_id uuid) RETURNS void + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + + IF (_to_index = -1) + THEN + _to_index = COALESCE((SELECT MAX(sort_order) + 1 FROM tasks WHERE project_id = _project_id), 0); + END IF; + + IF _to_index > _from_index + THEN + UPDATE tasks + SET sort_order = sort_order - 1 + WHERE project_id = _project_id + AND sort_order > _from_index + AND sort_order < _to_index; + + UPDATE tasks SET sort_order = _to_index - 1 WHERE id = _task_id AND project_id = _project_id; + END IF; + + IF _to_index < _from_index + THEN + UPDATE tasks + SET sort_order = sort_order + 1 + WHERE project_id = _project_id + AND sort_order > _to_index + AND sort_order < _from_index; + + UPDATE tasks SET sort_order = _to_index + 1 WHERE id = _task_id AND project_id = _project_id; + END IF; +END +$$; + +CREATE OR REPLACE FUNCTION handle_task_list_sort_inside_group(_from_index integer, _to_index integer, _task_id uuid, _project_id uuid) RETURNS void + LANGUAGE plpgsql +AS +$$ +BEGIN + IF _to_index > _from_index + THEN + UPDATE tasks + SET sort_order = sort_order - 1 + WHERE project_id = _project_id + AND sort_order > _from_index + AND sort_order <= _to_index; + END IF; + + IF _to_index < _from_index + THEN + UPDATE tasks + SET sort_order = sort_order + 1 + WHERE project_id = _project_id + AND sort_order >= _to_index + AND sort_order < _from_index; + END IF; + + UPDATE tasks SET sort_order = _to_index WHERE id = _task_id AND project_id = _project_id; +END +$$; + +CREATE OR REPLACE FUNCTION handle_task_list_sort_order_change(_body json) RETURNS void + LANGUAGE plpgsql +AS +$$ +DECLARE + _from_index INT; + _to_index INT; + _task_id UUID; + _project_id UUID; + _from_group UUID; + _to_group UUID; + _group_by TEXT; +BEGIN + _project_id = (_body ->> 'project_id')::UUID; + _task_id = (_body ->> 'task_id')::UUID; + + _from_index = (_body ->> 'from_index')::INT; -- from sort_order + _to_index = (_body ->> 'to_index')::INT; -- to sort_order + + _from_group = (_body ->> 'from_group')::UUID; + _to_group = (_body ->> 'to_group')::UUID; + + _group_by = (_body ->> 'group_by')::TEXT; + + IF (_from_group <> _to_group OR (_from_group <> _to_group) IS NULL) + THEN + IF (_group_by = 'status') + THEN + UPDATE tasks SET status_id = _to_group WHERE id = _task_id AND status_id = _from_group; + END IF; + + IF (_group_by = 'priority') + THEN + UPDATE tasks SET priority_id = _to_group WHERE id = _task_id AND priority_id = _from_group; + END IF; + + IF (_group_by = 'phase') + THEN + IF (is_null_or_empty(_to_group) IS FALSE) + THEN + INSERT INTO task_phase (task_id, phase_id) + VALUES (_task_id, _to_group) + ON CONFLICT (task_id) DO UPDATE SET phase_id = _to_group; + END IF; + IF (is_null_or_empty(_to_group) IS TRUE) + THEN + DELETE + FROM task_phase + WHERE task_id = _task_id; + END IF; + END IF; + + IF ((_body ->> 'to_last_index')::BOOLEAN IS TRUE AND _from_index < _to_index) + THEN + PERFORM handle_task_list_sort_inside_group(_from_index, _to_index, _task_id, _project_id); + ELSE + PERFORM handle_task_list_sort_between_groups(_from_index, _to_index, _task_id, _project_id); + END IF; + ELSE + PERFORM handle_task_list_sort_inside_group(_from_index, _to_index, _task_id, _project_id); + END IF; +END +$$; + +CREATE OR REPLACE FUNCTION handle_task_name_change(_task_id uuid, _task_name text, _user_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _name TEXT; + _previous_name TEXT; + _user_name TEXT; + _message TEXT; + _assignees JSON; +BEGIN + + IF (is_null_or_empty(_task_name)) + THEN + SELECT name FROM tasks WHERE id = _task_id INTO _name; + ELSE + SELECT name FROM tasks WHERE id = _task_id INTO _previous_name; + UPDATE tasks SET name = _task_name WHERE id = _task_id RETURNING name INTO _name; + END IF; + + IF (_name != _previous_name) + THEN + SELECT get_task_assignees(_task_id) INTO _assignees; + + SELECT name FROM users WHERE id = _user_id INTO _user_name; + + _message = CONCAT(_user_name, ' has renamed "', _previous_name, '" → "', _name, '"'); + END IF; + + RETURN JSON_BUILD_OBJECT( + 'name', _name, + 'previous_name', _previous_name, + 'project_id', (SELECT project_id FROM tasks WHERE id = _task_id), + 'message', _message, + 'members', _assignees + ); +END +$$; + +CREATE OR REPLACE FUNCTION handle_task_roadmap_sort_order(_from_index integer, _to_index integer, _task_id uuid) RETURNS void + LANGUAGE plpgsql +AS +$$ +DECLARE + _project_id uuid; +BEGIN + SELECT project_id INTO _project_id FROM tasks WHERE id = _task_id; + + IF _to_index > _from_index + THEN + UPDATE tasks + SET roadmap_sort_order = roadmap_sort_order - 1 + WHERE project_id = _project_id + AND roadmap_sort_order > _from_index + AND roadmap_sort_order <= _to_index; + END IF; + + IF _to_index < _from_index + THEN + UPDATE tasks + SET roadmap_sort_order = roadmap_sort_order + 1 + WHERE project_id = _project_id + AND roadmap_sort_order >= _to_index + AND roadmap_sort_order < _from_index; + END IF; + + UPDATE tasks SET roadmap_sort_order = _to_index WHERE id = _task_id AND project_id = _project_id; +END +$$; + +CREATE OR REPLACE FUNCTION home_task_form_view_model(_user_id uuid, _team_id uuid, _task_id uuid, _project_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _task JSON; +BEGIN + + -- Select task info + SELECT COALESCE(ROW_TO_JSON(rec), '{}'::JSON) + INTO _task + FROM (SELECT id, + name, + start_date, + end_date, + done, + priority_id, + project_id, + created_at, + updated_at, + status_id, + parent_task_id, + parent_task_id IS NOT NULL AS is_sub_task, + (SELECT COUNT(*) FROM tasks WHERE parent_task_id = _task_id) AS sub_tasks_count, + (SELECT name FROM users WHERE id = tasks.reporter_id) AS reporter, + (SELECT get_task_assignees(tasks.id)) AS assignees, + (SELECT id FROM team_members WHERE user_id = _user_id AND team_id = _team_id) AS team_member_id + FROM tasks + WHERE id = _task_id) rec; + + RETURN JSON_BUILD_OBJECT( + 'task', _task + ); +END; +$$; + +CREATE OR REPLACE FUNCTION import_tasks_from_template(_project_id uuid, _user_id uuid, _tasks json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _task JSON; + _max_sort INT; + _task_id_new UUID; +BEGIN + + SELECT COALESCE((SELECT MAX(sort_order) FROM tasks WHERE project_id = _project_id), 0) INTO _max_sort; + + -- insert tasks for task templates + FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS(_tasks) + LOOP + _max_sort = _max_sort + 1; + INSERT INTO tasks (name, priority_id, project_id, reporter_id, status_id, sort_order, total_minutes) + VALUES (TRIM((_task ->> 'name')::TEXT), + (SELECT id FROM task_priorities WHERE value = 1), + _project_id, + _user_id, + + -- This should be came from client side later + (SELECT id + FROM task_statuses + WHERE project_id = _project_id::UUID + AND category_id IN (SELECT id FROM sys_task_status_categories WHERE is_todo IS TRUE) + LIMIT 1), _max_sort, (_task ->> 'total_minutes')::NUMERIC) RETURNING id INTO _task_id_new; + + INSERT INTO task_activity_logs (task_id, team_id, attribute_type, user_id, log_type, old_value, new_value, project_id) + VALUES ( + _task_id_new, + (SELECT team_id FROM projects WHERE id = _project_id), + 'status', + _user_id, + 'update', + NULL, + (SELECT id FROM task_statuses WHERE project_id = _project_id::UUID AND category_id IN (SELECT id FROM sys_task_status_categories WHERE is_todo IS TRUE)LIMIT 1), + _project_id + ); + + END LOOP; + + RETURN JSON_BUILD_OBJECT('id', _project_id); +END; +$$; + +CREATE OR REPLACE FUNCTION in_organization(_team_id_in uuid, _team_id uuid) RETURNS boolean + LANGUAGE plpgsql +AS +$$ +BEGIN + RETURN EXISTS ( + SELECT 1 + FROM teams t1 + JOIN teams t2 ON t1.user_id = t2.user_id + WHERE t1.id = _team_id_in AND t2.id = _team_id + ); +END; +$$; + +CREATE OR REPLACE FUNCTION insert_job_title(_job_title text, _team_id uuid) RETURNS uuid + LANGUAGE plpgsql +AS +$$ +DECLARE + _job_title_id UUID; +BEGIN + IF EXISTS(SELECT name FROM job_titles WHERE name = _job_title AND team_id = _team_id) + THEN + SELECT id FROM job_titles WHERE name = _job_title AND team_id = _team_id INTO _job_title_id; + ELSE + INSERT INTO job_titles (name, team_id) + VALUES (_job_title, _team_id) + RETURNING id INTO _job_title_id; + END IF; + + RETURN _job_title_id; +END; +$$; + +CREATE OR REPLACE FUNCTION insert_task_list_columns(_project_id uuid) RETURNS void + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) + VALUES (_project_id, 'Key', 'KEY', 0, FALSE); + INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) + VALUES (_project_id, 'Description', 'DESCRIPTION', 2, FALSE); + INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) + VALUES (_project_id, 'Progress', 'PROGRESS', 3, TRUE); + INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) + VALUES (_project_id, 'Members', 'ASSIGNEES', 4, TRUE); + INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) + VALUES (_project_id, 'Labels', 'LABELS', 5, TRUE); + INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) + VALUES (_project_id, 'Status', 'STATUS', 6, TRUE); + INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) + VALUES (_project_id, 'Priority', 'PRIORITY', 7, TRUE); + INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) + VALUES (_project_id, 'Time Tracking', 'TIME_TRACKING', 8, TRUE); + INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) + VALUES (_project_id, 'Estimation', 'ESTIMATION', 9, FALSE); + INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) + VALUES (_project_id, 'Start Date', 'START_DATE', 10, FALSE); + INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) + VALUES (_project_id, 'Due Date', 'DUE_DATE', 11, TRUE); + INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) + VALUES (_project_id, 'Completed Date', 'COMPLETED_DATE', 12, FALSE); + INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) + VALUES (_project_id, 'Created Date', 'CREATED_DATE', 13, FALSE); + INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) + VALUES (_project_id, 'Last Updated', 'LAST_UPDATED', 14, FALSE); + INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) + VALUES (_project_id, 'Reporter', 'REPORTER', 15, FALSE); + INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) + VALUES (_project_id, 'Phase', 'PHASE', 16, FALSE); +END +$$; + +CREATE OR REPLACE FUNCTION is_admin(_user_id uuid, _team_id uuid) RETURNS boolean + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + RETURN EXISTS(SELECT 1 + FROM team_members + WHERE team_id = _team_id + AND user_id = _user_id + AND role_id = (SELECT id + FROM roles + WHERE id = team_members.role_id + AND admin_role IS TRUE)); +END +$$; + +CREATE OR REPLACE FUNCTION is_completed(_status_id uuid, _project_id uuid) RETURNS boolean + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + RETURN (SELECT _status_id IN (SELECT id + FROM task_statuses + WHERE project_id = _project_id + AND category_id = + (SELECT id FROM sys_task_status_categories WHERE is_done IS TRUE))); +END +$$; + +CREATE OR REPLACE FUNCTION is_doing(_status_id uuid, _project_id uuid) RETURNS boolean + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + RETURN (SELECT _status_id IN (SELECT id + FROM task_statuses + WHERE project_id = _project_id + AND category_id = + (SELECT id FROM sys_task_status_categories WHERE is_doing IS TRUE))); +END +$$; + +CREATE OR REPLACE FUNCTION is_member_of_project(_project_id uuid, _user_id uuid, _team_id uuid) RETURNS boolean + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + RETURN EXISTS(SELECT 1 + FROM project_members + WHERE project_id = _project_id + AND team_member_id = (SELECT id FROM team_members WHERE team_id = _team_id AND user_id = _user_id)); +END +$$; + +CREATE OR REPLACE FUNCTION is_null_or_empty(_value anyelement) RETURNS boolean + LANGUAGE plpgsql +AS +$$ +BEGIN + RETURN COALESCE(TRIM(_value::TEXT), '') = ''; +END; +$$; + +CREATE OR REPLACE FUNCTION is_overdue(_task_id uuid) RETURNS boolean + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + RETURN EXISTS(SELECT 1 + FROM tasks + WHERE id = _task_id + AND end_date < CURRENT_TIMESTAMP + AND is_completed(tasks.status_id, tasks.project_id) IS FALSE); +END +$$; + +CREATE OR REPLACE FUNCTION is_owner(_user_id uuid, _team_id uuid) RETURNS boolean + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + RETURN EXISTS(SELECT 1 + FROM teams + WHERE teams.user_id = _user_id + AND teams.id = _team_id); +END +$$; + +CREATE OR REPLACE FUNCTION is_todo(_status_id uuid, _project_id uuid) RETURNS boolean + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + RETURN (SELECT _status_id IN (SELECT id + FROM task_statuses + WHERE project_id = _project_id + AND category_id = + (SELECT id FROM sys_task_status_categories WHERE is_todo IS TRUE))); +END +$$; + +CREATE OR REPLACE FUNCTION lower_email() RETURNS trigger + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + + IF (is_null_or_empty(NEW.email) IS FALSE) + THEN + NEW.email = LOWER(TRIM(NEW.email)); + END IF; + + RETURN NEW; +END +$$; + +CREATE OR REPLACE FUNCTION migrate_member_allocations(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _member JSON; + _output JSON; +BEGIN + FOR _member IN SELECT * FROM JSON_ARRAY_ELEMENTS(_body::JSON) + LOOP + INSERT INTO project_member_allocations(project_id, team_member_id, allocated_from, allocated_to) + VALUES ((_member ->> 'project_id')::UUID, (_member ->> 'team_member_id')::UUID, + (_member ->> 'allocated_from')::DATE, + (_member ->> 'allocated_to')::DATE); + END LOOP; + RETURN _output; +END; +$$; + +CREATE OR REPLACE FUNCTION move_tasks_and_delete_status(_body json) RETURNS void + LANGUAGE plpgsql +AS +$$ +DECLARE + _status_id UUID; + _category_id UUID; +BEGIN + _status_id = (_body ->> 'id')::UUID; + SELECT category_id FROM task_statuses WHERE id = _status_id INTO _category_id; + + IF (is_null_or_empty(_body ->> 'replacing_status') IS FALSE) + THEN + UPDATE tasks + SET status_id = (_body ->> 'replacing_status')::UUID + WHERE project_id = (_body ->> 'project_id')::UUID + AND status_id = _status_id; + END IF; + + IF ((SELECT COUNT(*) + FROM task_statuses + WHERE category_id = _category_id + AND project_id = (_body ->> 'project_id')::UUID) < 2) + THEN + RAISE 'ERROR_ONE_SHOULD_EXISTS'; + END IF; + + DELETE FROM task_statuses WHERE id = _status_id AND project_id = (_body ->> 'project_id')::UUID; +END +$$; + +CREATE OR REPLACE FUNCTION notification_settings_delete_trigger_fn() RETURNS trigger + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + DELETE FROM notification_settings WHERE user_id = OLD.user_id AND team_id = OLD.team_id; + RETURN OLD; +END +$$; + +CREATE OR REPLACE FUNCTION notification_settings_insert_trigger_fn() RETURNS trigger + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + + IF (NOT EXISTS(SELECT 1 FROM notification_settings WHERE team_id = NEW.team_id AND user_id = NEW.user_id)) AND + (is_null_or_empty(NEW.user_id) IS FALSE) AND (EXISTS(SELECT 1 FROM users WHERE id = NEW.user_id)) + THEN + INSERT INTO notification_settings (popup_notifications_enabled, show_unread_items_count, user_id, + team_id) + VALUES (TRUE, TRUE, NEW.user_id, NEW.team_id); + END IF; + + RETURN NEW; +END +$$; + +CREATE OR REPLACE FUNCTION notify_task_assignment_update(_type text, _reporter_id uuid, _task_id uuid, _user_id uuid, _team_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _reporter_name TEXT; + _task_name TEXT; + _message TEXT; + _team_id UUID; + _project_id UUID; +BEGIN + IF (is_null_or_empty(_task_id) IS FALSE) + THEN + + SELECT project_id FROM tasks WHERE id = _task_id INTO _project_id; + SELECT team_id FROM projects WHERE id = _project_id INTO _team_id; + + INSERT INTO task_updates (type, reporter_id, task_id, user_id, team_id, project_id) + VALUES (_type, _reporter_id, _task_id, _user_id, _team_id, (SELECT project_id FROM tasks WHERE id = _task_id)); + + SELECT name FROM users WHERE id = _reporter_id INTO _reporter_name; + SELECT name FROM tasks WHERE id = _task_id INTO _task_name; + + IF (_type = 'ASSIGN') + THEN + _message = CONCAT('', _reporter_name, ' has assigned you in ', _task_name, ''); + ELSE + _message = CONCAT('', _reporter_name, ' has removed you from ', _task_name, ''); + END IF; + + PERFORM create_notification(_user_id, _team_id, _task_id, _project_id, _message); + + RETURN JSON_BUILD_OBJECT( + 'receiver_socket_id', (SELECT socket_id FROM users WHERE id = _user_id), + 'team', (SELECT name FROM teams WHERE id = _team_id), + 'team_id', _team_id, + 'project', (SELECT name FROM projects WHERE id = _project_id), + 'project_color', (SELECT color_code FROM projects WHERE id = _project_id), + 'project_id', _project_id, + 'task_id', _task_id, + 'message', _message + ); + + END IF; + + RETURN NULL; +END +$$; + +CREATE OR REPLACE FUNCTION register_google_user(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _user_id UUID; + _organization_id UUID; + _team_id UUID; + _role_id UUID; + + _name TEXT; + _email TEXT; + _google_id TEXT; +BEGIN + _name = (_body ->> 'displayName')::TEXT; + _email = (_body ->> 'email')::TEXT; + _google_id = (_body ->> 'id'); + + INSERT INTO users (name, email, google_id, timezone_id) + VALUES (_name, _email, _google_id, COALESCE((SELECT id FROM timezones WHERE name = (_body ->> 'timezone')), + (SELECT id FROM timezones WHERE name = 'UTC'))) + RETURNING id INTO _user_id; + + --insert organization data + INSERT INTO organizations (user_id, organization_name, contact_number, contact_number_secondary, trial_in_progress, + trial_expire_date, subscription_status, license_type_id) + VALUES (_user_id, TRIM((_body ->> 'displayName')::TEXT), NULL, NULL, TRUE, CURRENT_DATE + INTERVAL '14 days', + 'trialing', (SELECT id FROM sys_license_types WHERE key = 'TRIAL')) + RETURNING id INTO _organization_id; + + INSERT INTO teams (name, user_id, organization_id) + VALUES (_name, _user_id, _organization_id) + RETURNING id INTO _team_id; + + -- insert default roles + INSERT INTO roles (name, team_id, default_role) VALUES ('Member', _team_id, TRUE); + INSERT INTO roles (name, team_id, admin_role) VALUES ('Admin', _team_id, TRUE); + INSERT INTO roles (name, team_id, owner) VALUES ('Owner', _team_id, TRUE) RETURNING id INTO _role_id; + + INSERT INTO team_members (user_id, team_id, role_id) + VALUES (_user_id, _team_id, _role_id); + + IF (is_null_or_empty(_body ->> 'team') OR is_null_or_empty(_body ->> 'member_id')) + THEN + UPDATE users SET active_team = _team_id WHERE id = _user_id; + ELSE + -- Verify team member + IF EXISTS(SELECT id + FROM team_members + WHERE id = (_body ->> 'member_id')::UUID + AND team_id = (_body ->> 'team')::UUID) + THEN + UPDATE team_members + SET user_id = _user_id + WHERE id = (_body ->> 'member_id')::UUID + AND team_id = (_body ->> 'team')::UUID; + + DELETE + FROM email_invitations + WHERE team_id = (_body ->> 'team')::UUID + AND team_member_id = (_body ->> 'member_id')::UUID; + + UPDATE users SET active_team = (_body ->> 'team')::UUID WHERE id = _user_id; + END IF; + END IF; + + RETURN JSON_BUILD_OBJECT( + 'id', _user_id, + 'email', _email, + 'google_id', _google_id + ); +END +$$; + +CREATE OR REPLACE FUNCTION register_user(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _user_id UUID; + _organization_id UUID; + _team_id UUID; + _role_id UUID; + _trimmed_email TEXT; + _trimmed_name TEXT; + _trimmed_team_name TEXT; +BEGIN + + _trimmed_email = LOWER(TRIM((_body ->> 'email'))); + _trimmed_name = TRIM((_body ->> 'name')); + _trimmed_team_name = TRIM((_body ->> 'team_name')); + + -- check user exists + IF EXISTS(SELECT email FROM users WHERE email = _trimmed_email) + THEN + RAISE 'EMAIL_EXISTS_ERROR:%', (_body ->> 'email'); + END IF; + + -- insert user + INSERT INTO users (name, email, password, timezone_id) + VALUES (_trimmed_name, _trimmed_email, (_body ->> 'password'), + COALESCE((SELECT id FROM timezones WHERE name = (_body ->> 'timezone')), + (SELECT id FROM timezones WHERE name = 'UTC'))) + RETURNING id INTO _user_id; + + --insert organization data + INSERT INTO organizations (user_id, organization_name, contact_number, contact_number_secondary, trial_in_progress, + trial_expire_date, subscription_status, license_type_id) + VALUES (_user_id, TRIM((_body ->> 'team_name')::TEXT), NULL, NULL, TRUE, CURRENT_DATE + INTERVAL '14 days', + 'trialing', (SELECT id FROM sys_license_types WHERE key = 'TRIAL')) + RETURNING id INTO _organization_id; + + -- insert team + INSERT INTO teams (name, user_id, organization_id) + VALUES (_trimmed_team_name, _user_id, _organization_id) + RETURNING id INTO _team_id; + + IF (is_null_or_empty((_body ->> 'invited_team_id'))) + THEN + UPDATE users SET active_team = _team_id WHERE id = _user_id; + ELSE + IF NOT EXISTS(SELECT id + FROM email_invitations + WHERE team_id = (_body ->> 'invited_team_id')::UUID + AND email = _trimmed_email) + THEN + RAISE 'ERROR_INVALID_JOINING_EMAIL'; + END IF; + UPDATE users SET active_team = (_body ->> 'invited_team_id')::UUID WHERE id = _user_id; + END IF; + + -- insert default roles + INSERT INTO roles (name, team_id, default_role) VALUES ('Member', _team_id, TRUE); + INSERT INTO roles (name, team_id, admin_role) VALUES ('Admin', _team_id, TRUE); + INSERT INTO roles (name, team_id, owner) VALUES ('Owner', _team_id, TRUE) RETURNING id INTO _role_id; + + -- insert team member + INSERT INTO team_members (user_id, team_id, role_id) + VALUES (_user_id, _team_id, _role_id); + + -- update team member table with user id + IF (_body ->> 'team_member_id') IS NOT NULL + THEN + UPDATE team_members SET user_id = (_user_id)::UUID WHERE id = (_body ->> 'team_member_id')::UUID; + DELETE + FROM email_invitations + WHERE email = _trimmed_email + AND team_member_id = (_body ->> 'team_member_id')::UUID; + END IF; + + RETURN JSON_BUILD_OBJECT( + 'id', _user_id, + 'name', _trimmed_name, + 'email', _trimmed_email, + 'team_id', _team_id + ); +END; +$$; + +CREATE OR REPLACE FUNCTION remove_project_member(_id uuid, _user_id uuid, _team_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _team_member_id UUID; + _project_id UUID; + _member_user_id UUID; + _notification TEXT; +BEGIN + SELECT project_id FROM project_members WHERE id = _id INTO _project_id; + SELECT team_member_id FROM project_members WHERE id = _id INTO _team_member_id; + SELECT user_id FROM team_members WHERE id = _team_member_id INTO _member_user_id; + DELETE FROM project_members WHERE id = _id; + DELETE FROM project_member_allocations WHERE project_id = _project_id AND team_member_id = _team_member_id; + + IF (_member_user_id != _user_id) + THEN + _notification = + CONCAT('You have been removed from the ', (SELECT name FROM projects WHERE id = _project_id), + ' by ', + (SELECT name FROM users WHERE id = _user_id), ''); + PERFORM create_notification( + (SELECT user_id FROM team_members WHERE id = _team_member_id), + _team_id, + NULL, + _project_id, + _notification + ); + END IF; + + RETURN JSON_BUILD_OBJECT( + 'id', _id, + 'notification', _notification, + 'socket_id', (SELECT socket_id FROM users WHERE id = _member_user_id), + 'project', (SELECT name FROM projects WHERE id = _project_id), + 'project_id', _project_id, + 'project_color', (SELECT color_code FROM projects WHERE id = _project_id), + 'team', (SELECT name FROM teams WHERE id = _team_id), + 'member_user_id', _member_user_id + ); +END; +$$; + +CREATE OR REPLACE FUNCTION remove_task_assignee(_task_id uuid, _team_member_id uuid, _project_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _team_id UUID; + _user_id UUID; +BEGIN + + SELECT team_id FROM team_members WHERE id = _team_member_id INTO _team_id; + SELECT user_id FROM team_members WHERE id = _team_member_id INTO _user_id; + + DELETE + FROM tasks_assignees + WHERE task_id = _task_id + AND project_member_id = + (SELECT id FROM project_members WHERE team_member_id = _team_member_id AND project_id = _project_id); + + RETURN JSON_BUILD_OBJECT( + 'user_id', _user_id, + 'team_id', _team_id + ); +END +$$; + +CREATE OR REPLACE FUNCTION remove_team_member(_id uuid, _user_id uuid, _team_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _removed_user_id UUID; + _removed_team_name TEXT; +BEGIN + SELECT user_id FROM team_members WHERE id = _id INTO _removed_user_id; + SELECT name FROM teams WHERE id = _team_id INTO _removed_team_name; + + UPDATE users + SET active_team = (SELECT id FROM teams WHERE user_id = _removed_user_id LIMIT 1) + WHERE active_team = _team_id + AND id = _removed_user_id; + + PERFORM create_notification( + _removed_user_id, + _team_id, + NULL, + NULL, + CONCAT('You have been removed from ', (SELECT name FROM teams WHERE id = _team_id), ' by ', + (SELECT name FROM users WHERE id = _user_id), '') + ); + + DELETE FROM team_members WHERE id = _id AND team_id = _team_id; + + RETURN JSON_BUILD_OBJECT( + 'id', _removed_user_id, + 'team', _removed_team_name, + 'socket_id', (SELECT socket_id FROM users WHERE id = _user_id) + ); +END; +$$; + +CREATE OR REPLACE FUNCTION resend_team_invitation(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _id UUID; + _team_id UUID; + _user_id UUID; + _email TEXT; + _output JSON; +BEGIN + _team_id = (_body ->> 'team_id')::UUID; + _id = (_body ->> 'id')::UUID; + + SELECT email FROM email_invitations WHERE team_member_id = _id AND team_id = _team_id INTO _email; + SELECT id FROM users WHERE email = _email INTO _user_id; + + IF is_null_or_empty(_email) IS FALSE + THEN + DELETE FROM email_invitations WHERE team_id = _team_id AND team_member_id = _id; + END IF; + + INSERT INTO email_invitations(team_id, team_member_id, email, name) + VALUES (_team_id, _id, _email, SPLIT_PART(_email, '@', 1)); + + SELECT JSON_BUILD_OBJECT( + 'name', (SELECT name FROM users WHERE id = _user_id), + 'email', _email, + 'is_new', is_null_or_empty(_user_id), + 'team_member_id', _id, + 'team_member_user_id', _user_id + ) + INTO _output; + + RETURN _output; +END; +$$; + +CREATE OR REPLACE FUNCTION set_active_team(_user_id uuid, _team_id uuid) RETURNS void + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + IF EXISTS(SELECT id FROM team_members WHERE team_id = _team_id AND user_id = _user_id) + THEN + UPDATE users SET active_team = _team_id WHERE id = _user_id; + ELSE + UPDATE users SET active_team = (SELECT id FROM teams WHERE user_id = users.id LIMIT 1) WHERE id = _user_id; + END IF; +END; +$$; + +CREATE OR REPLACE FUNCTION set_active_team_by_member_id(_team_member_id uuid) RETURNS void + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + PERFORM set_active_team( + (SELECT user_id FROM team_members WHERE id = _team_member_id), + (SELECT team_id FROM team_members WHERE id = _team_member_id) + ); +END; +$$; + +CREATE OR REPLACE FUNCTION set_task_updated_at_trigger_fn() RETURNS trigger + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + NEW.updated_at = CURRENT_TIMESTAMP; + RETURN NEW; +END +$$; + +CREATE OR REPLACE FUNCTION slugify(value text) RETURNS text + IMMUTABLE + STRICT + LANGUAGE sql +AS +$$ + -- removes accents (diacritic signs) from a given string -- +WITH "unaccented" AS (SELECT unaccent("value") AS "value"), + -- lowercase the string + "lowercase" AS (SELECT LOWER("value") AS "value" + FROM "unaccented"), + -- replaces anything that's not a letter, number, hyphen('-'), or underscore('_') with a hyphen('-') + "hyphenated" AS (SELECT REGEXP_REPLACE("value", '[^a-z0-9\\-_.''`"]+', '-', 'gi') AS "value" + FROM "lowercase"), + -- trims hyphens('-') if they exist on the head or tail of the string + "trimmed" AS (SELECT REGEXP_REPLACE(REGEXP_REPLACE("value", '\\-+$', ''), '^\\-', '') AS "value" + FROM "hyphenated") +SELECT "value" +FROM "trimmed"; +$$; + +CREATE OR REPLACE FUNCTION sys_insert_project_healths() RETURNS void + LANGUAGE plpgsql +AS +$$ +BEGIN + INSERT INTO sys_project_healths (name, color_code, sort_order, is_default) + VALUES ('Not Set', '#a9a9a9', 0, TRUE); + INSERT INTO sys_project_healths (name, color_code, sort_order, is_default) + VALUES ('Needs Attention', '#fbc84c', 1, FALSE); + INSERT INTO sys_project_healths (name, color_code, sort_order, is_default) + VALUES ('At Risk', '#f37070', 2, FALSE); + INSERT INTO sys_project_healths (name, color_code, sort_order, is_default) + VALUES ('Good', '#75c997', 3, FALSE); +END; +$$; + +CREATE OR REPLACE FUNCTION task_status_change_trigger_fn() RETURNS trigger + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + IF ((SELECT is_done + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = NEW.status_id)) IS TRUE) + THEN + UPDATE tasks SET completed_at = CURRENT_TIMESTAMP WHERE id = NEW.id; + ELSE + UPDATE tasks SET completed_at = NULL WHERE id = NEW.id; + END IF; + + RETURN NEW; +END +$$; + +CREATE OR REPLACE FUNCTION tasks_task_subscriber_notify_done_trigger() RETURNS trigger + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + IF (EXISTS(SELECT 1 + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = NEW.status_id) + AND is_done IS TRUE)) + THEN + PERFORM pg_notify('db_task_status_changed', NEW.id::TEXT); + END IF; + + RETURN NEW; +END +$$; + +CREATE OR REPLACE FUNCTION to_seconds(t text) RETURNS integer + LANGUAGE plpgsql +AS +$$ +DECLARE + hs INTEGER; + ms INTEGER; + s INTEGER; +BEGIN + SELECT (EXTRACT(HOUR FROM t::TIME) * 60 * 60) INTO hs; + SELECT (EXTRACT(MINUTES FROM t::TIME) * 60) INTO ms; + SELECT (EXTRACT(SECONDS FROM t::TIME)) INTO s; + SELECT (hs + ms + s) INTO s; + RETURN s; +END; +$$; + +CREATE OR REPLACE FUNCTION toggle_archive_all_projects(_project_id uuid) RETURNS void + LANGUAGE plpgsql +AS +$$ +DECLARE + _user_id UUID; + +BEGIN + IF EXISTS(SELECT project_id FROM archived_projects WHERE project_id = _project_id) + THEN + DELETE FROM archived_projects WHERE project_id = _project_id; + ELSE + FOR _user_id IN + SELECT user_id FROM team_members WHERE team_id = (SELECT team_id FROM projects WHERE id = _project_id) + LOOP + IF NOT (is_null_or_empty(_user_id)) + THEN + INSERT INTO archived_projects (user_id, project_id) + VALUES (_user_id, _project_id); + END IF; + END LOOP; + END IF; +END +$$; + +CREATE OR REPLACE FUNCTION toggle_archive_project(_user_id uuid, _project_id uuid) RETURNS void + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + IF EXISTS(SELECT user_id FROM archived_projects WHERE user_id = _user_id AND project_id = _project_id) + THEN + DELETE FROM archived_projects WHERE user_id = _user_id AND project_id = _project_id; + ELSE + INSERT INTO archived_projects (user_id, project_id) VALUES (_user_id, _project_id); + END IF; +END +$$; + +CREATE OR REPLACE FUNCTION toggle_favorite_project(_user_id uuid, _project_id uuid) RETURNS void + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + IF EXISTS(SELECT user_id FROM favorite_projects WHERE user_id = _user_id AND project_id = _project_id) + THEN + DELETE FROM favorite_projects WHERE user_id = _user_id AND project_id = _project_id; + ELSE + INSERT INTO favorite_projects (user_id, project_id) VALUES (_user_id, _project_id); + END IF; +END +$$; + +CREATE OR REPLACE FUNCTION update_project(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _user_id UUID; + _team_id UUID; + _client_id UUID; + _project_id UUID; + _project_manager_team_member_id UUID; + _client_name TEXT; + _project_name TEXT; +BEGIN + -- need a test, can be throw errors + _client_name = TRIM((_body ->> 'client_name')::TEXT); + _project_name = TRIM((_body ->> 'name')::TEXT); + + -- add inside the controller + _user_id = (_body ->> 'user_id')::UUID; + _team_id = (_body ->> 'team_id')::UUID; + _project_manager_team_member_id = (_body ->> 'team_member_id')::UUID; + + -- cache exists client if exists + SELECT id FROM clients WHERE LOWER(name) = LOWER(_client_name) AND team_id = _team_id INTO _client_id; + + -- insert client if not exists + IF is_null_or_empty(_client_id) IS TRUE AND is_null_or_empty(_client_name) IS FALSE + THEN + INSERT INTO clients (name, team_id) VALUES (_client_name, _team_id) RETURNING id INTO _client_id; + END IF; + + -- check whether the project name is already in + IF EXISTS( + SELECT name FROM projects WHERE LOWER(name) = LOWER(_project_name) + AND team_id = _team_id AND id != (_body ->> 'id')::UUID + ) + THEN + RAISE 'PROJECT_EXISTS_ERROR:%', _project_name; + END IF; + + -- update the project + UPDATE projects + SET name = _project_name, + notes = (_body ->> 'notes')::TEXT, + color_code = (_body ->> 'color_code')::TEXT, + status_id = (_body ->> 'status_id')::UUID, + health_id = (_body ->> 'health_id')::UUID, + key = (_body ->> 'key')::TEXT, + start_date = (_body ->> 'start_date')::TIMESTAMPTZ, + end_date = (_body ->> 'end_date')::TIMESTAMPTZ, + client_id = _client_id, + folder_id = (_body ->> 'folder_id')::UUID, + category_id = (_body ->> 'category_id')::UUID, + updated_at = CURRENT_TIMESTAMP, + estimated_working_days = (_body ->> 'working_days')::INTEGER, + estimated_man_days = (_body ->> 'man_days')::INTEGER, + hours_per_day = (_body ->> 'hours_per_day')::INTEGER + WHERE id = (_body ->> 'id')::UUID + AND team_id = _team_id + RETURNING id INTO _project_id; + + UPDATE project_members SET project_access_level_id = (SELECT id FROM project_access_levels WHERE key = 'MEMBER') WHERE project_id = _project_id; + + IF NOT (_project_manager_team_member_id IS NULL) + THEN + PERFORM update_project_manager(_project_manager_team_member_id, _project_id::UUID); + END IF; + + RETURN JSON_BUILD_OBJECT( + 'id', _project_id, + 'name', (_body ->> 'name')::TEXT, + 'project_manager_id', _project_manager_team_member_id::UUID + ); +END; +$$; + +CREATE OR REPLACE FUNCTION update_project_manager(_team_member_id uuid, _project_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _project_member_id UUID; + _team_id UUID; + _user_id UUID; + _project_member JSON; +BEGIN + + SELECT id + FROM project_members + WHERE team_member_id = _team_member_id + AND project_id = _project_id + INTO _project_member_id; + + SELECT team_id FROM team_members WHERE id = _team_member_id INTO _team_id; + SELECT user_id FROM team_members WHERE id = _team_member_id INTO _user_id; + + IF is_null_or_empty(_project_member_id) + THEN + SELECT create_project_member(JSON_BUILD_OBJECT( + 'team_member_id', _team_member_id, + 'team_id', _team_id, + 'project_id', _project_id, + 'user_id', _user_id, + 'access_level', 'PROJECT_MANAGER'::TEXT + )) + INTO _project_member; + END IF; + + UPDATE project_members SET project_access_level_id = (SELECT id FROM project_access_levels WHERE key = 'PROJECT_MANAGER') WHERE id = _project_member_id AND project_id = _project_id; + + RETURN JSON_BUILD_OBJECT( + 'project_member_id', _project_member_id, + 'team_member_id', _team_member_id, + 'team_id', _team_id, + 'user_id', _user_id + ); +END +$$; + +CREATE OR REPLACE FUNCTION update_project_tasks_counter_trigger_fn() RETURNS trigger + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + + UPDATE projects SET tasks_counter = (tasks_counter + 1) WHERE id = NEW.project_id; + NEW.task_no = (SELECT tasks_counter FROM projects WHERE id = NEW.project_id); + + RETURN NEW; +END +$$; + +CREATE OR REPLACE FUNCTION update_status_order(_status_ids json) RETURNS void + LANGUAGE plpgsql +AS +$$ +DECLARE + _iterator NUMERIC := 0; + _status_id TEXT; +BEGIN + + FOR _status_id IN SELECT * FROM JSON_ARRAY_ELEMENTS((_status_ids)::JSON) + LOOP + UPDATE task_statuses + SET sort_order = _iterator + WHERE id = (SELECT TRIM(BOTH '"' FROM _status_id))::UUID; + _iterator := _iterator + 1; + END LOOP; + + RETURN; +END +$$; + +CREATE OR REPLACE FUNCTION update_task(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _assignee TEXT; + _assignee_id UUID; + _label JSON; + _old_assignees JSON; + _new_assignees JSON; +BEGIN + UPDATE tasks + SET name = TRIM((_body ->> 'name')::TEXT), + start_date = (_body ->> 'start')::TIMESTAMPTZ, + end_date = (_body ->> 'end')::TIMESTAMPTZ, + priority_id = (_body ->> 'priority_id')::UUID, + description = COALESCE(TRIM((_body ->> 'description')::TEXT), description), + total_minutes = (_body ->> 'total_minutes')::NUMERIC, + status_id = (_body ->> 'status_id')::UUID + WHERE id = (_body ->> 'id')::UUID; + + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _old_assignees + FROM ( + -- + SELECT team_member_id, + (SELECT user_id FROM team_members WHERE id = tasks_assignees.team_member_id), + (SELECT team_id FROM team_members WHERE id = tasks_assignees.team_member_id) + FROM tasks_assignees + WHERE task_id = (_body ->> 'id')::UUID + -- + ) rec; + + -- delete existing task assignees + DELETE FROM tasks_assignees WHERE task_id = (_body ->> 'id')::UUID; + + -- insert task assignees + FOR _assignee IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'assignees')::JSON) + LOOP + _assignee_id = TRIM('"' FROM _assignee)::UUID; + PERFORM create_task_assignee(_assignee_id, (_body ->> 'project_id')::UUID, (_body ->> 'id')::UUID, + (_body ->> 'reporter_id')::UUID); + END LOOP; + + IF ((_body ->> 'inline')::BOOLEAN IS FALSE) + THEN + DELETE FROM task_labels WHERE task_id = (_body ->> 'id')::UUID; + FOR _label IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'labels')::JSON) + LOOP + PERFORM assign_or_create_label((_body ->> 'team_id')::UUID, (_body ->> 'id')::UUID, + (_label ->> 'name')::TEXT, + (_label ->> 'color')::TEXT); + END LOOP; + END IF; + + + SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + INTO _new_assignees + FROM ( + -- + SELECT team_member_id, + (SELECT user_id FROM team_members WHERE id = tasks_assignees.team_member_id), + (SELECT team_id FROM team_members WHERE id = tasks_assignees.team_member_id) + FROM tasks_assignees + WHERE task_id = (_body ->> 'id')::UUID + -- + ) rec; + + RETURN JSON_BUILD_OBJECT( + 'id', (_body ->> 'id')::UUID, + 'name', (_body ->> 'name')::TEXT, + 'old_assignees', _old_assignees, + 'new_assignees', _new_assignees + ); +END; +$$; + +CREATE OR REPLACE FUNCTION update_task_status(_updated_task_id uuid, _status_id uuid, _task_ids json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _iterator NUMERIC := 0; + _task_id TEXT; +BEGIN + UPDATE tasks SET status_id = _status_id WHERE id = _updated_task_id; + + FOR _task_id IN SELECT * FROM JSON_ARRAY_ELEMENTS((_task_ids)::JSON) + LOOP + UPDATE tasks + SET sort_order = _iterator + WHERE id = (SELECT TRIM(BOTH '"' FROM _task_id))::UUID; + _iterator := _iterator + 1; + END LOOP; + + RETURN JSON_BUILD_OBJECT( + 'id', _updated_task_id + ); +END; +$$; + +CREATE OR REPLACE FUNCTION update_task_status(_task_id uuid, _project_id uuid, _status_id uuid, _from_index integer, _to_index integer) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + UPDATE tasks SET status_id = _status_id WHERE id = _task_id AND project_id = _project_id; + + IF (_from_index != _to_index) + THEN + IF _to_index > _from_index + THEN + UPDATE tasks + SET sort_order = sort_order - 1 + WHERE project_id = _project_id + AND sort_order > _from_index + AND sort_order <= _to_index; + END IF; + + IF _to_index < _from_index + THEN + UPDATE tasks + SET sort_order = sort_order + 1 + WHERE project_id = _project_id + AND sort_order >= _to_index + AND sort_order < _from_index; + END IF; + + UPDATE tasks SET sort_order = _to_index WHERE id = _task_id AND project_id = _project_id; + END IF; + + RETURN JSON_BUILD_OBJECT( + 'id', _task_id + ); +END; +$$; + +CREATE OR REPLACE FUNCTION update_task_template(_id uuid, _name text, _tasks json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _task JSON; + +BEGIN + UPDATE task_templates SET name = _name, updated_at = NOW() WHERE id = _id; + + -- delete all existing tasks for the selected template + DELETE FROM task_templates_tasks WHERE template_id = _id; + + -- insert tasks for task templates + FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS(_tasks) + LOOP + INSERT INTO task_templates_tasks (template_id, name) VALUES (_id, (_task ->> 'name')::TEXT); + END LOOP; + + RETURN JSON_BUILD_OBJECT( + 'id', _id, + 'template_name', _name + ); +END +$$; + +CREATE OR REPLACE FUNCTION update_team_member(_body json) RETURNS void + LANGUAGE plpgsql +AS +$$ +DECLARE + _team_id UUID; + _job_title_id UUID; + _role_id UUID; +BEGIN + _team_id = (_body ->> 'team_id')::UUID; + + IF ((_body ->> 'is_admin')::BOOLEAN IS TRUE) + THEN + SELECT id FROM roles WHERE admin_role IS TRUE INTO _role_id; + ELSE + SELECT id FROM roles WHERE default_role IS TRUE INTO _role_id; + END IF; + + IF is_null_or_empty((_body ->> 'job_title')) IS FALSE + THEN + SELECT insert_job_title((_body ->> 'job_title')::TEXT, _team_id) INTO _job_title_id; + ELSE + _job_title_id = NULL; + END IF; + + UPDATE team_members + SET job_title_id = _job_title_id, + role_id = _role_id, + updated_at = CURRENT_TIMESTAMP + WHERE id = (_body ->> 'id')::UUID + AND team_id = _team_id; +END; +$$; + +CREATE OR REPLACE FUNCTION update_team_name(_body json) RETURNS void + LANGUAGE plpgsql +AS +$$ +DECLARE + _name TEXT; +BEGIN + _name = (_body ->> 'name')::TEXT; + + IF ((SELECT name FROM teams WHERE id = (_body ->> 'id')::UUID) != _name) + THEN + UPDATE teams + SET name = _name, + updated_at = CURRENT_TIMESTAMP + WHERE id = (_body ->> 'id')::UUID + AND user_id = (_body ->> 'user_id')::UUID; + END IF; +END; +$$; + +CREATE OR REPLACE FUNCTION update_existing_phase_colors(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + result JSON; + _phase JSON; +BEGIN + + FOR _phase IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'phases')::JSON) + LOOP + UPDATE project_phases SET color_code = (_phase ->> 'color_code')::WL_HEX_COLOR WHERE id = (_phase ->> 'id')::UUID; + END LOOP; + + SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))) + FROM (SELECT * FROM project_phases) rec + INTO result; + + RETURN result; +END; +$$; + +CREATE OR REPLACE FUNCTION update_existing_phase_sort_order(_body json) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + result JSON; + _phase JSON; +BEGIN + + FOR _phase IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'phases')::JSON) + LOOP + UPDATE project_phases SET sort_index = (_phase ->> 'sort_number')::INT WHERE id = (_phase ->> 'id')::UUID AND project_id = (_phase ->> 'project_id')::UUID; + END LOOP; + + SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))) + FROM (SELECT * FROM project_phases) rec + INTO result; + + RETURN result; +END; +$$; + +CREATE OR REPLACE FUNCTION handle_phase_sort_order(_body json) RETURNS void + LANGUAGE plpgsql +AS +$$ +DECLARE + _phase JSON; + _sort_index INT := 0; +BEGIN + + FOR _phase IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'phases')::JSON) + LOOP + UPDATE project_phases SET sort_index = _sort_index::INT WHERE id = (_phase ->> 'id')::UUID AND project_id = (_body ->> 'project_id')::UUID; + _sort_index = _sort_index + 1; + END LOOP; +END +$$; + +CREATE OR REPLACE FUNCTION bulk_change_tasks_priority(_body json, _userid uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _task JSON; + _output JSON; + _previous_priority UUID; +BEGIN + FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) + LOOP + _previous_priority = (SELECT priority_id FROM tasks WHERE id = (_task ->> 'id')::UUID); + + UPDATE tasks SET priority_id = (_body ->> 'priority_id')::UUID WHERE id = (_task ->> 'id')::UUID; + + IF (_previous_priority IS DISTINCT FROM (_body ->> 'priority_id')::UUID) + THEN + INSERT INTO task_activity_logs (task_id, team_id, attribute_type, user_id, log_type, old_value, new_value, project_id) + VALUES ( + (_task ->> 'id')::UUID, + (SELECT team_id FROM projects WHERE id = (SELECT project_id FROM tasks WHERE id = (_task ->> 'id')::UUID)), + 'priority', + _userId, + 'update', + _previous_priority, + (_body ->> 'priority_id')::UUID, + (SELECT project_id FROM tasks WHERE id = (_task ->> 'id')::UUID) + ); + END IF; + END LOOP; + RETURN _output; +END; +$$; + +CREATE OR REPLACE FUNCTION bulk_change_tasks_phase(_body json, _userid uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _task JSON; + _output JSON; + _previous_phase UUID; +BEGIN + FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) + LOOP + _previous_phase = (SELECT phase_id FROM task_phase WHERE task_id = (_task ->> 'id')::UUID); + + IF NOT EXISTS(SELECT 1 FROM task_phase WHERE task_id = (_task ->> 'id')::UUID) + THEN + INSERT INTO task_phase (task_id, phase_id) VALUES ((_task ->> 'id')::UUID, (_body ->> 'phase_id')::UUID); + ELSE + UPDATE task_phase SET phase_id = (_body ->> 'phase_id')::UUID WHERE task_id = (_task ->> 'id')::UUID; + END IF; + + IF (_previous_phase IS DISTINCT FROM (_body ->> 'phase_id')::UUID) + THEN + INSERT INTO task_activity_logs (task_id, team_id, attribute_type, user_id, log_type, old_value, new_value, project_id) + VALUES ( + (_task ->> 'id')::UUID, + (SELECT team_id FROM projects WHERE id = (SELECT project_id FROM tasks WHERE id = (_task ->> 'id')::UUID)), + 'phase', + _userId, + 'update', + _previous_phase, + (_body ->> 'phase_id')::UUID, + (SELECT project_id FROM tasks WHERE id = (_task ->> 'id')::UUID) + ); + END IF; + + + END LOOP; + RETURN _output; +END; +$$; + +CREATE OR REPLACE FUNCTION bulk_assign_label(_body json, _user_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _task JSON; + _label JSON; + _output JSON; +BEGIN + FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) + LOOP + FOR _label IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'labels')::JSON) + LOOP + + DELETE FROM task_labels WHERE task_id = (_task ->> 'id')::UUID AND label_id = (_label ->> 'id')::UUID; + + INSERT INTO task_labels (task_id, label_id) VALUES ((_task ->> 'id')::UUID, (_label ->> 'id')::UUID); + + INSERT INTO task_activity_logs + (task_id, team_id, attribute_type, user_id, log_type, old_value, new_value, next_string, project_id) + VALUES + ( + (_task ->> 'id')::UUID, + (SELECT team_id FROM projects WHERE id = (SELECT project_id FROM tasks WHERE tasks.id = (_task ->> 'id')::UUID)), + 'label', + _user_id::UUID, + 'create', + NULL, + (_label ->> 'id')::UUID, + NULL, + (SELECT project_id FROM tasks WHERE tasks.id = (_task ->> 'id')::UUID) + ); + + END LOOP; + END LOOP; + + RETURN _output; +END; +$$; + +CREATE OR REPLACE FUNCTION update_phase_name(_phase_id uuid, _phase_name text, _template_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _color_code TEXT; +BEGIN + IF EXISTS(SELECT name + FROM cpt_phases + WHERE name = _phase_name + AND template_id = _template_id) + THEN + RAISE 'PHASE_EXISTS_ERROR:%', _phase_name::TEXT; + END IF; + UPDATE cpt_phases + SET name = _phase_name + WHERE id = _phase_id + AND template_id = _template_id + RETURNING color_code INTO _color_code; + + RETURN JSON_BUILD_OBJECT( + 'color_code', _color_code, + 'id', _phase_id, + 'name', _phase_name + ); +END +$$; + +CREATE OR REPLACE FUNCTION mark_bulk_refunds(coupon_data jsonb[]) RETURNS void + LANGUAGE plpgsql +AS +$$ +DECLARE + data_record JSONB; +BEGIN + FOREACH data_record IN ARRAY coupon_data + LOOP + UPDATE licensing_coupon_codes + SET is_refunded = TRUE, + reason = TRIM(data_record ->> 'reason'), + feedback = TRIM(data_record ->> 'feedback'), + refunded_at = (data_record ->> 'refund_date')::TIMESTAMPTZ + WHERE coupon_code = (data_record ->> 'code')::TEXT; + END LOOP; +END; +$$; + +CREATE OR REPLACE FUNCTION is_overdue_for_date(_task_id uuid, _end_date date) RETURNS boolean + LANGUAGE plpgsql +AS +$$ +DECLARE +BEGIN + RETURN EXISTS(SELECT 1 + FROM tasks + WHERE id = _task_id + AND end_date < _end_date + AND is_completed(tasks.status_id, tasks.project_id) IS FALSE); +END +$$; + +CREATE OR REPLACE FUNCTION is_completed_between(_task_id uuid, _start_date date, _end_date date) RETURNS boolean + LANGUAGE plpgsql +AS +$$ +DECLARE + BEGIN + RETURN EXISTS ( SELECT 1 FROM tasks WHERE id = _task_id AND completed_at::DATE >= _start_date::DATE AND completed_at::DATE <= _end_date::DATE); +END +$$; + +CREATE OR REPLACE FUNCTION update_task_template(_id uuid, _name text, _tasks json, _team_id uuid) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _task JSON; + +BEGIN + + -- check whether the project name is already in + IF EXISTS( + SELECT name FROM task_templates WHERE LOWER(name) = LOWER(_name) + AND team_id = _team_id AND id != _id + ) + THEN + RAISE 'TASK_TEMPLATE_EXISTS_ERROR:%', _name; + END IF; + + UPDATE task_templates SET name = _name, updated_at = NOW() WHERE id = _id; + + -- delete all existing tasks for the selected template + DELETE FROM task_templates_tasks WHERE template_id = _id; + + -- insert tasks for task templates + FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS(_tasks) + LOOP + INSERT INTO task_templates_tasks (template_id, name) VALUES (_id, (_task ->> 'name')::TEXT); + END LOOP; + + RETURN JSON_BUILD_OBJECT( + 'id', _id, + 'template_name', _name + ); +END +$$; + +CREATE OR REPLACE FUNCTION insert_task_dependency(_task_id uuid, _related_task_id uuid, _dependency_type dependency_type DEFAULT 'blocked_by'::dependency_type) RETURNS void + LANGUAGE plpgsql +AS +$$ +BEGIN + -- Attempt to insert into task_dependencies + INSERT INTO task_dependencies (task_id, related_task_id, dependency_type) + VALUES (_task_id, _related_task_id, _dependency_type) + ON CONFLICT (task_id, related_task_id, dependency_type) + DO NOTHING; + + -- Check if the insert was successful + IF NOT FOUND THEN + -- Raise an exception if a conflict was found + RAISE EXCEPTION 'DEPENDENCY_EXISTS'; + END IF; +END; +$$; + +CREATE OR REPLACE FUNCTION can_update_task(_task_id uuid, _status_id uuid) RETURNS boolean + LANGUAGE plpgsql +AS +$$ +DECLARE + -- Declare a variable to store whether the update can continue + can_continue BOOLEAN; +BEGIN + -- First, check if the status is not in the "done" category + SELECT EXISTS ( + SELECT 1 + FROM task_statuses ts + WHERE ts.id = _status_id + AND ts.project_id = (SELECT project_id FROM tasks WHERE id = _task_id) + AND ts.category_id IN ( + SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE + ) + ) INTO can_continue; + + -- If the status is not "done", continue the process + IF can_continue THEN + RETURN TRUE; + END IF; + + -- If the status is "done", check if any dependent tasks are not completed + SELECT NOT EXISTS ( + SELECT 1 + FROM task_dependencies td + LEFT JOIN tasks t ON t.id = td.related_task_id + WHERE td.task_id = _task_id + AND t.status_id NOT IN ( + SELECT id + FROM task_statuses ts + WHERE t.project_id = ts.project_id + AND ts.category_id IN ( + SELECT id FROM sys_task_status_categories WHERE is_done IS TRUE + ) + ) + ) INTO can_continue; + + -- Return whether the update can continue based on the dependent task completion check + RETURN can_continue; +END; +$$; + +CREATE OR REPLACE FUNCTION bulk_change_tasks_status(_body json, _userid uuid) RETURNS uuid[] + LANGUAGE plpgsql +AS +$$ +DECLARE + _task JSON; + _output JSON; + _previous_status UUID; + _failed_tasks UUID[]; +BEGIN + + FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) + LOOP + IF can_update_task((_task ->> 'id')::UUID, (_body ->> 'status_id')::UUID) + THEN + -- Proceed with the update if the task is eligible for update + _previous_status = (SELECT status_id FROM tasks WHERE id = (_task ->> 'id')::UUID); + + UPDATE tasks SET status_id = (_body ->> 'status_id')::UUID WHERE id = (_task ->> 'id')::UUID; + + IF (_previous_status IS DISTINCT FROM (_body ->> 'status_id')::UUID) + THEN + INSERT INTO task_activity_logs (task_id, team_id, attribute_type, user_id, log_type, old_value, + new_value, project_id) + VALUES ((_task ->> 'id')::UUID, + (SELECT team_id + FROM projects + WHERE id = (SELECT project_id FROM tasks WHERE id = (_task ->> 'id')::UUID)), + 'status', + _userId, + 'update', + _previous_status, + (_body ->> 'status_id')::UUID, + (SELECT project_id FROM tasks WHERE id = (_task ->> 'id')::UUID)); + END IF; + ELSE + -- Add failed task IDs to the array + _failed_tasks := ARRAY_APPEND(_failed_tasks, (_task ->> 'id')::UUID); + END IF; + + END LOOP; + RETURN _failed_tasks; +END +$$; + +CREATE OR REPLACE FUNCTION create_recurring_task_template(p_task_id uuid, p_schedule_id uuid) RETURNS uuid + LANGUAGE plpgsql +AS +$$ +DECLARE + v_new_id UUID; +BEGIN + INSERT INTO task_recurring_templates ( + id, + task_id, + schedule_id, + name, + description, + end_date, + priority_id, + project_id, + assignees, + labels + ) + SELECT + uuid_generate_v4(), + t.id AS task_id, + p_schedule_id, + t.name, + t.description, + t.end_date, + t.priority_id, + t.project_id, + COALESCE( + (SELECT JSONB_AGG(JSONB_BUILD_OBJECT('project_member_id', tas.project_member_id, 'team_member_id', tas.team_member_id)) + FROM tasks_assignees tas + WHERE tas.task_id = t.id), + '[]'::JSONB + ) AS assignees, + COALESCE( + (SELECT JSONB_AGG(JSONB_BUILD_OBJECT('label_id', tla.label_id)) + FROM task_labels tla + WHERE tla.task_id = t.id), + '[]'::JSONB + ) AS labels + FROM tasks t + WHERE t.id = p_task_id + RETURNING id INTO v_new_id; + + RETURN v_new_id; +END; +$$; diff --git a/worklenz-backend/database/4_views.sql b/worklenz-backend/database/4_views.sql deleted file mode 100644 index 78a934f7..00000000 --- a/worklenz-backend/database/4_views.sql +++ /dev/null @@ -1,34 +0,0 @@ -CREATE VIEW task_labels_view(name, task_id, label_id) AS -SELECT (SELECT team_labels.name - FROM team_labels - WHERE team_labels.id = task_labels.label_id) AS name, - task_labels.task_id, - task_labels.label_id -FROM task_labels; - -CREATE VIEW tasks_with_status_view(task_id, parent_task_id, is_todo, is_doing, is_done) AS -SELECT tasks.id AS task_id, - tasks.parent_task_id, - stsc.is_todo, - stsc.is_doing, - stsc.is_done -FROM tasks - JOIN task_statuses ts ON tasks.status_id = ts.id - JOIN sys_task_status_categories stsc ON ts.category_id = stsc.id -WHERE tasks.archived IS FALSE; - -CREATE VIEW team_member_info_view(avatar_url, email, name, user_id, team_member_id, team_id) AS -SELECT u.avatar_url, - COALESCE(u.email, (SELECT email_invitations.email - FROM email_invitations - WHERE email_invitations.team_member_id = team_members.id)) AS email, - COALESCE(u.name, (SELECT email_invitations.name - FROM email_invitations - WHERE email_invitations.team_member_id = team_members.id)) AS name, - u.id AS user_id, - team_members.id AS team_member_id, - team_members.team_id -FROM team_members - LEFT JOIN users u ON team_members.user_id = u.id; - - diff --git a/worklenz-backend/database/5_database_user.sql b/worklenz-backend/database/5_database_user.sql new file mode 100644 index 00000000..ce195fa5 --- /dev/null +++ b/worklenz-backend/database/5_database_user.sql @@ -0,0 +1,31 @@ +REVOKE CREATE ON SCHEMA public FROM PUBLIC; +CREATE ROLE worklenz_client; + +GRANT CONNECT ON DATABASE worklenz_db TO worklenz_client; +GRANT INSERT, SELECT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO worklenz_client; + +GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO worklenz_client; + +REVOKE ALL PRIVILEGES ON task_priorities FROM worklenz_client; +GRANT SELECT ON task_priorities TO worklenz_client; + +REVOKE ALL PRIVILEGES ON project_access_levels FROM worklenz_client; +GRANT SELECT ON project_access_levels TO worklenz_client; + +REVOKE ALL PRIVILEGES ON timezones FROM worklenz_client; +GRANT SELECT ON timezones TO worklenz_client; + +REVOKE ALL PRIVILEGES ON worklenz_alerts FROM worklenz_client; +GRANT SELECT ON worklenz_alerts TO worklenz_client; + +REVOKE ALL PRIVILEGES ON sys_task_status_categories FROM worklenz_client; +GRANT SELECT ON sys_task_status_categories TO worklenz_client; + +REVOKE ALL PRIVILEGES ON sys_project_statuses FROM worklenz_client; +GRANT SELECT ON sys_project_statuses TO worklenz_client; + +REVOKE ALL PRIVILEGES ON sys_project_healths FROM worklenz_client; +GRANT SELECT ON sys_project_healths TO worklenz_client; + +CREATE USER worklenz_backend WITH PASSWORD 'n?&bb24=aWmnw+G@'; +GRANT worklenz_client TO worklenz_backend; diff --git a/worklenz-backend/database/5_functions.sql b/worklenz-backend/database/5_functions.sql deleted file mode 100644 index 3ee7e10a..00000000 --- a/worklenz-backend/database/5_functions.sql +++ /dev/null @@ -1,5791 +0,0 @@ -CREATE OR REPLACE FUNCTION accept_invitation(_email text, _team_member_id uuid, _user_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - IF _team_member_id IS NOT NULL - THEN - UPDATE team_members SET user_id = _user_id WHERE id = _team_member_id; - DELETE FROM email_invitations WHERE email = _email AND team_member_id = _team_member_id; - END IF; - - RETURN JSON_BUILD_OBJECT( - 'email', _email, - 'id', (SELECT id FROM teams WHERE id = (SELECT team_id FROM team_members WHERE id = _team_member_id)) - ); -END; -$$; - -CREATE OR REPLACE FUNCTION activate_team(_team_id uuid, _user_id uuid) RETURNS void - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - UPDATE users - SET active_team =_team_id - WHERE id = _user_id - AND EXISTS(SELECT id FROM team_members WHERE team_id = _team_id AND user_id = _user_id); - - DELETE - FROM email_invitations - WHERE team_id = _team_id - AND team_member_id = - (SELECT id FROM team_members WHERE user_id = _user_id AND team_members.team_id = _team_id); -END -$$; - -CREATE OR REPLACE FUNCTION add_or_remove_pt_task_label(_task_id uuid, _label_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; -BEGIN - IF EXISTS(SELECT task_id FROM cpt_task_labels WHERE task_id = _task_id AND label_id = _label_id) - THEN - DELETE FROM cpt_task_labels WHERE task_id = _task_id AND label_id = _label_id; - ELSE - INSERT INTO cpt_task_labels (task_id, label_id) VALUES (_task_id, _label_id); - END IF; - - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _result - FROM (SELECT cpt_task_labels.label_id AS id, - (SELECT name FROM team_labels WHERE id = cpt_task_labels.label_id) AS name, - (SELECT color_code FROM team_labels WHERE id = cpt_task_labels.label_id) - FROM cpt_task_labels - WHERE task_id = _task_id - ORDER BY name) rec; - - RETURN _result; -END -$$; - -CREATE OR REPLACE FUNCTION add_or_remove_task_label(_task_id uuid, _label_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; -BEGIN - IF EXISTS(SELECT task_id FROM task_labels WHERE task_id = _task_id AND label_id = _label_id) - THEN - DELETE FROM task_labels WHERE task_id = _task_id AND label_id = _label_id; - ELSE - INSERT INTO task_labels (task_id, label_id) VALUES (_task_id, _label_id); - END IF; - - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _result - FROM (SELECT task_labels.label_id AS id, - (SELECT name FROM team_labels WHERE id = task_labels.label_id) AS name, - (SELECT color_code FROM team_labels WHERE id = task_labels.label_id) - FROM task_labels - WHERE task_id = _task_id - ORDER BY name) rec; - - RETURN _result; -END -$$; - -CREATE OR REPLACE FUNCTION assign_or_create_label(_team_id uuid, _task_id uuid, _name text, _color_code text) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _label_id UUID; - _is_new BOOLEAN; -BEGIN - SELECT id FROM team_labels WHERE team_id = _team_id AND LOWER(name) = TRIM(LOWER(_name)) INTO _label_id; - - IF (is_null_or_empty(_label_id) IS TRUE) - THEN - INSERT INTO team_labels (name, team_id, color_code) - VALUES (TRIM(_name), _team_id, _color_code) - RETURNING id INTO _label_id; - _is_new = TRUE; - END IF; - - INSERT INTO task_labels (task_id, label_id) VALUES (_task_id, _label_id); - - RETURN JSON_BUILD_OBJECT('id', _label_id, 'is_new', _is_new); -END; -$$; - -CREATE OR REPLACE FUNCTION assign_or_create_pt_label(_team_id uuid, _task_id uuid, _name text, _color_code text) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _label_id UUID; - _is_new BOOLEAN; -BEGIN - SELECT id FROM team_labels WHERE team_id = _team_id AND LOWER(name) = TRIM(LOWER(_name)) INTO _label_id; - - IF (is_null_or_empty(_label_id) IS TRUE) - THEN - INSERT INTO team_labels (name, team_id, color_code) - VALUES (TRIM(_name), _team_id, _color_code) - RETURNING id INTO _label_id; - _is_new = TRUE; - END IF; - - INSERT INTO cpt_task_labels (task_id, label_id) VALUES (_task_id, _label_id); - - RETURN JSON_BUILD_OBJECT('id', _label_id, 'is_new', _is_new); -END; -$$; - -CREATE OR REPLACE FUNCTION bulk_archive_tasks(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _task JSON; - _output JSON; -BEGIN - FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) - LOOP - -- Archive the parent task - UPDATE tasks - SET archived = ((_body ->> 'type')::TEXT = 'archive') - WHERE id = (_task ->> 'id')::UUID - AND parent_task_id IS NULL; - -- Prevent archiving subtasks - - -- Archive its sub-tasks - UPDATE tasks - SET archived = ((_body ->> 'type')::TEXT = 'archive') - WHERE parent_task_id = (_task ->> 'id')::UUID; - END LOOP; - - RETURN _output; -END; -$$; - -CREATE OR REPLACE FUNCTION bulk_assign_or_create_label(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _task JSON; - _output JSON; -BEGIN - FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) - LOOP - PERFORM assign_or_create_label((_body ->> 'team_id')::UUID, (_task ->> 'id')::UUID, - (_body ->> 'text')::TEXT, (_body ->> 'color')::TEXT); - END LOOP; - RETURN _output; -END; -$$; - -CREATE OR REPLACE FUNCTION bulk_assign_to_me(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _task JSON; - _output JSON; - _project_member JSON; - _team_member_id UUID; - _project_member_id UUID; -BEGIN - SELECT id - FROM team_members - WHERE team_id = (_body ->> 'team_id')::UUID - AND user_id = (_body ->> 'user_id')::UUID - INTO _team_member_id; - - FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) - LOOP - - SELECT id - FROM project_members - WHERE project_id = (_body ->> 'project_id')::UUID - AND project_members.team_member_id = _team_member_id - INTO _project_member_id; - - IF is_null_or_empty(_project_member_id) - THEN - SELECT create_project_member(JSON_BUILD_OBJECT( - 'team_member_id', _team_member_id, - 'team_id', (_body ->> 'team_id')::UUID, - 'project_id', (_body ->> 'project_id')::UUID, - 'user_id', (_body ->> 'user_id')::UUID, - 'access_level', 'MEMBER'::TEXT - )) - INTO _project_member; - _project_member_id = (_project_member ->> 'id')::UUID; - END IF; - - IF NOT EXISTS(SELECT task_id - FROM tasks_assignees - WHERE task_id = (_task ->> 'id')::UUID - AND project_member_id = _project_member_id - AND team_member_id = _team_member_id) - THEN - INSERT INTO tasks_assignees (task_id, project_member_id, team_member_id, assigned_by) - VALUES ((_task ->> 'id')::UUID, _project_member_id, _team_member_id, (_body ->> 'user_id')::UUID); - END IF; - END LOOP; - - RETURN _output; -END; -$$; - -CREATE OR REPLACE FUNCTION bulk_delete_pt_tasks(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _task JSON; - _output JSON; -BEGIN - FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) - LOOP - DELETE FROM cpt_tasks WHERE id = (_task ->> 'id')::UUID; - END LOOP; - - RETURN _output; -END; -$$; - -CREATE OR REPLACE FUNCTION bulk_delete_tasks(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _task JSON; - _output JSON; -BEGIN - FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) - LOOP - DELETE FROM tasks WHERE id = (_task ->> 'id')::UUID; - END LOOP; - - RETURN _output; -END; -$$; - -CREATE OR REPLACE FUNCTION complete_account_setup(_user_id uuid, _team_id uuid, _body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _project_id UUID; - _default_status_id UUID; - _task_id UUID; - _task TEXT; - _members JSON; - _sort_order INT; - _team_member_id UUID; - _project_member_id UUID; -BEGIN - - -- Update team name - UPDATE teams SET name = TRIM((_body ->> 'team_name')::TEXT) WHERE id = _team_id AND user_id = _user_id; - - -- Create the project - INSERT INTO projects (name, team_id, owner_id, color_code, status_id, key) - VALUES ((_body ->> 'project_name')::TEXT, _team_id, _user_id, '#3b7ad4', - (SELECT id FROM sys_project_statuses WHERE is_default IS TRUE), (_body ->> 'key')::TEXT) - RETURNING id INTO _project_id; - - -- Insert task's statuses - INSERT INTO task_statuses (name, project_id, team_id, category_id) - VALUES ('To do', _project_id, _team_id, (SELECT id FROM sys_task_status_categories WHERE is_todo IS TRUE)) - RETURNING id INTO _default_status_id; - - INSERT INTO task_statuses (name, project_id, team_id, category_id) - VALUES ('Doing', _project_id, _team_id, (SELECT id FROM sys_task_status_categories WHERE is_doing IS TRUE)); - - INSERT INTO task_statuses (name, project_id, team_id, category_id) - VALUES ('Done', _project_id, _team_id, (SELECT id FROM sys_task_status_categories WHERE is_done IS TRUE)); - - SELECT id FROM team_members WHERE user_id = _user_id AND team_id = _team_id INTO _team_member_id; - - INSERT INTO project_members (team_member_id, project_access_level_id, project_id, role_id) - VALUES (_team_member_id, (SELECT id FROM project_access_levels WHERE key = 'PROJECT_MANAGER'), - _project_id, - (SELECT id FROM roles WHERE team_id = _team_id AND default_role IS TRUE)) - RETURNING id INTO _project_member_id; - - -- Insert tasks - _sort_order = 1; - FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) - LOOP - INSERT INTO tasks (name, priority_id, project_id, reporter_id, status_id, sort_order) - VALUES (TRIM('"' FROM _task)::TEXT, (SELECT id FROM task_priorities WHERE value = 1), _project_id, _user_id, - _default_status_id, _sort_order) - RETURNING id INTO _task_id; - _sort_order = _sort_order + 1; - - INSERT INTO tasks_assignees (task_id, project_member_id, team_member_id, assigned_by) - VALUES (_task_id, _project_member_id, _team_member_id, _user_id); - END LOOP; - - -- Insert team members if available - IF is_null_or_empty((_body ->> 'team_members')) IS FALSE - THEN - SELECT create_team_member(JSON_BUILD_OBJECT('team_id', _team_id, 'emails', (_body ->> 'team_members'))) - INTO _members; - END IF; - - -- insert default columns for task list - PERFORM insert_task_list_columns(_project_id); - - UPDATE users SET setup_completed = TRUE WHERE id = _user_id; - - -- Update organization name - UPDATE organizations SET organization_name = TRIM((_body ->> 'team_name')::TEXT) WHERE user_id = _user_id; - - --insert user data - INSERT INTO users_data (user_id, organization_name, contact_number, contact_number_secondary, trial_in_progress, - trial_expire_date, subscription_status) - VALUES (_user_id, TRIM((_body ->> 'team_name')::TEXT), NULL, NULL, TRUE, CURRENT_DATE + INTERVAL '14 days', - 'trialing') - ON CONFLICT (user_id) DO UPDATE SET organization_name = TRIM((_body ->> 'team_name')::TEXT); - - RETURN JSON_BUILD_OBJECT('id', _project_id, 'members', _members); -END; -$$; - -CREATE OR REPLACE FUNCTION create_bulk_task_assignees(_team_member_id uuid, _project_id uuid, _task_id uuid, _reporter_user_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _project_member JSON; - _project_member_id UUID; - _team_id UUID; - _user_id UUID; -BEGIN - SELECT id - FROM project_members - WHERE team_member_id = _team_member_id - AND project_id = _project_id - INTO _project_member_id; - - SELECT team_id FROM team_members WHERE id = _team_member_id INTO _team_id; - SELECT user_id FROM team_members WHERE id = _team_member_id INTO _user_id; - - IF is_null_or_empty(_project_member_id) - THEN - SELECT create_project_member(JSON_BUILD_OBJECT( - 'team_member_id', _team_member_id, - 'team_id', _team_id, - 'project_id', _project_id, - 'user_id', _reporter_user_id, - 'access_level', 'MEMBER'::TEXT - )) - INTO _project_member; - _project_member_id = (_project_member ->> 'id')::UUID; - END IF; - - IF NOT EXISTS (SELECT 1 FROM tasks_assignees WHERE task_id = _task_id AND project_member_id = _project_member_id) - THEN - INSERT INTO tasks_assignees (task_id, project_member_id, team_member_id, assigned_by) - VALUES (_task_id, _project_member_id, _team_member_id, _reporter_user_id); - - INSERT INTO task_activity_logs (task_id, team_id, attribute_type, user_id, log_type, old_value, new_value, project_id) - VALUES ( - _task_id, - (SELECT team_id FROM projects WHERE id = (SELECT project_id FROM tasks WHERE id = _task_id)), - 'assignee', - _reporter_user_id, - 'assign', - NULL, - _user_id, - (SELECT project_id FROM tasks WHERE id = _task_id) - ); - - END IF; - - RETURN JSON_BUILD_OBJECT( - 'task_id', _task_id, - 'project_member_id', _project_member_id, - 'team_member_id', _team_member_id, - 'team_id', _team_id, - 'user_id', _user_id - ); -END -$$; - -CREATE OR REPLACE FUNCTION create_home_task(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _task_id UUID; -BEGIN - - INSERT INTO tasks (name, end_date, priority_id, project_id, reporter_id, status_id, sort_order) - VALUES (TRIM((_body ->> 'name')::TEXT), - (_body ->> 'end_date')::TIMESTAMP, - (SELECT id FROM task_priorities WHERE value = 1), - (_body ->> 'project_id')::UUID, - (_body ->> 'reporter_id')::UUID, - - -- This should be came from client side later - (SELECT id - FROM task_statuses - WHERE project_id = (_body ->> 'project_id')::UUID - AND category_id IN (SELECT id FROM sys_task_status_categories WHERE is_todo IS TRUE) - LIMIT 1), - COALESCE((SELECT MAX(sort_order) + 1 FROM tasks WHERE project_id = (_body ->> 'project_id')::UUID), 0)) - RETURNING id INTO _task_id; - - -- RETURN(SELECT id FROM team_members WHERE user_id=(_body ->> 'reporter_id')::UUID AND team_id=(_body ->> 'team_id')::UUID); - - RETURN home_task_form_view_model((_body ->> 'reporter_id')::UUID, (_body ->> 'team_id')::UUID, _task_id, - (_body ->> 'project_id')::UUID); -END; -$$; - -CREATE OR REPLACE FUNCTION create_new_team(_name text, _user_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _owner_id UUID; - _team_id UUID; - _admin_role_id UUID; - _owner_role_id UUID; - _trimmed_name TEXT; - _trimmed_team_name TEXT; -BEGIN - - _trimmed_team_name = TRIM(_name); - -- get owner id - SELECT user_id INTO _owner_id FROM teams WHERE id = (SELECT active_team FROM users WHERE id = _user_id); - - -- insert team - INSERT INTO teams (name, user_id) - VALUES (_trimmed_team_name, _owner_id) - RETURNING id INTO _team_id; - - -- insert default roles - INSERT INTO roles (name, team_id, default_role) VALUES ('Member', _team_id, TRUE); - INSERT INTO roles (name, team_id, admin_role) VALUES ('Admin', _team_id, TRUE) RETURNING id INTO _admin_role_id; - INSERT INTO roles (name, team_id, owner) VALUES ('Owner', _team_id, TRUE) RETURNING id INTO _owner_role_id; - - -- insert team member - INSERT INTO team_members (user_id, team_id, role_id) - VALUES (_owner_id, _team_id, _owner_role_id); - - IF (_user_id <> _owner_id) - THEN - INSERT INTO team_members (user_id, team_id, role_id) - VALUES (_user_id, _team_id, _admin_role_id); - END IF; - - RETURN JSON_BUILD_OBJECT( - 'id', _user_id, - 'name', _trimmed_name, - 'team_id', _team_id - ); -END; -$$; - -CREATE OR REPLACE FUNCTION create_notification(_user_id uuid, _team_id uuid, _task_id uuid, _project_id uuid, _message text) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - IF (_user_id IS NOT NULL AND _team_id IS NOT NULL AND is_null_or_empty(_message) IS FALSE) - THEN - INSERT INTO user_notifications (message, user_id, team_id, task_id, project_id) - VALUES (TRIM(_message), _user_id, _team_id, _task_id, _project_id); - END IF; - - RETURN JSON_BUILD_OBJECT( - 'project', (SELECT name FROM projects WHERE id = _project_id), - 'project_color', (SELECT color_code FROM projects WHERE id = _project_id), - 'team', (SELECT name FROM teams WHERE id = _team_id) - ); -END -$$; - -CREATE OR REPLACE FUNCTION create_project(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _user_id UUID; - _team_id UUID; - _client_id UUID; - _project_id UUID; - _client_name TEXT; - _project_name TEXT; -BEGIN - -- need a test, can be throw errors - _client_name = TRIM((_body ->> 'client_name')::TEXT); - _project_name = TRIM((_body ->> 'name')::TEXT); - - -- add inside the controller - _user_id = (_body ->> 'user_id')::UUID; - _team_id = (_body ->> 'team_id')::UUID; - - -- cache exists client if exists - SELECT id FROM clients WHERE LOWER(name) = LOWER(_client_name) AND team_id = _team_id INTO _client_id; - - -- check whether the project name is already in - IF EXISTS(SELECT name - FROM projects - WHERE LOWER(name) = LOWER(_project_name) - AND team_id = _team_id) - THEN - RAISE 'PROJECT_EXISTS_ERROR:%', _project_name; - END IF; - - -- insert client if not exists - IF is_null_or_empty(_client_id) IS TRUE AND is_null_or_empty(_client_name) IS FALSE - THEN - INSERT INTO clients (name, team_id) VALUES (_client_name, _team_id) RETURNING id INTO _client_id; - END IF; - - -- insert project - INSERT INTO projects (name, key, notes, color_code, team_id, client_id, owner_id, status_id, health_id, start_date, - end_date, - folder_id, category_id, estimated_working_days, estimated_man_days, hours_per_day) - VALUES (_project_name, (_body ->> 'key')::TEXT, (_body ->> 'notes')::TEXT, (_body ->> 'color_code')::TEXT, _team_id, - _client_id, - _user_id, (_body ->> 'status_id')::UUID, (_body ->> 'health_id')::UUID, - (_body ->> 'start_date')::TIMESTAMPTZ, - (_body ->> 'end_date')::TIMESTAMPTZ, (_body ->> 'folder_id')::UUID, (_body ->> 'category_id')::UUID, - (_body ->> 'working_days')::INTEGER, (_body ->> 'man_days')::INTEGER, (_body ->> 'hours_per_day')::INTEGER) - RETURNING id INTO _project_id; - - -- log record - INSERT INTO project_logs (team_id, project_id, description) - VALUES (_team_id, _project_id, - REPLACE((_body ->> 'project_created_log')::TEXT, '@user', - (SELECT name FROM users WHERE id = _user_id))); - - -- insert statuses - INSERT INTO task_statuses (name, project_id, team_id, category_id, sort_order) - VALUES ('To Do', _project_id, _team_id, (SELECT id FROM sys_task_status_categories WHERE is_todo IS TRUE), 0); - INSERT INTO task_statuses (name, project_id, team_id, category_id, sort_order) - VALUES ('Doing', _project_id, _team_id, (SELECT id FROM sys_task_status_categories WHERE is_doing IS TRUE), 1); - INSERT INTO task_statuses (name, project_id, team_id, category_id, sort_order) - VALUES ('Done', _project_id, _team_id, (SELECT id FROM sys_task_status_categories WHERE is_done IS TRUE), 2); - - -- insert default columns for task list - PERFORM insert_task_list_columns(_project_id); - - -- insert user as default project manager(project owner) - IF NOT (_body ->> 'project_manager_id' IS NULL) - THEN - PERFORM create_project_member(JSON_BUILD_OBJECT( - 'team_member_id', (_body ->> 'project_manager_id')::UUID, - 'team_id', _team_id, - 'project_id', _project_id, - 'user_id', _user_id, - 'access_level', 'PROJECT_MANAGER'::TEXT - )); - END IF; - - RETURN JSON_BUILD_OBJECT( - 'id', _project_id, - 'name', (_body ->> 'name')::TEXT - ); -END; -$$; - -CREATE OR REPLACE FUNCTION create_project_comment(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _project_id UUID; - _created_by UUID; - _comment_id UUID; - _team_id UUID; - _user_name TEXT; - _project_name TEXT; - _content TEXT; - _mention_index INT := 0; - _mention JSON; -BEGIN - _project_id = (_body ->> 'project_id'); - _created_by = (_body ->> 'created_by'); - _content = (_body ->> 'content'); - _team_id = (_body ->> 'team_id'); - - SELECT name FROM users WHERE id = _created_by LIMIT 1 INTO _user_name; - SELECT name FROM projects WHERE id = _project_id INTO _project_name; - - INSERT INTO project_comments (content, created_by, project_id) - VALUES (_content, _created_by, _project_id) - RETURNING id INTO _comment_id; - - FOR _mention IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'mentions')::JSON) - LOOP - - INSERT INTO project_comment_mentions (comment_id, mentioned_index, mentioned_by, informed_by) - VALUES (_comment_id, _mention_index, _created_by, (_mention ->> 'id')::UUID); - - PERFORM create_notification( - (SELECT id FROM users WHERE id = (_mention ->> 'id')::UUID), - (_team_id)::UUID, - null, - (_project_id)::UUID, - CONCAT('', _user_name, ' has mentioned you in a comment on ', _project_name, '') - ); - _mention_index := _mention_index + 1; - - END LOOP; - - RETURN JSON_BUILD_OBJECT( - 'id', (_comment_id)::UUID, - 'content', (_content)::TEXT, - 'project_name', (_project_name)::TEXT, - 'team_name', (SELECT name FROM teams WHERE id = (_team_id)::UUID) - ); -END -$$; - -CREATE OR REPLACE FUNCTION create_project_member(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _id UUID; - _team_member_id UUID; - _team_id UUID; - _project_id UUID; - _user_id UUID; - _member_user_id UUID; - _notification TEXT; - _access_level TEXT; -BEGIN - _team_member_id = (_body ->> 'team_member_id')::UUID; - _team_id = (_body ->> 'team_id')::UUID; - _project_id = (_body ->> 'project_id')::UUID; - _user_id = (_body ->> 'user_id')::UUID; - _access_level = (_body ->> 'access_level')::TEXT; - - SELECT user_id FROM team_members WHERE id = _team_member_id INTO _member_user_id; - - INSERT INTO project_members (team_member_id, project_access_level_id, project_id, role_id) - VALUES (_team_member_id, (SELECT id FROM project_access_levels WHERE key = _access_level)::UUID, - _project_id, - (SELECT id FROM roles WHERE team_id = _team_id AND default_role IS TRUE)) - RETURNING id INTO _id; - - IF (_member_user_id != _user_id) - THEN - _notification = CONCAT('You have been added to the ', - (SELECT name FROM projects WHERE id = _project_id), - ' by ', - (SELECT name FROM users WHERE id = _user_id), ''); - PERFORM create_notification( - (SELECT user_id FROM team_members WHERE id = _team_member_id), - _team_id, - NULL, - _project_id, - _notification - ); - END IF; - - RETURN JSON_BUILD_OBJECT( - 'id', _id, - 'notification', _notification, - 'socket_id', (SELECT socket_id FROM users WHERE id = _member_user_id), - 'project', (SELECT name FROM projects WHERE id = _project_id), - 'project_id', _project_id, - 'project_color', (SELECT color_code FROM projects WHERE id = _project_id), - 'team', (SELECT name FROM teams WHERE id = _team_id), - 'member_user_id', _member_user_id - ); -END -$$; - -CREATE OR REPLACE FUNCTION create_project_template(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _template_id UUID; -BEGIN - -- check whether the project name is already in - IF EXISTS(SELECT name - FROM custom_project_templates - WHERE LOWER(name) = LOWER((_body ->> 'name')::TEXT) - AND team_id = (_body ->> 'team_id')::uuid) - THEN - RAISE 'TEMPLATE_EXISTS_ERROR:%', (_body ->> 'name')::TEXT; - END IF; - - -- insert client if not exists - INSERT INTO custom_project_templates(name, phase_label, color_code, notes, team_id) - VALUES ((_body ->> 'name')::TEXT, (_body ->> 'phase_label')::TEXT, (_body ->> 'color_code')::TEXT, - (_body ->> 'notes')::TEXT, - (_body ->> 'team_id')::uuid) - RETURNING id INTO _template_id; - - RETURN JSON_BUILD_OBJECT('id', _template_id); -END; -$$; - -CREATE OR REPLACE FUNCTION create_pt_task_status(_body json, _team_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _status_id UUID; - _group_status JSON; -BEGIN - INSERT INTO cpt_task_statuses (name, template_id, team_id, category_id, sort_order) - VALUES (TRIM((_body ->> 'name')::TEXT), - (_body ->> 'template_id')::UUID, - _team_id, - (_body ->> 'category_id')::UUID, - COALESCE((SELECT MAX(sort_order) + 1 - FROM cpt_task_statuses - WHERE template_id = (_body ->> 'template_id') ::UUID), - 0)) RETURNING id INTO _status_id; - SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))) - FROM (SELECT id, - name, - template_id, - team_id, - category_id, - sort_order, - (SELECT color_code - FROM sys_task_status_categories - WHERE id = - (SELECT category_id FROM cpt_task_statuses WHERE id = _status_id)) AS color_code - FROM cpt_task_statuses - WHERE id = _status_id) rec INTO _group_status; - RETURN _group_status; -END; -$$; - -CREATE OR REPLACE FUNCTION create_quick_pt_task(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _task_id UUID; - _parent_task UUID; - _status_id UUID; - _priority_id UUID; -BEGIN - - _parent_task = (_body ->> 'parent_task_id')::UUID; - _status_id = COALESCE( - (_body ->> 'status_id')::UUID, - (SELECT id - FROM cpt_task_statuses - WHERE template_id = (_body ->> 'template_id')::UUID - AND category_id IN (SELECT id FROM sys_task_status_categories WHERE is_todo IS TRUE) - LIMIT 1) - ); - _priority_id = COALESCE((_body ->> 'priority_id')::UUID, (SELECT id FROM task_priorities WHERE value = 1)); - - INSERT INTO cpt_tasks(name, priority_id, template_id, status_id, parent_task_id, sort_order, task_no) - VALUES (TRIM((_body ->> 'name')::TEXT), - _priority_id, - (_body ->> 'template_id')::UUID, - - -- This should be came from client side later - _status_id, _parent_task, - COALESCE((SELECT MAX(sort_order) + 1 FROM cpt_tasks WHERE template_id = (_body ->> 'template_id')::UUID), - 0), ((SELECT COUNT(*) FROM cpt_tasks WHERE template_id = (_body ->> 'template_id')::UUID) + 1)) - RETURNING id INTO _task_id; - - PERFORM handle_on_pt_task_phase_change(_task_id, (_body ->> 'phase_id')::UUID); - - RETURN get_single_pt_task(_task_id); -END; -$$; - -CREATE OR REPLACE FUNCTION create_quick_task(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _task_id UUID; - _parent_task UUID; - _status_id UUID; - _priority_id UUID; - _start_date TIMESTAMP; - _end_date TIMESTAMP; -BEGIN - - _parent_task = (_body ->> 'parent_task_id')::UUID; - _status_id = COALESCE( - (_body ->> 'status_id')::UUID, - (SELECT id - FROM task_statuses - WHERE project_id = (_body ->> 'project_id')::UUID - AND category_id IN (SELECT id FROM sys_task_status_categories WHERE is_todo IS TRUE) - LIMIT 1) - ); - _priority_id = COALESCE((_body ->> 'priority_id')::UUID, (SELECT id FROM task_priorities WHERE value = 1)); - _start_date = (_body ->> 'start_date')::TIMESTAMP; - _end_date = (_body ->> 'end_date')::TIMESTAMP; - - INSERT INTO tasks (name, priority_id, project_id, reporter_id, status_id, parent_task_id, sort_order, start_date, end_date) - VALUES (TRIM((_body ->> 'name')::TEXT), - _priority_id, - (_body ->> 'project_id')::UUID, - (_body ->> 'reporter_id')::UUID, - - -- This should be came from client side later - _status_id, _parent_task, - COALESCE((SELECT MAX(sort_order) + 1 FROM tasks WHERE project_id = (_body ->> 'project_id')::UUID), 0), - (_body ->> 'start_date')::TIMESTAMP, - (_body ->> 'end_date')::TIMESTAMP) - RETURNING id INTO _task_id; - - PERFORM handle_on_task_phase_change(_task_id, (_body ->> 'phase_id')::UUID); - - RETURN get_single_task(_task_id); -END; -$$; - -CREATE OR REPLACE FUNCTION create_task(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _assignee TEXT; - _attachment_id TEXT; - _assignee_id UUID; - _task_id UUID; - _label JSON; -BEGIN - INSERT INTO tasks (name, done, priority_id, project_id, reporter_id, start_date, end_date, total_minutes, - description, parent_task_id, status_id, sort_order) - VALUES (TRIM((_body ->> 'name')::TEXT), (FALSE), - COALESCE((_body ->> 'priority_id')::UUID, (SELECT id FROM task_priorities WHERE value = 1)), - (_body ->> 'project_id')::UUID, - (_body ->> 'reporter_id')::UUID, - (_body ->> 'start')::TIMESTAMPTZ, - (_body ->> 'end')::TIMESTAMPTZ, - (_body ->> 'total_minutes')::NUMERIC, - (_body ->> 'description')::TEXT, - (_body ->> 'parent_task_id')::UUID, - (_body ->> 'status_id')::UUID, - COALESCE((SELECT MAX(sort_order) + 1 FROM tasks WHERE project_id = (_body ->> 'project_id')::UUID), 0)) - RETURNING id INTO _task_id; - - -- insert task assignees - FOR _assignee IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'assignees')::JSON) - LOOP - _assignee_id = TRIM('"' FROM _assignee)::UUID; - PERFORM create_task_assignee(_assignee_id, (_body ->> 'project_id')::UUID, _task_id, - (_body ->> 'reporter_id')::UUID); - END LOOP; - - FOR _attachment_id IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'attachments')::JSON) - LOOP - UPDATE task_attachments SET task_id = _task_id WHERE id = TRIM('"' FROM _attachment_id)::UUID; - END LOOP; - - FOR _label IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'labels')::JSON) - LOOP - PERFORM assign_or_create_label((_body ->> 'team_id')::UUID, _task_id, (_label ->> 'name')::TEXT, - (_label ->> 'color')::TEXT); - END LOOP; - - RETURN get_task_form_view_model((_body ->> 'reporter_id')::UUID, (_body ->> 'team_id')::UUID, _task_id, - (_body ->> 'project_id')::UUID); -END; -$$; - -CREATE OR REPLACE FUNCTION create_task_assignee(_team_member_id uuid, _project_id uuid, _task_id uuid, _reporter_user_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _project_member JSON; - _project_member_id UUID; - _team_id UUID; - _user_id UUID; -BEGIN - SELECT id - FROM project_members - WHERE team_member_id = _team_member_id - AND project_id = _project_id - INTO _project_member_id; - - SELECT team_id FROM team_members WHERE id = _team_member_id INTO _team_id; - SELECT user_id FROM team_members WHERE id = _team_member_id INTO _user_id; - - IF is_null_or_empty(_project_member_id) - THEN - SELECT create_project_member(JSON_BUILD_OBJECT( - 'team_member_id', _team_member_id, - 'team_id', _team_id, - 'project_id', _project_id, - 'user_id', _reporter_user_id, - 'access_level', 'MEMBER'::TEXT - )) - INTO _project_member; - _project_member_id = (_project_member ->> 'id')::UUID; - END IF; - - INSERT INTO tasks_assignees (task_id, project_member_id, team_member_id, assigned_by) - VALUES (_task_id, _project_member_id, _team_member_id, _reporter_user_id); - - RETURN JSON_BUILD_OBJECT( - 'task_id', _task_id, - 'project_member_id', _project_member_id, - 'team_member_id', _team_member_id, - 'team_id', _team_id, - 'user_id', _user_id - ); -END -$$; - -CREATE OR REPLACE FUNCTION create_task_comment(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _task_id UUID; - _user_id UUID; - _comment_id UUID; - _team_member_id UUID; - _mentioned_member_id TEXT; - _user_name TEXT; - _task_name TEXT; - _mention_index INT := 0; - _mention JSON; -BEGIN - - _task_id = (_body ->> 'task_id')::UUID; - _user_id = (_body ->> 'user_id')::UUID; - - SELECT name FROM users WHERE id = _user_id LIMIT 1 INTO _user_name; - SELECT name FROM tasks WHERE id = _task_id INTO _task_name; - - SELECT id - FROM team_members - WHERE user_id = _user_id - AND team_id = (_body ->> 'team_id')::UUID - INTO _team_member_id; - - INSERT INTO task_comments (user_id, team_member_id, task_id) - VALUES (_user_id, _team_member_id, _task_id) - RETURNING id INTO _comment_id; - - INSERT INTO task_comment_contents (index, comment_id, text_content) - VALUES (0, _comment_id, (_body ->> 'content')::TEXT); - - -- notify mentions - FOR _mention IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'mentions')::JSON) - LOOP - INSERT INTO task_comment_mentions (comment_id, mentioned_index, mentioned_by, informed_by) - VALUES (_comment_id, _mention_index, _user_id, (_mention ->> 'team_member_id')::UUID); - PERFORM create_notification( - (SELECT user_id FROM team_members WHERE id = TRIM(BOTH '"' FROM (_mention ->> 'team_member_id'))::UUID), - (_body ->> 'team_id')::UUID, - _task_id, - (SELECT project_id FROM tasks WHERE id = _task_id), - CONCAT('', _user_name, ' has mentioned you in a comment on ', _task_name, '') - ); - _mention_index := _mention_index + 1; - END LOOP; - - RETURN JSON_BUILD_OBJECT( - 'id', _comment_id, - 'content', (_body ->> 'content')::TEXT, - 'task_name', _task_name, - 'project_id', (SELECT project_id FROM tasks WHERE id = _task_id), - 'project_name', (SELECT name FROM projects WHERE id = (SELECT project_id FROM tasks WHERE id = _task_id)), - 'team_name', (SELECT name FROM teams WHERE id = (_body ->> 'team_id')::UUID) - ); -END -$$; - -CREATE OR REPLACE FUNCTION create_task_status(_body json, _team_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _status_id UUID; - _group_status JSON; -BEGIN - - INSERT INTO task_statuses (name, project_id, team_id, category_id, sort_order) - VALUES (TRIM((_body ->> 'name')::TEXT), - (_body ->> 'project_id')::UUID, - _team_id, - (_body ->> 'category_id')::UUID, - COALESCE((SELECT MAX(sort_order) + 1 FROM task_statuses WHERE project_id = (_body ->> 'project_id')::UUID), - 0)) - RETURNING id INTO _status_id; - - SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))) - FROM (SELECT id, - name, - project_id, - team_id, - category_id, - sort_order, - (SELECT color_code - FROM sys_task_status_categories - WHERE id = - (SELECT category_id FROM task_statuses WHERE id = _status_id)) AS color_code - FROM task_statuses - WHERE id = _status_id) rec - INTO _group_status; - - RETURN _group_status; - -END; -$$; - -CREATE OR REPLACE FUNCTION create_task_template(_name text, _team_id uuid, _tasks json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _template_id UUID; - _task JSON; -BEGIN - INSERT INTO task_templates (name, team_id) VALUES (_name, _team_id) RETURNING id INTO _template_id; - - -- insert tasks for task templates - FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS(_tasks) - LOOP - INSERT INTO task_templates_tasks (template_id, name, total_minutes) VALUES (_template_id, (_task ->> 'name')::TEXT, (SELECT total_minutes FROM tasks WHERE id = (_task ->> 'id')::UUID)::NUMERIC); - END LOOP; - - RETURN JSON_BUILD_OBJECT( - 'id', _template_id, - 'template_name', _name - ); -END -$$; - -CREATE OR REPLACE FUNCTION create_team_member(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _team_id UUID; - _user_id UUID; - _job_title_id UUID; - _team_member_id UUID; - _role_id UUID; - _email TEXT; - _output JSON; -BEGIN - _team_id = (_body ->> 'team_id')::UUID; - - IF ((_body ->> 'is_admin')::BOOLEAN IS TRUE) - THEN - SELECT id FROM roles WHERE team_id = _team_id AND admin_role IS TRUE INTO _role_id; - ELSE - SELECT id FROM roles WHERE team_id = _team_id AND default_role IS TRUE INTO _role_id; - END IF; - - IF is_null_or_empty((_body ->> 'job_title')) IS FALSE - THEN - SELECT insert_job_title((_body ->> 'job_title')::TEXT, _team_id) INTO _job_title_id; - ELSE - _job_title_id = NULL; - END IF; - - CREATE TEMPORARY TABLE temp_new_team_members ( - name TEXT, - email TEXT, - is_new BOOLEAN, - team_member_id UUID, - team_member_user_id UUID - ); - - FOR _email IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'emails')::JSON) - LOOP - - _email = LOWER(TRIM('"' FROM _email)::TEXT); - - SELECT id FROM users WHERE email = _email INTO _user_id; - - INSERT INTO team_members (job_title_id, user_id, team_id, role_id) - VALUES (_job_title_id, _user_id, _team_id, _role_id) - RETURNING id INTO _team_member_id; - - IF EXISTS(SELECT id - FROM email_invitations - WHERE email = _email - AND team_id = _team_id) - THEN - -- DELETE --- FROM team_members --- WHERE id = (SELECT team_member_id --- FROM email_invitations --- WHERE email = _email --- AND team_id = _team_id); --- DELETE FROM email_invitations WHERE team_id = _team_id AND email = _email; - - DELETE FROM email_invitations WHERE email = _email AND team_id = _team_id; - --- RAISE 'ERROR_EMAIL_INVITATION_EXISTS:%', _email; - END IF; - - INSERT INTO email_invitations(team_id, team_member_id, email, name) - VALUES (_team_id, _team_member_id, _email, SPLIT_PART(_email, '@', 1)); - - INSERT INTO temp_new_team_members (is_new, team_member_id, team_member_user_id, name, email) - VALUES ((is_null_or_empty(_user_id)), _team_member_id, _user_id, - (SELECT name FROM users WHERE id = _user_id), _email); - END LOOP; - - SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))) - FROM (SELECT * FROM temp_new_team_members) rec - INTO _output; - - DROP TABLE temp_new_team_members; - - RETURN _output; -END; -$$; - -CREATE OR REPLACE FUNCTION delete_user(_id uuid) RETURNS void - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - SET SESSION_REPLICATION_ROLE = replica; - - UPDATE users SET active_team = NULL WHERE id = _id; - DELETE FROM notification_settings WHERE user_id = _id; - DELETE FROM teams WHERE user_id = _id; - DELETE FROM roles WHERE team_id IN (SELECT id FROM teams WHERE user_id = _id); - DELETE - FROM tasks_assignees - WHERE project_member_id IN - (SELECT id FROM project_members WHERE team_member_id IN (SELECT id FROM team_members WHERE user_id = _id)); - DELETE FROM project_members WHERE team_member_id IN (SELECT id FROM team_members WHERE user_id = _id); - DELETE FROM team_members WHERE user_id = _id; - DELETE FROM job_titles WHERE team_id = (SELECT id FROM teams WHERE user_id = _id); - DELETE - FROM tasks - WHERE project_id IN (SELECT id FROM projects WHERE team_id = (SELECT id FROM teams WHERE user_id = _id)); - DELETE FROM projects WHERE team_id = (SELECT id FROM teams WHERE user_id = _id); - DELETE FROM clients WHERE team_id = (SELECT id FROM teams WHERE user_id = _id); - DELETE FROM teams WHERE user_id = _id; - DELETE FROM personal_todo_list WHERE user_id = _id; - DELETE FROM user_notifications WHERE user_id = _id; - UPDATE users SET active_team = NULL WHERE id = _id; - DELETE FROM users WHERE id = _id; - - SET SESSION_REPLICATION_ROLE = default; -END; -$$; - -CREATE OR REPLACE FUNCTION deserialize_user(_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; - _team_id UUID; -BEGIN - - SELECT active_team FROM users WHERE id = _id INTO _team_id; - IF NOT EXISTS(SELECT 1 FROM notification_settings WHERE team_id = _team_id AND user_id = _id) - THEN - INSERT INTO notification_settings (popup_notifications_enabled, show_unread_items_count, user_id, team_id) - VALUES (TRUE, TRUE, _id, _team_id); - END IF; - - SELECT ROW_TO_JSON(rec) - INTO _result - FROM (SELECT users.id, - users.name, - users.email, - users.timezone_id AS timezone, - (SELECT name FROM timezones WHERE id = users.timezone_id) AS timezone_name, - users.avatar_url, - users.user_no, - users.socket_id, - users.created_at AS joined_date, - users.updated_at AS last_updated, - - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM (SELECT description, type FROM worklenz_alerts WHERE active is TRUE) rec) AS alerts, - - (SELECT email_notifications_enabled - FROM notification_settings - WHERE user_id = users.id - AND team_id = t.id) AS email_notifications_enabled, - (CASE - WHEN is_owner(users.id, users.active_team) THEN users.setup_completed - ELSE TRUE END) AS setup_completed, - users.setup_completed AS my_setup_completed, - (is_null_or_empty(users.google_id) IS FALSE) AS is_google, - t.name AS team_name, - t.id AS team_id, - (SELECT id - FROM team_members - WHERE team_members.user_id = _id - AND team_id = users.active_team - AND active IS TRUE) AS team_member_id, - is_owner(users.id, users.active_team) AS owner, - is_admin(users.id, users.active_team) AS is_admin, - t.user_id AS owner_id, - ud.subscription_status - FROM users - INNER JOIN teams t - ON t.id = COALESCE(users.active_team, - (SELECT id FROM teams WHERE teams.user_id = users.id LIMIT 1)) - LEFT JOIN organizations ud ON ud.user_id = t.user_id - WHERE users.id = _id) rec; - - RETURN _result; -END -$$; - -CREATE OR REPLACE FUNCTION get_activity_logs_by_task(_task_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; -BEGIN - SELECT ROW_TO_JSON(rec) - INTO _result - FROM (SELECT (SELECT tasks.created_at FROM tasks WHERE tasks.id = _task_id), - (SELECT name - FROM users - WHERE id = (SELECT reporter_id FROM tasks WHERE id = _task_id)), - (SELECT avatar_url - FROM users - WHERE id = (SELECT reporter_id FROM tasks WHERE id = _task_id)), - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec2))), '[]'::JSON) - FROM (SELECT task_id, - created_at, - attribute_type, - log_type, - - -- new case, - (CASE - WHEN (attribute_type = 'status') - THEN (SELECT name FROM task_statuses WHERE id = old_value::UUID) - WHEN (attribute_type = 'priority') - THEN (SELECT name FROM task_priorities WHERE id = old_value::UUID) - WHEN (attribute_type = 'phase' AND old_value <> 'Unmapped') - THEN (SELECT name FROM project_phases WHERE id = old_value::UUID) - ELSE (old_value) END) AS previous, - - -- new case - (CASE - WHEN (attribute_type = 'assignee') - THEN (SELECT name FROM users WHERE id = new_value::UUID) - WHEN (attribute_type = 'label') - THEN (SELECT name FROM team_labels WHERE id = new_value::UUID) - WHEN (attribute_type = 'status') - THEN (SELECT name FROM task_statuses WHERE id = new_value::UUID) - WHEN (attribute_type = 'priority') - THEN (SELECT name FROM task_priorities WHERE id = new_value::UUID) - WHEN (attribute_type = 'phase' AND new_value <> 'Unmapped') - THEN (SELECT name FROM project_phases WHERE id = new_value::UUID) - ELSE (new_value) END) AS current, - - -- new case - (CASE - WHEN (attribute_type = 'assignee') - THEN (SELECT ROW_TO_JSON(rec) - FROM (SELECT (CASE - WHEN (new_value IS NOT NULL) - THEN (SELECT name FROM users WHERE users.id = new_value::UUID) - ELSE (next_string) END) AS name, - (SELECT avatar_url FROM users WHERE users.id = new_value::UUID)) rec) - ELSE (NULL) END) AS assigned_user, - - -- new case - (CASE - WHEN (attribute_type = 'label') - THEN (SELECT ROW_TO_JSON(rec) - FROM (SELECT (SELECT name FROM team_labels WHERE id = new_value::UUID), - (SELECT color_code FROM team_labels WHERE id = new_value::UUID)) rec) - ELSE (NULL) END) AS label_data, - - -- new case - (CASE - WHEN (attribute_type = 'status') - THEN (SELECT ROW_TO_JSON(rec) - FROM (SELECT (SELECT name FROM task_statuses WHERE id = old_value::UUID), - (SELECT color_code - FROM sys_task_status_categories - WHERE id = (SELECT category_id FROM task_statuses WHERE id = old_value::UUID))) rec) - ELSE (NULL) END) AS previous_status, - - -- new case - (CASE - WHEN (attribute_type = 'status') - THEN (SELECT ROW_TO_JSON(rec) - FROM (SELECT (SELECT name FROM task_statuses WHERE id = new_value::UUID), - (SELECT color_code - FROM sys_task_status_categories - WHERE id = (SELECT category_id FROM task_statuses WHERE id = new_value::UUID))) rec) - ELSE (NULL) END) AS next_status, - - -- new case - (CASE - WHEN (attribute_type = 'priority') - THEN (SELECT ROW_TO_JSON(rec) - FROM (SELECT (SELECT name FROM task_priorities WHERE id = old_value::UUID), - (SELECT color_code FROM task_priorities WHERE id = old_value::UUID)) rec) - ELSE (NULL) END) AS previous_priority, - - -- new case - (CASE - WHEN (attribute_type = 'priority') - THEN (SELECT ROW_TO_JSON(rec) - FROM (SELECT (SELECT name FROM task_priorities WHERE id = new_value::UUID), - (SELECT color_code FROM task_priorities WHERE id = new_value::UUID)) rec) - ELSE (NULL) END) AS next_priority, - - -- new case - (CASE - WHEN (attribute_type = 'phase' AND old_value <> 'Unmapped') - THEN (SELECT ROW_TO_JSON(rec) - FROM (SELECT (SELECT name FROM project_phases WHERE id = old_value::UUID), - (SELECT color_code FROM project_phases WHERE id = old_value::UUID)) rec) - ELSE (NULL) END) AS previous_phase, - - -- new case - (CASE - WHEN (attribute_type = 'phase' AND new_value <> 'Unmapped') - THEN (SELECT ROW_TO_JSON(rec) - FROM (SELECT (SELECT name FROM project_phases WHERE id = new_value::UUID), - (SELECT color_code FROM project_phases WHERE id = new_value::UUID)) rec) - ELSE (NULL) END) AS next_phase, - - -- new case - (SELECT ROW_TO_JSON(rec) - FROM (SELECT (SELECT name FROM users WHERE users.id = tal.user_id), - (SELECT avatar_url FROM users WHERE users.id = tal.user_id)) rec) AS done_by - - - FROM task_activity_logs tal - WHERE task_id = _task_id - ORDER BY created_at DESC) rec2) AS logs) rec; - RETURN _result; -END; -$$; - -CREATE OR REPLACE FUNCTION get_daily_digest() RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; -BEGIN - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _result - FROM ( - -- - SELECT name, - email, - (SELECT get_daily_digest_recently_assigned(u.id)) AS recently_assigned, - (SELECT get_daily_digest_overdue(u.id)) AS overdue, - (SELECT get_daily_digest_recently_completed(u.id)) AS recently_completed - FROM users u - -- - ) rec; - RETURN _result; -END; -$$; - -CREATE OR REPLACE FUNCTION get_daily_digest_overdue(_user_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; -BEGIN - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _result - FROM ( - -- - SELECT id, - name, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) AS projects - FROM ( - -- - SELECT id, - name, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) AS tasks - FROM ( - -- - SELECT t.id, - t.name, - (SELECT STRING_AGG(DISTINCT - (SELECT name - FROM team_member_info_view - WHERE team_member_id = tasks_assignees.team_member_id), - ', ') - FROM tasks_assignees - WHERE task_id = t.id) AS members - FROM tasks_assignees - INNER JOIN tasks t ON tasks_assignees.task_id = t.id - INNER JOIN team_members tm ON tasks_assignees.team_member_id = tm.id - WHERE tm.user_id = _user_id - AND t.project_id = projects.id - AND t.end_date IS NOT NULL - AND t.end_date < CURRENT_DATE - AND EXISTS(SELECT id - FROM task_statuses - WHERE id = t.status_id - AND category_id IN - (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) - LIMIT 10 - -- - ) r) - FROM projects - WHERE projects.team_id IN - (SELECT team_id - FROM team_members - WHERE user_id = _user_id - AND team_members.team_id = teams.id) - -- - ) r) - FROM teams - WHERE (SELECT daily_digest_enabled - FROM notification_settings - WHERE team_id = teams.id - AND user_id = _user_id) IS TRUE - AND EXISTS(SELECT 1 - FROM team_members - WHERE team_id = teams.id - AND team_members.user_id = _user_id) - -- - ) rec; - - RETURN _result; -END -$$; - -CREATE OR REPLACE FUNCTION get_daily_digest_recently_assigned(_user_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; -BEGIN - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _result - FROM ( - -- - - -- - SELECT id, - name, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) AS projects - FROM ( - -- - SELECT id, - name, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) AS tasks - FROM ( - -- - SELECT t.id, - t.name, - (SELECT STRING_AGG(DISTINCT - (SELECT name - FROM team_member_info_view - WHERE team_member_id = tasks_assignees.team_member_id), - ', ') - FROM tasks_assignees - WHERE task_id = t.id) AS members - FROM tasks_assignees - INNER JOIN tasks t ON tasks_assignees.task_id = t.id - INNER JOIN team_members tm ON tasks_assignees.team_member_id = tm.id - WHERE tm.user_id = _user_id - AND t.project_id = projects.id - AND TO_CHAR(tasks_assignees.created_at, 'yyyy-mm-dd') = - TO_CHAR(CURRENT_DATE, 'yyyy-mm-dd') - -- - ) r) - FROM projects - WHERE projects.team_id IN - (SELECT team_id - FROM team_members - WHERE user_id = _user_id - AND team_members.team_id = teams.id) - -- - ) r) - FROM teams - WHERE (SELECT daily_digest_enabled - FROM notification_settings - WHERE team_id = teams.id - AND user_id = _user_id) IS TRUE - AND EXISTS(SELECT 1 - FROM team_members - WHERE team_id = teams.id - AND team_members.user_id = _user_id) - -- - ) rec; - - RETURN _result; -END -$$; - -CREATE OR REPLACE FUNCTION get_daily_digest_recently_completed(_user_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; -BEGIN - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _result - FROM ( - -- - SELECT id, - name, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) AS projects - FROM ( - -- - SELECT name, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) AS tasks - FROM ( - -- - SELECT t.id, - t.name, - (SELECT STRING_AGG(DISTINCT - (SELECT name - FROM team_member_info_view - WHERE team_member_id = tasks_assignees.team_member_id), - ', ') - FROM tasks_assignees - WHERE task_id = t.id) AS members - FROM tasks_assignees - INNER JOIN tasks t ON tasks_assignees.task_id = t.id - INNER JOIN team_members tm ON tasks_assignees.team_member_id = tm.id - WHERE tm.user_id = _user_id - AND t.project_id = projects.id - AND t.completed_at IS NOT NULL - AND TO_CHAR(t.completed_at, 'yyyy-mm-dd') = - TO_CHAR(CURRENT_DATE, 'yyyy-mm-dd') - LIMIT 10 - -- - ) r) - FROM projects - WHERE projects.team_id IN - (SELECT team_id - FROM team_members - WHERE user_id = _user_id - AND team_members.team_id = teams.id) - -- - ) r) - FROM teams - WHERE (SELECT daily_digest_enabled - FROM notification_settings - WHERE team_id = teams.id - AND user_id = _user_id) IS TRUE - AND EXISTS(SELECT 1 - FROM team_members - WHERE team_id = teams.id - AND team_members.user_id = _user_id) - -- - ) rec; - - RETURN _result; -END -$$; - -CREATE OR REPLACE FUNCTION get_last_updated_tasks_by_project(_project_id uuid, _limit integer, _offset integer, _archived boolean) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _tasks JSON; -BEGIN - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _tasks - FROM (SELECT id, - name, - (SELECT name FROM task_statuses WHERE status_id = task_statuses.id) AS status, - status_id, - end_date, - priority_id AS priority, - updated_at, - (SELECT color_code - FROM sys_task_status_categories - WHERE id = (SELECT category_id FROM task_statuses WHERE id = status_id)) AS status_color - FROM tasks - WHERE project_id = _project_id - AND CASE - WHEN (_archived IS TRUE) THEN project_id IS NOT NULL - ELSE archived IS FALSE END - ORDER BY updated_at DESC - LIMIT _limit OFFSET _offset) rec; - RETURN _tasks; -END -$$; - -CREATE OR REPLACE FUNCTION get_my_tasks(_team_id uuid, _user_id uuid, _size numeric, _offset numeric, _filter text) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; -BEGIN - -- _filter = '0' is tasks due today - IF _filter = '0' - THEN - SELECT ROW_TO_JSON(rec) - INTO _result - FROM (SELECT COUNT(*) AS total, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM (SELECT t.id, - t.name, - t.project_id, - t.status_id, - t.start_date, - t.end_date, - t.created_at, - p.team_id, - p.name AS project_name, - p.color_code AS project_color, - (SELECT name FROM task_statuses WHERE id = t.status_id) AS status, - (SELECT color_code - FROM sys_task_status_categories - WHERE id = (SELECT category_id FROM task_statuses WHERE id = t.status_id)) AS status_color, - TRUE AS is_task - FROM tasks t - CROSS JOIN projects p - WHERE t.project_id = p.id - AND t.archived IS FALSE - AND t.end_date::DATE = CURRENT_DATE::DATE - AND t.status_id NOT IN (SELECT id - FROM task_statuses - WHERE category_id NOT IN - (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) - AND t.id IN - (SELECT task_id - FROM tasks_assignees - WHERE team_member_id = (SELECT id - FROM team_members - WHERE user_id = _user_id - AND team_id = _team_id)) - ORDER BY p.updated_at, created_at - LIMIT _size OFFSET _offset) rec) AS data - FROM tasks t - CROSS JOIN projects p - WHERE t.project_id = p.id - AND t.archived IS FALSE - AND t.end_date::DATE = CURRENT_DATE::DATE - AND t.status_id NOT IN (SELECT id - FROM task_statuses - WHERE category_id NOT IN - (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) - AND t.id IN - (SELECT task_id - FROM tasks_assignees - WHERE team_member_id = (SELECT id - FROM team_members - WHERE user_id = _user_id - AND team_id = _team_id))) rec; - RETURN _result; - END IF; - - -- _filter = '1' is upcoming tasks - IF _filter = '1' - THEN - SELECT ROW_TO_JSON(rec) - INTO _result - FROM (SELECT COUNT(*) AS total, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM (SELECT t.id, - t.name, - t.project_id, - t.status_id, - t.start_date, - t.end_date, - t.created_at, - p.team_id, - p.name AS project_name, - p.color_code AS project_color, - (SELECT name FROM task_statuses WHERE id = t.status_id) AS status, - (SELECT color_code - FROM sys_task_status_categories - WHERE id = (SELECT category_id FROM task_statuses WHERE id = t.status_id)) AS status_color, - TRUE AS is_task - FROM tasks t - CROSS JOIN projects p - WHERE t.project_id = p.id - AND t.archived IS FALSE - AND t.end_date::DATE > CURRENT_DATE::DATE - AND t.status_id NOT IN (SELECT id - FROM task_statuses - WHERE category_id NOT IN - (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) - AND t.id IN - (SELECT task_id - FROM tasks_assignees - WHERE team_member_id = (SELECT id - FROM team_members - WHERE user_id = _user_id - AND team_id = _team_id)) - ORDER BY p.updated_at, created_at - LIMIT _size OFFSET _offset) rec) AS data - FROM tasks t - CROSS JOIN projects p - WHERE t.project_id = p.id - AND t.archived IS FALSE - AND t.end_date::DATE > CURRENT_DATE::DATE - AND t.status_id NOT IN (SELECT id - FROM task_statuses - WHERE category_id NOT IN - (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) - AND t.id IN - (SELECT task_id - FROM tasks_assignees - WHERE team_member_id = (SELECT id - FROM team_members - WHERE user_id = _user_id - AND team_id = _team_id))) rec; - RETURN _result; - END IF; - - -- _filter = '2' is overdue tasks - IF _filter = '2' - THEN - SELECT ROW_TO_JSON(rec) - INTO _result - FROM (SELECT COUNT(*) AS total, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM (SELECT t.id, - t.name, - t.project_id, - t.status_id, - t.start_date, - t.end_date, - t.created_at, - p.team_id, - p.name AS project_name, - p.color_code AS project_color, - (SELECT name FROM task_statuses WHERE id = t.status_id) AS status, - (SELECT color_code - FROM sys_task_status_categories - WHERE id = (SELECT category_id FROM task_statuses WHERE id = t.status_id)) AS status_color, - TRUE AS is_task - FROM tasks t - CROSS JOIN projects p - WHERE t.project_id = p.id - AND t.archived IS FALSE - AND t.end_date::DATE < CURRENT_DATE::DATE - AND t.status_id NOT IN (SELECT id - FROM task_statuses - WHERE category_id NOT IN - (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) - AND t.id IN - (SELECT task_id - FROM tasks_assignees - WHERE team_member_id = (SELECT id - FROM team_members - WHERE user_id = _user_id - AND team_id = _team_id)) - ORDER BY p.updated_at, created_at - LIMIT _size OFFSET _offset) rec) AS data - FROM tasks t - CROSS JOIN projects p - WHERE t.project_id = p.id - AND t.archived IS FALSE - AND t.end_date::DATE < CURRENT_DATE::DATE - AND t.status_id NOT IN (SELECT id - FROM task_statuses - WHERE category_id NOT IN - (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) - AND t.id IN - (SELECT task_id - FROM tasks_assignees - WHERE team_member_id = (SELECT id - FROM team_members - WHERE user_id = _user_id - AND team_id = _team_id))) rec; - RETURN _result; - END IF; - - -- _filter = '3' is todo list - IF _filter = '3' - THEN - SELECT ROW_TO_JSON(rec) - INTO _result - FROM (SELECT COUNT(*) AS total, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM (SELECT id, name, created_at, color_code, done - FROM personal_todo_list - WHERE user_id = _user_id - ORDER BY updated_at, created_at - LIMIT _size OFFSET _offset) rec) AS data) rec; - RETURN _result; - END IF; - -END; -$$; - -CREATE OR REPLACE FUNCTION get_project_daily_digest() RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; -BEGIN - - SELECT COALESCE(JSON_AGG(rec), '[]'::JSON) - INTO _result - FROM (SELECT id, - name, - (SELECT name FROM teams WHERE id = projects.team_id) AS team_name, - - (SELECT COALESCE(JSON_AGG(rec), '[]'::JSON) - FROM (SELECT id, - name, - (SELECT STRING_AGG(DISTINCT - (SELECT name - FROM team_member_info_view - WHERE team_member_id = tasks_assignees.team_member_id), - ', ') - FROM tasks_assignees - WHERE task_id = tasks.id) AS members - FROM tasks - WHERE project_id = projects.id - AND TO_CHAR(tasks.completed_at, 'yyyy-mm-dd') = - TO_CHAR(CURRENT_DATE, 'yyyy-mm-dd')) rec) AS today_completed, - - (SELECT COALESCE(JSON_AGG(rec), '[]'::JSON) - FROM (SELECT id, - name, - (SELECT STRING_AGG(DISTINCT - (SELECT name - FROM team_member_info_view - WHERE team_member_id = tasks_assignees.team_member_id), - ', ') - FROM tasks_assignees - WHERE task_id = tasks.id) AS members - FROM tasks - WHERE project_id = projects.id - AND TO_CHAR(tasks.created_at, 'yyyy-mm-dd') = - TO_CHAR(CURRENT_DATE, 'yyyy-mm-dd')) rec) AS today_new, - - (SELECT COALESCE(JSON_AGG(rec), '[]'::JSON) - FROM (SELECT id, - name, - (SELECT STRING_AGG(DISTINCT - (SELECT name - FROM team_member_info_view - WHERE team_member_id = tasks_assignees.team_member_id), - ', ') - FROM tasks_assignees - WHERE task_id = tasks.id) AS members - FROM tasks - WHERE project_id = projects.id - AND TO_CHAR(tasks.end_date, 'yyyy-mm-dd') = - TO_CHAR(CURRENT_DATE + INTERVAL '1 day', 'yyyy-mm-dd')) rec) AS due_tomorrow, - - (SELECT COALESCE(JSON_AGG(rec), '[]'::JSON) - FROM (SELECT name, email - FROM users - WHERE id = (SELECT user_id - FROM project_subscribers - WHERE project_id = projects.id - AND user_id = users.id)) rec) AS subscribers - - FROM projects - WHERE EXISTS(SELECT 1 FROM project_subscribers WHERE project_id = projects.id) - ORDER BY team_id, name) rec; - - RETURN _result; -END -$$; - -CREATE OR REPLACE FUNCTION get_project_deadline_tasks(_project_id uuid, _archived boolean) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; - -BEGIN - SELECT COALESCE(ROW_TO_JSON(rec), '[]'::JSON) - INTO _result - FROM (SELECT (SELECT COUNT(*) - FROM tasks - WHERE project_id = _project_id - AND CASE - WHEN (_archived IS TRUE) THEN project_id IS NOT NULL - ELSE archived IS FALSE END - AND end_date::DATE > (SELECT end_date - FROM projects - WHERE id = _project_id)::DATE) AS deadline_tasks_count, - (SELECT SUM(twl.time_spent) - FROM tasks t - CROSS JOIN task_work_log twl - WHERE twl.task_id = t.id - AND t.project_id = _project_id - AND twl.created_at::DATE > (SELECT end_date - FROM projects - WHERE id = _project_id)::DATE - AND CASE - WHEN (_archived IS TRUE) THEN t.project_id IS NOT NULL - ELSE t.archived IS FALSE END) AS deadline_logged_hours, - (SELECT end_date FROM projects WHERE id = _project_id) AS project_end_date, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) AS tasks - FROM (SELECT id, - name, - status_id, - start_date, - end_date, - (SELECT name FROM task_statuses WHERE id = tasks.status_id) AS status, - (SELECT color_code - FROM sys_task_status_categories - WHERE id = (SELECT category_id FROM task_statuses WHERE id = status_id)) AS status_color - - FROM tasks - WHERE project_id = _project_id - AND CASE - WHEN (_archived IS TRUE) THEN project_id IS NOT NULL - ELSE archived IS FALSE END - AND end_date::DATE > (SELECT end_date - FROM projects - WHERE id = _project_id)::DATE) r)) rec; - RETURN _result; - -END; -$$; - -CREATE OR REPLACE FUNCTION get_project_gantt_tasks(_project_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _tasks JSON; -BEGIN - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _tasks - FROM (SELECT p.id, - p.name, - 0 AS level, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM (SELECT pm.id, - pm.team_member_id, - pm.project_access_level_id, - (SELECT name - FROM job_titles - WHERE job_titles.id = tm.job_title_id) AS job_title, - (SELECT name - FROM team_member_info_view - WHERE team_member_info_view.team_member_id = tm.id), - u.avatar_url, - (SELECT email - FROM team_member_info_view - WHERE team_member_info_view.team_member_id = tm.id), - (SELECT name - FROM project_access_levels - WHERE project_access_levels.id = pm.project_access_level_id) AS access_level, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM (SELECT t.id, - t.name, - t.start_date AS start, - t.project_id, - t.priority_id, - t.done, - t.end_date AS "end", - (SELECT color_code - FROM projects - WHERE projects.id = t.project_id) AS color_code, - t.status_id, - (SELECT name FROM task_statuses WHERE id = t.status_id) AS status, - (SELECT ARRAY_AGG(ROW_TO_JSON(rec)) - FROM (SELECT project_member_id AS id, - (SELECT name - FROM team_member_info_view - WHERE team_member_info_view.team_member_id = tm.id), - u2.avatar_url, - (SELECT team_member_info_view.email - FROM team_member_info_view - WHERE team_member_info_view.team_member_id = tm.id) - FROM tasks_assignees - INNER JOIN project_members pm ON pm.id = tasks_assignees.project_member_id - INNER JOIN team_members tm2 ON pm.team_member_id = tm2.id - LEFT JOIN users u2 ON tm2.user_id = u2.id - WHERE project_id = _project_id::UUID - AND project_member_id = pm.id - AND t.id = tasks_assignees.task_id - ORDER BY name) rec) AS assignees - FROM tasks_assignees ta, - tasks t - WHERE t.archived IS FALSE - AND ta.project_member_id = pm.id - AND t.id = ta.task_id - ORDER BY start_date) rec) AS tasks - FROM project_members pm - INNER JOIN team_members tm ON pm.team_member_id = tm.id - LEFT JOIN users u ON tm.user_id = u.id - WHERE project_id = p.id) rec) AS team_members - FROM projects p - WHERE p.id = _project_id::UUID) rec; - - RETURN _tasks; -END; -$$; - -CREATE OR REPLACE FUNCTION get_project_member_insights(_project_id uuid, _archived boolean) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; - -BEGIN - SELECT COALESCE(ROW_TO_JSON(rec), '[]'::JSON) - INTO _result - FROM (SELECT (SELECT COUNT(*) FROM project_members WHERE project_id = _project_id) AS total_members_count, - (SELECT COUNT(*) - FROM project_members - WHERE project_id = _project_id - AND team_member_id NOT IN - (SELECT team_member_id - FROM tasks_assignees - WHERE task_id IN (SELECT id - FROM tasks - WHERE tasks.project_id = _project_id - AND CASE - WHEN (_archived IS TRUE) THEN project_id IS NOT NULL - ELSE archived IS FALSE END))) AS unassigned_members, - (SELECT COUNT(*) - FROM project_members - WHERE project_id = _project_id - AND team_member_id IN - (SELECT team_member_id - FROM tasks_assignees - WHERE task_id IN (SELECT id - FROM tasks - WHERE tasks.project_id = _project_id - AND CASE - WHEN (_archived IS TRUE) THEN project_id IS NOT NULL - ELSE archived IS FALSE END - AND tasks.end_date::DATE < NOW()::DATE))) AS overdue_members) rec; - RETURN _result; - -END; -$$; - -CREATE OR REPLACE FUNCTION get_project_members(_project_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _output JSON; -BEGIN - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM ( - -- - SELECT (SELECT name FROM team_member_info_view WHERE team_member_info_view.team_member_id = tm.id), - u.avatar_url - FROM project_members - INNER JOIN team_members tm ON project_members.team_member_id = tm.id - LEFT JOIN users u ON tm.user_id = u.id - WHERE project_id = _project_id - -- - ) rec - INTO _output; - - RETURN _output; -END; -$$; - -CREATE OR REPLACE FUNCTION get_project_overview_data(_project_id uuid, _archived boolean) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; - -BEGIN - SELECT COALESCE(ROW_TO_JSON(rec), '[]'::JSON) - INTO _result - FROM (SELECT (SELECT COUNT(*) FROM tasks WHERE project_id = _project_id) AS total_tasks_count, - (SELECT COUNT(*) - FROM tasks - WHERE project_id = _project_id - AND archived IS TRUE) AS archived_tasks_count, - (SELECT COUNT(*) - FROM tasks - WHERE completed_at > CURRENT_DATE - INTERVAL '7 days' - AND project_id = _project_id - AND CASE - WHEN (_archived IS TRUE) THEN project_id IS NOT NULL - ELSE archived IS FALSE END) AS last_week_count, - (SELECT COUNT(*) - FROM tasks - WHERE project_id = _project_id - AND parent_task_id IS NOT NULL - AND CASE - WHEN (_archived IS TRUE) THEN project_id IS NOT NULL - ELSE archived IS FALSE END) AS sub_tasks_count, - (SELECT COUNT(*) - FROM tasks - WHERE project_id = _project_id - AND CASE - WHEN (_archived IS TRUE) THEN project_id IS NOT NULL - ELSE archived IS FALSE END - AND status_id IN (SELECT id - FROM task_statuses - WHERE project_id = _project_id - AND category_id IN - (SELECT id - FROM sys_task_status_categories - WHERE sys_task_status_categories.is_done IS TRUE))) AS completed_tasks_count, - (SELECT color_code - FROM sys_task_status_categories - WHERE sys_task_status_categories.is_done IS TRUE) AS completed_tasks_color_code, - (SELECT COUNT(*) - FROM tasks - WHERE project_id = _project_id - AND CASE - WHEN (_archived IS TRUE) THEN project_id IS NOT NULL - ELSE archived IS FALSE END - AND status_id IN (SELECT id - FROM task_statuses - WHERE project_id = _project_id - AND category_id IN - (SELECT id - FROM sys_task_status_categories - WHERE sys_task_status_categories.is_doing IS TRUE))) AS pending_tasks_count, - (SELECT color_code - FROM sys_task_status_categories - WHERE sys_task_status_categories.is_doing IS TRUE) AS pending_tasks_color_code, - (SELECT COUNT(*) - FROM tasks - WHERE project_id = _project_id - AND CASE - WHEN (_archived IS TRUE) THEN project_id IS NOT NULL - ELSE archived IS FALSE END - AND is_completed(status_id, project_id) IS FALSE) AS todo_tasks_count, - (SELECT color_code - FROM sys_task_status_categories - WHERE sys_task_status_categories.is_todo IS TRUE) AS todo_tasks_color_code, - (SELECT COUNT(*) - FROM tasks - WHERE project_id = _project_id - AND end_date::DATE < NOW()::DATE - AND CASE - WHEN (_archived IS TRUE) THEN project_id IS NOT NULL - ELSE archived IS FALSE END - AND status_id IN (SELECT id - FROM task_statuses - WHERE project_id = _project_id - AND category_id IN - (SELECT id - FROM sys_task_status_categories - WHERE sys_task_status_categories.is_done IS FALSE))) AS overdue_count, - (SELECT SUM(total_minutes) - FROM tasks - WHERE project_id = _project_id - AND CASE - WHEN (_archived IS TRUE) THEN project_id IS NOT NULL - ELSE archived IS FALSE END) AS total_minutes_sum, - (SELECT SUM(time_spent) - FROM task_work_log - CROSS JOIN tasks t - WHERE task_id = t.id - AND CASE - WHEN (_archived IS TRUE) THEN project_id IS NOT NULL - ELSE archived IS FALSE END - AND t.project_id = _project_id) AS time_spent_sum) rec; - RETURN _result; - -END; -$$; - -CREATE OR REPLACE FUNCTION get_project_wise_resources(_start_date date, _end_date date, _team_id uuid) RETURNS text - LANGUAGE plpgsql -AS -$$ -DECLARE - _projects JSON; - -BEGIN - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM (SELECT id, - name, - color_code, - FALSE AS collapsed, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM (SELECT date_series, - project_id, - SUM(total_minutes / 60), - JSON_AGG(JSON_BUILD_OBJECT('id', tasks_.id, - 'name', tasks_.name - )) AS scheduled_tasks - FROM GENERATE_SERIES( - _start_date::DATE, - _end_date::DATE, - '1 day' - ) AS date_series - CROSS JOIN (SELECT id, name, project_id, total_minutes, start_date, end_date - FROM tasks) AS tasks_ - WHERE (date_series >= tasks_.start_date::DATE AND date_series <= tasks_.end_date::DATE) - AND tasks_.project_id = projects.id - GROUP BY date_series, project_id) rec) AS schedule, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM (SELECT team_member_id, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM (SELECT date_series, - project_id, - SUM(total_minutes / 60), - JSON_AGG(JSON_BUILD_OBJECT('id', tasks_.id, - 'name', tasks_.name - )) AS scheduled_tasks - FROM GENERATE_SERIES( - _start_date::DATE, - _end_date::DATE, - '1 day' - ) AS date_series - CROSS JOIN (SELECT id, name, project_id, total_minutes, start_date, end_date - FROM tasks, - tasks_assignees - WHERE task_id = tasks.id - AND tasks_assignees.team_member_id = project_members.team_member_id) AS tasks_ - WHERE (date_series >= tasks_.start_date::DATE AND - date_series <= tasks_.end_date::DATE) - AND tasks_.project_id = projects.id - GROUP BY date_series, project_id) rec) AS tasks, - (SELECT name - FROM users - WHERE users.id = - (SELECT user_id - FROM team_members - WHERE team_members.id = project_members.team_member_id)), - (SELECT email - FROM email_invitations - WHERE project_members.team_member_id = email_invitations.team_member_id) AS invitee_email, - (SELECT avatar_url - FROM users - WHERE users.id = - (SELECT user_id - FROM team_members - WHERE team_members.id = project_members.team_member_id)) - FROM project_members - WHERE project_id = projects.id) rec) AS project_members, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM (SELECT date_series, - project_id, - SUM(total_minutes / 60), - JSON_AGG(JSON_BUILD_OBJECT('id', tasks_.id, - 'name', tasks_.name - )) AS scheduled_tasks - FROM GENERATE_SERIES( - _start_date::DATE, - _end_date, - '1 day' - ) AS date_series - CROSS JOIN (SELECT id, name, project_id, total_minutes, start_date, end_date - FROM tasks - WHERE tasks.project_id = project_id - AND tasks.id NOT IN (SELECT task_id FROM tasks_assignees)) AS tasks_ - WHERE (date_series >= tasks_.start_date::DATE AND date_series <= tasks_.end_date::DATE) - AND tasks_.project_id = projects.id - GROUP BY date_series, project_id) rec) AS unassigned_tasks - FROM projects - WHERE team_id = _team_id - AND id NOT IN (SELECT project_id FROM archived_projects WHERE project_id = projects.id) - ORDER BY updated_at DESC) rec - INTO _projects; - - RETURN _projects; -END; -$$; - -CREATE OR REPLACE FUNCTION get_reporting_member_current_doing_tasks(_team_member_id uuid, _user_id uuid, _include_archived boolean, _limit numeric, _offset numeric) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; - -BEGIN - SELECT ROW_TO_JSON(rec) - INTO _result - FROM (SELECT COUNT(*) AS total, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM ((SELECT t.id, - t.name AS task, - p.name AS project, - p.id AS project_id, - (SELECT name FROM teams WHERE id = p.team_id) AS team_name, - (SELECT name - FROM task_statuses - WHERE id = t.status_id) AS status, - (SELECT color_code - FROM sys_task_status_categories - WHERE id = - (SELECT category_id FROM task_statuses WHERE id = t.status_id)) AS status_color, - t.end_date, - t.updated_at AS last_updated - FROM tasks t - LEFT JOIN tasks_assignees ta ON t.id = ta.task_id - LEFT JOIN projects p ON t.project_id = p.id - WHERE ta.team_member_id = _team_member_id - AND p.team_id IN (SELECT team_id - FROM team_members - WHERE user_id = _user_id - AND role_id IN (SELECT id - FROM roles - WHERE (admin_role IS TRUE OR owner IS TRUE))) - AND CASE - WHEN (_include_archived IS TRUE) THEN team_id IS NOT NULL - ELSE NOT EXISTS(SELECT project_id - FROM archived_projects - WHERE project_id = p.id) END - AND t.status_id IN - (SELECT id - FROM task_statuses - WHERE category_id IN - (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) - ORDER BY end_date DESC - LIMIT _limit OFFSET _offset)) rec) AS data - FROM tasks t - LEFT JOIN tasks_assignees ta ON t.id = ta.task_id - LEFT JOIN projects p ON t.project_id = p.id - WHERE ta.team_member_id = _team_member_id - AND p.team_id IN (SELECT team_id - FROM team_members - WHERE user_id = _user_id - AND role_id IN (SELECT id - FROM roles - WHERE (admin_role IS TRUE OR owner IS TRUE))) - AND CASE - WHEN (_include_archived IS TRUE) THEN team_id IS NOT NULL - ELSE NOT EXISTS(SELECT project_id - FROM archived_projects - WHERE project_id = p.id) END - AND t.status_id IN - (SELECT id - FROM task_statuses - WHERE category_id IN - (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE))) rec; - RETURN _result; -END; -$$; - -CREATE OR REPLACE FUNCTION get_reporting_member_overdue_tasks(_team_member_id uuid, _user_id uuid, _include_archived boolean, _limit numeric, _offset numeric) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; - -BEGIN - SELECT ROW_TO_JSON(rec) - INTO _result - FROM (SELECT COUNT(*) AS total, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM ((SELECT t.id, - t.name AS task, - p.name AS project, - p.id AS project_id, - (SELECT name FROM teams WHERE id = p.team_id) AS team_name, - (SELECT name - FROM task_statuses - WHERE id = t.status_id) AS status, - (SELECT color_code - FROM sys_task_status_categories - WHERE id = - (SELECT category_id FROM task_statuses WHERE id = t.status_id)) AS status_color, - t.end_date, - t.updated_at AS last_updated, - t.end_date AS due_date - FROM tasks t - LEFT JOIN tasks_assignees ta ON t.id = ta.task_id - LEFT JOIN projects p ON t.project_id = p.id - WHERE ta.team_member_id = _team_member_id - AND p.team_id IN (SELECT team_id - FROM team_members - WHERE user_id = _user_id - AND role_id IN (SELECT id - FROM roles - WHERE (admin_role IS TRUE OR owner IS TRUE))) - AND CASE - WHEN (_include_archived IS TRUE) THEN team_id IS NOT NULL - ELSE NOT EXISTS(SELECT project_id - FROM archived_projects - WHERE project_id = p.id) END - AND t.status_id IN - (SELECT id - FROM task_statuses - WHERE category_id IN - (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) - AND t.end_date::DATE < NOW()::DATE - ORDER BY end_date DESC - LIMIT _limit OFFSET _offset)) rec) AS data - FROM tasks t - LEFT JOIN tasks_assignees ta ON t.id = ta.task_id - LEFT JOIN projects p ON t.project_id = p.id - WHERE ta.team_member_id = _team_member_id - AND p.team_id IN (SELECT team_id - FROM team_members - WHERE user_id = _user_id - AND role_id IN (SELECT id - FROM roles - WHERE (admin_role IS TRUE OR owner IS TRUE))) - AND CASE - WHEN (_include_archived IS TRUE) THEN team_id IS NOT NULL - ELSE NOT EXISTS(SELECT project_id - FROM archived_projects - WHERE project_id = p.id) END - AND t.status_id IN - (SELECT id - FROM task_statuses - WHERE category_id IN - (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) - AND t.end_date::DATE < NOW()::DATE) rec; - RETURN _result; -END; -$$; - -CREATE OR REPLACE FUNCTION get_reporting_member_recently_logged_tasks(_team_member_id uuid, _user_id uuid, _include_archived boolean) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; - -BEGIN - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _result - FROM ((SELECT DISTINCT twl.task_id AS id, - t.name, - (SELECT SUM(twl.time_spent)) AS logged_time, - p.name AS project_name, - p.id AS project_id, - (SELECT name FROM teams WHERE id = p.team_id) AS team_name, - (SELECT name - FROM task_statuses - WHERE id = (SELECT status_id FROM tasks WHERE id = twl.task_id)) AS status, - (SELECT color_code - FROM sys_task_status_categories - WHERE id = - (SELECT category_id FROM task_statuses WHERE id = t.status_id)) AS status_color, - t.end_date::DATE, - (SELECT MAX(created_at) - FROM task_work_log - WHERE task_work_log.task_id = twl.task_id) AS logged_timestamp - FROM task_work_log twl - LEFT JOIN tasks t ON twl.task_id = t.id - LEFT JOIN projects p ON t.project_id = p.id - WHERE user_id = (SELECT user_id FROM team_members WHERE id = _team_member_id) - - -- check if the team is a team that the user is either an admin or owner - AND p.team_id IN - (SELECT team_id - FROM team_members - WHERE user_id = _user_id - AND role_id IN (SELECT id - FROM roles - WHERE (admin_role IS TRUE OR owner IS TRUE))) - - -- check if the include_archived flag is true or false - AND CASE - WHEN (_include_archived IS TRUE) THEN team_id IS NOT NULL - ELSE NOT EXISTS(SELECT project_id - FROM archived_projects - WHERE project_id = p.id) END - - GROUP BY task_id, t.name, project_id, t.end_date, p.name, p.team_id, t.status_id, p.id - ORDER BY (SELECT MAX(created_at) FROM task_work_log WHERE task_work_log.task_id = twl.task_id) DESC - LIMIT 10)) rec; - RETURN _result; - -END; -$$; - -CREATE OR REPLACE FUNCTION get_reporting_members_stats(_team_member_id uuid, _include_archived boolean, _user_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; - _teams UUID[]; - -BEGIN - -- SELECT the team_id to select into the _teams variable so the function doesn't have to run it multiple times - SELECT ARRAY_AGG(team_id) - INTO _teams - FROM team_members - WHERE user_id = _user_id - AND role_id IN (SELECT id - FROM roles - WHERE (admin_role IS TRUE OR owner IS TRUE)); - /* - ##### id IN (SELECT UNNEST(_teams)) ##### - - the above piece of query is added to all the queries below to ensure that the statistics that are shown are related - to the teams that the user is either an admin or owner of the team - */ - - SELECT COALESCE(ROW_TO_JSON(rec), '[]'::JSON) - INTO _result - FROM ((SELECT (SELECT name FROM team_member_info_view WHERE team_member_id = _team_member_id), - - -- get total teams that the user is added to - (SELECT COUNT(*) - FROM teams - WHERE id IN (SELECT team_id - FROM team_members - WHERE team_members.user_id = - (SELECT user_id - FROM team_members - WHERE id = _team_member_id)) - AND id IN (SELECT UNNEST(_teams))) AS total_teams, - - -- select total projects the user is added to - (SELECT COUNT(*) - FROM project_members - LEFT JOIN projects p ON project_members.project_id = p.id - WHERE team_member_id IN (SELECT id - FROM team_members - WHERE user_id = - (SELECT user_id FROM team_members WHERE id = _team_member_id)) - - -- check if the team is a team that the user is either an admin or owner - AND p.team_id IN (SELECT UNNEST(_teams)) - AND p.team_id IN (SELECT team_id - FROM projects - WHERE id IN (SELECT project_id - FROM tasks - WHERE id IN (SELECT task_id - FROM tasks_assignees - WHERE tasks.id = tasks_assignees.task_id - AND team_member_id IN - (SELECT id - FROM team_members - WHERE user_id = (SELECT user_id - FROM team_members - WHERE id = _team_member_id))))) - AND CASE - WHEN (_include_archived IS TRUE) THEN project_members.project_id IS NOT NULL - ELSE NOT EXISTS(SELECT project_id - FROM archived_projects - WHERE project_id = project_members.project_id) END) AS project_members, - - -- select the total number of seconds estimated for the tasks that the user is assigned to - (SELECT SUM(total_minutes * 60) - FROM tasks - LEFT JOIN projects p ON tasks.project_id = p.id - WHERE EXISTS(SELECT 1 - FROM tasks_assignees - WHERE tasks.id = tasks_assignees.task_id - AND team_member_id IN - (SELECT id - FROM team_members - WHERE user_id = _user_id)) - - -- check if the team is a team that the user is either an admin or owner - AND p.team_id IN (SELECT UNNEST(_teams)) - AND CASE - WHEN (_include_archived IS TRUE) THEN tasks.project_id IS NOT NULL - ELSE NOT EXISTS(SELECT project_id - FROM archived_projects - WHERE project_id = tasks.project_id) END) AS total_estimated, - - -- select the total logged time for the tasks that the user is assigned to - (SELECT SUM(time_spent) - FROM tasks - LEFT JOIN task_work_log twl ON tasks.id = twl.task_id - LEFT JOIN projects p ON tasks.project_id = p.id - WHERE EXISTS(SELECT 1 - FROM tasks_assignees - WHERE tasks.id = tasks_assignees.task_id - AND team_member_id IN - (SELECT id - FROM team_members - WHERE user_id = _user_id)) - - -- check if the team is a team that the user is either an admin or owner - AND p.team_id IN (SELECT UNNEST(_teams)) - - AND CASE - WHEN (_include_archived IS TRUE) THEN tasks.project_id IS NOT NULL - ELSE NOT EXISTS(SELECT project_id - FROM archived_projects - WHERE project_id = tasks.project_id) END) AS total_logged, - - -- select the total tasks that the user is assigned to - (SELECT COUNT(*) - FROM tasks - LEFT JOIN projects p ON tasks.project_id = p.id - WHERE EXISTS(SELECT 1 - FROM tasks_assignees - WHERE tasks.id = tasks_assignees.task_id - AND team_member_id IN - (SELECT id - FROM team_members - WHERE user_id = _user_id)) - - -- check if the team is a team that the user is either an admin or owner - AND p.team_id IN (SELECT UNNEST(_teams)) - - AND CASE - WHEN (_include_archived IS TRUE) THEN tasks.project_id IS NOT NULL - ELSE NOT EXISTS(SELECT project_id - FROM archived_projects - WHERE project_id = tasks.project_id) END) AS total_tasks, - - -- select the total tasks that the user has completed - (SELECT COUNT(*) - FROM tasks - LEFT JOIN projects p ON tasks.project_id = p.id - - WHERE EXISTS(SELECT 1 - FROM tasks_assignees - WHERE tasks.id = tasks_assignees.task_id - AND team_member_id IN - (SELECT id - FROM team_members - WHERE user_id = _user_id)) - - -- check if the team is a team that the user is either an admin or owner - AND p.team_id IN (SELECT UNNEST(_teams)) - - AND tasks.status_id IN - (SELECT id - FROM task_statuses - WHERE category_id IN - (SELECT id FROM sys_task_status_categories WHERE is_done IS TRUE)) - AND CASE - WHEN (_include_archived IS TRUE) THEN tasks.project_id IS NOT NULL - ELSE NOT EXISTS(SELECT project_id - FROM archived_projects - WHERE project_id = tasks.project_id) END) AS total_tasks_completed, - - -- select the total tasks that are overdue - (SELECT COUNT(*) - FROM tasks - LEFT JOIN projects p ON tasks.project_id = p.id - - WHERE EXISTS(SELECT 1 - FROM tasks_assignees - WHERE tasks.id = tasks_assignees.task_id - AND team_member_id IN - (SELECT id - FROM team_members - WHERE user_id = _user_id)) - - -- check if the team is a team that the user is either an admin or owner - AND p.team_id IN (SELECT UNNEST(_teams)) - - AND tasks.end_date::DATE < NOW()::DATE - AND tasks.status_id IN - (SELECT id - FROM task_statuses - WHERE category_id IN - (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)) - AND CASE - WHEN (_include_archived IS TRUE) THEN tasks.project_id IS NOT NULL - ELSE NOT EXISTS(SELECT project_id - FROM archived_projects - WHERE project_id = tasks.project_id) END) AS overdue_tasks)) rec; - RETURN _result; - -END; -$$; - -CREATE OR REPLACE FUNCTION get_reporting_overview_stats(_user_id uuid, _include_archived boolean) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; - -BEGIN - SELECT COALESCE(ROW_TO_JSON(rec), '[]'::JSON) - INTO _result - FROM ((SELECT (SELECT COUNT(*) - FROM team_members - WHERE user_id = _user_id - AND role_id IN (SELECT id - FROM roles - WHERE (admin_role IS TRUE OR owner IS TRUE))) AS total_teams, - (SELECT COUNT(*) - FROM projects - WHERE team_id IN - (SELECT team_id - FROM team_members - WHERE user_id = _user_id - AND role_id IN (SELECT id - FROM roles - WHERE (admin_role IS TRUE OR owner IS TRUE))) - AND CASE - WHEN (_include_archived IS TRUE) THEN team_id IS NOT NULL - ELSE NOT EXISTS(SELECT project_id - FROM archived_projects - WHERE project_id = projects.id) END) AS total_projects, - (SELECT COUNT(*) - FROM projects - WHERE team_id IN - (SELECT team_id - FROM team_members - WHERE user_id = _user_id - AND role_id IN (SELECT id - FROM roles - WHERE (admin_role IS TRUE OR owner IS TRUE))) - AND CASE - WHEN (_include_archived IS TRUE) THEN team_id IS NOT NULL - ELSE NOT EXISTS(SELECT project_id - FROM archived_projects - WHERE project_id = projects.id) END - AND status_id IN (SELECT ID - FROM sys_project_statuses - WHERE sys_project_statuses.name NOT IN ('Completed', 'Cancelled'))) AS active_projects, - (SELECT COUNT(*) - FROM (SELECT DISTINCT user_id - FROM team_members tm - LEFT JOIN email_invitations ei ON tm.id = ei.team_member_id - WHERE tm.team_id IN (SELECT team_id - FROM team_members - WHERE user_id = _user_id - AND role_id IN (SELECT id - FROM roles - WHERE (admin_role IS TRUE OR owner IS TRUE)))) AS members) AS total_members, - (SELECT COUNT(*) - FROM (SELECT DISTINCT user_id - FROM team_members tm - LEFT JOIN email_invitations ei ON tm.id = ei.team_member_id - WHERE tm.team_id IN (SELECT team_id - FROM team_members - WHERE user_id = _user_id - AND role_id IN (SELECT id - FROM roles - WHERE (admin_role IS TRUE OR owner IS TRUE))) - AND team_member_id NOT IN - (SELECT team_member_id - FROM project_members pm - WHERE pm.project_id IN - (SELECT id FROM projects WHERE projects.team_id = tm.team_id))) AS members) AS unassigned_members, - (SELECT COUNT(*) - FROM (SELECT DISTINCT user_id - FROM team_members tm - LEFT JOIN email_invitations ei ON tm.id = ei.team_member_id - WHERE tm.team_id IN (SELECT team_id - FROM team_members - WHERE user_id = _user_id - AND role_id IN (SELECT id - FROM roles - WHERE (admin_role IS TRUE OR owner IS TRUE))) - AND tm.id IN (SELECT ta.team_member_id - FROM tasks_assignees ta - LEFT JOIN tasks t - ON t.id = ta.task_id AND t.end_date::DATE < NOW()::DATE AND - t.status_id IN - (SELECT id - FROM task_statuses - WHERE category_id NOT IN - (SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE)))) AS members) AS overdue_task_members, - (SELECT COUNT(*) - FROM projects - WHERE team_id IN - (SELECT team_id - FROM team_members - WHERE user_id = _user_id - AND role_id IN (SELECT id - FROM roles - WHERE (admin_role IS TRUE OR owner IS TRUE))) - AND end_date::DATE < NOW()::DATE - AND CASE - WHEN (_include_archived IS TRUE) THEN team_id IS NOT NULL - ELSE NOT EXISTS(SELECT project_id - FROM archived_projects - WHERE project_id = projects.id) END) AS overdue_projects)) rec; - RETURN _result; - -END; -$$; - -CREATE OR REPLACE FUNCTION get_reporting_projects_stats(_user_id uuid, _include_archived boolean) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; - _teams UUID[]; - -BEGIN - -- SELECT the team_id to select into the _teams variable so the function doesn't have to run it multiple times - SELECT ARRAY_AGG(team_id) - INTO _teams - FROM team_members - WHERE user_id = _user_id - AND role_id IN (SELECT id - FROM roles - WHERE (admin_role IS TRUE OR owner IS TRUE)); - - SELECT COALESCE(ROW_TO_JSON(rec), '[]'::JSON) - INTO _result - FROM ((SELECT (SELECT COUNT(*) - FROM projects - WHERE team_id IN (SELECT UNNEST(_teams)) - AND CASE - WHEN (_include_archived IS TRUE) THEN team_id IS NOT NULL - ELSE NOT EXISTS(SELECT project_id - FROM archived_projects - WHERE project_id = projects.id) END - AND status_id IN (SELECT ID - FROM sys_project_statuses - WHERE sys_project_statuses.name NOT IN ('Completed', 'Cancelled'))) AS active_projects, - (SELECT COUNT(*) - FROM projects - WHERE team_id IN (SELECT UNNEST(_teams)) - AND end_date::DATE < NOW()::DATE - AND CASE - WHEN (_include_archived IS TRUE) THEN team_id IS NOT NULL - ELSE NOT EXISTS(SELECT project_id - FROM archived_projects - WHERE project_id = projects.id) END) AS overdue_projects, - (SELECT SUM(time_spent) - FROM task_work_log - WHERE task_id IN (SELECT id - FROM tasks - WHERE project_id IN (SELECT id - FROM projects - WHERE team_id IN (SELECT UNNEST(_teams)) - AND CASE - WHEN (_include_archived IS TRUE) - THEN team_id IS NOT NULL - ELSE NOT EXISTS(SELECT project_id - FROM archived_projects - WHERE project_id = projects.id - AND user_id = _user_id) END)))::INT AS total_logged, - (SELECT SUM(total_minutes * 60) - FROM tasks - WHERE project_id IN (SELECT id - FROM projects - WHERE team_id IN (SELECT UNNEST(_teams)) - AND CASE - WHEN (TRUE IS TRUE) THEN team_id IS NOT NULL - ELSE NOT EXISTS(SELECT project_id - FROM archived_projects - WHERE project_id = projects.id - AND user_id = _user_id) END))::INT AS total_estimated, - (SELECT COUNT(*) - FROM tasks - WHERE archived IS FALSE - AND project_id IN (SELECT id - FROM projects - WHERE team_id IN (SELECT UNNEST(_teams)) - AND CASE - WHEN (TRUE IS TRUE) THEN team_id IS NOT NULL - ELSE NOT EXISTS(SELECT project_id - FROM archived_projects - WHERE project_id = projects.id - AND user_id = _user_id) END))::INT AS all_tasks_count, - (SELECT COUNT(*) - FROM tasks - WHERE archived IS FALSE - AND project_id IN (SELECT id - FROM projects - WHERE team_id IN (SELECT UNNEST(_teams)) - AND CASE - WHEN (TRUE IS TRUE) THEN team_id IS NOT NULL - ELSE NOT EXISTS(SELECT project_id - FROM archived_projects - WHERE project_id = projects.id - AND user_id = _user_id) END) - AND status_id IN (SELECT id - FROM task_statuses - WHERE project_id = tasks.project_id - AND category_id IN - (SELECT id FROM sys_task_status_categories WHERE is_done IS TRUE)))::INT AS completed_tasks_count)) rec; - RETURN _result; - -END; -$$; - -CREATE OR REPLACE FUNCTION get_resource_gantt_tasks(_user_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _tasks JSON; - _team_id UUID; -BEGIN - - SELECT active_team FROM users WHERE id = _user_id::UUID INTO _team_id; - - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _tasks - FROM (SELECT projects.id, - projects.name, - 0 AS level, - FALSE AS collapsed, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM (SELECT pm.id, - pm.team_member_id, - pm.project_access_level_id, - FALSE AS overdue, - (SELECT name - FROM job_titles - WHERE job_titles.id = tm.job_title_id) AS job_title, - (SELECT name - FROM team_member_info_view - WHERE team_member_info_view.team_member_id = tm.id), - u.avatar_url, - (SELECT name - FROM project_access_levels - WHERE project_access_levels.id = pm.project_access_level_id) AS access_level, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM (SELECT t.id, - t.id AS task_id, - t.name, - t.start_date AS start, - t.project_id, - t.priority_id, - t.done, - t.end_date AS "end", - 0 AS progress, - 2 AS level, - TRUE AS "showOnGraph", - t.status_id, - (SELECT name FROM task_statuses WHERE id = t.status_id) AS status, - (SELECT color_code - FROM projects - WHERE projects.id = t.project_id) AS color_code, - (SELECT ARRAY_AGG(ROW_TO_JSON(rec)) - FROM (SELECT project_member_id AS id, - (SELECT name - FROM team_member_info_view - WHERE team_member_info_view.team_member_id = tm.id) - FROM tasks_assignees - INNER JOIN project_members pm ON pm.id = tasks_assignees.project_member_id - INNER JOIN team_members tm2 ON pm.team_member_id = tm2.id - LEFT JOIN users u2 ON tm2.user_id = u2.id - WHERE project_id = t.project_id - AND project_member_id = pm.id - AND t.id = tasks_assignees.task_id - ORDER BY name) rec) AS assignees - FROM tasks_assignees ta, - tasks t - WHERE t.archived IS FALSE - AND ta.project_member_id = pm.id - AND t.id = ta.task_id - ORDER BY start_date) rec) AS tasks - FROM project_members pm - INNER JOIN team_members tm ON pm.team_member_id = tm.id - LEFT JOIN users u ON tm.user_id = u.id - WHERE project_id = projects.id) rec) AS team_members - FROM projects - WHERE team_id = _team_id - AND (CASE - WHEN (is_owner(_user_id, _team_id) OR is_admin(_user_id, _team_id)) THEN TRUE - ELSE is_member_of_project(projects.id, _user_id, _team_id) END) - ORDER BY NAME) rec; - - RETURN _tasks; -END; -$$; - -CREATE OR REPLACE FUNCTION get_selected_tasks(_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _tasks JSON; -BEGIN - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _tasks - FROM (SELECT t.id, - t.name, - t.start_date AS start, - t.end_date AS "end", - t.project_id, - (SELECT name FROM task_priorities WHERE task_priorities.id = t.priority_id) AS priority, - t.priority_id, - t.done, - t.created_at, - t.status_id, - (SELECT ARRAY_AGG(ROW_TO_JSON(rec)) - FROM (SELECT project_member_id AS id, - u2.name AS name, - (SELECT avatar_url FROM users WHERE id = tm2.user_id), - COALESCE((u2.email), (SELECT email - FROM email_invitations - WHERE email_invitations.team_member_id = tm2.id)) AS email - FROM tasks_assignees - INNER JOIN project_members pm ON pm.id = tasks_assignees.project_member_id - INNER JOIN team_members tm2 ON pm.team_member_id = tm2.id - LEFT JOIN users u2 ON tm2.user_id = u2.id - WHERE project_id = _id::UUID - AND project_member_id = pm.id - AND t.id = tasks_assignees.task_id - ORDER BY name) rec) AS assignees - FROM tasks t - WHERE archived IS FALSE - AND project_id = _id::UUID - AND (t.start_date IS NOT NULL - OR t.end_date IS NOT NULL - OR t.id IN (SELECT task_id FROM tasks_assignees))) rec; - - RETURN _tasks; -END; -$$; - -CREATE OR REPLACE FUNCTION get_single_pt_task(_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; -BEGIN - SELECT ROW_TO_JSON(rec) - INTO _result - FROM (SELECT id, - name, - cpt_tasks.template_id AS template_id, - cpt_tasks.parent_task_id, - cpt_tasks.parent_task_id IS NOT NULL AS is_sub_task, - (SELECT name FROM cpt_tasks WHERE id = cpt_tasks.parent_task_id) AS parent_task_name, - (SELECT COUNT('*') - FROM cpt_tasks - WHERE parent_task_id = cpt_tasks.id) AS sub_tasks_count, - cpt_tasks.status_id AS status, - (SELECT name FROM cpt_task_statuses WHERE id = cpt_tasks.status_id) AS status_name, - (SELECT color_code - FROM sys_task_status_categories - WHERE id = (SELECT category_id FROM cpt_task_statuses WHERE id = cpt_tasks.status_id)) AS status_color, - (SELECT COALESCE(ROW_TO_JSON(r), '{}'::JSON) - FROM (SELECT is_done, is_doing, is_todo - FROM sys_task_status_categories - WHERE id = (SELECT category_id FROM cpt_task_statuses WHERE id = cpt_tasks.status_id)) r) AS status_category, - (SELECT name - FROM cpt_phases - WHERE id = (SELECT phase_id FROM cpt_task_phases WHERE task_id = cpt_tasks.id)) AS phase_name, - (SELECT phase_id FROM cpt_task_phases WHERE task_id = cpt_tasks.id) AS phase_id, - (SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))) - FROM (SELECT cpt_task_labels.label_id AS id, - (SELECT name FROM team_labels WHERE id = cpt_task_labels.label_id) AS name, - (SELECT color_code FROM team_labels WHERE id = cpt_task_labels.label_id) - FROM cpt_task_labels - WHERE task_id = cpt_tasks.id - ORDER BY name) r) AS labels, - (SELECT id FROM task_priorities WHERE id = cpt_tasks.priority_id) AS priority, - (SELECT name FROM task_priorities WHERE id = cpt_tasks.priority_id) AS priority_name, - (SELECT value FROM task_priorities WHERE id = cpt_tasks.priority_id) AS priority_value, - total_minutes, - sort_order, - (SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))) - FROM (SELECT cpt_task_statuses.id AS id, - cpt_task_statuses.name AS name, - (SELECT color_code - FROM sys_task_status_categories - WHERE id = cpt_task_statuses.category_id) - FROM cpt_task_statuses - WHERE cpt_task_statuses.template_id = cpt_tasks.template_id) r) AS template_statuses - FROM cpt_tasks - WHERE id = _id) rec; - RETURN _result; -END -$$; - -CREATE OR REPLACE FUNCTION get_single_task(_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; -BEGIN - - SELECT ROW_TO_JSON(rec) - INTO _result - FROM (SELECT id, - name, - (SELECT name FROM projects WHERE project_id = projects.id) AS project_name, - CONCAT((SELECT key FROM projects WHERE id = project_id), '-', task_no) AS task_key, - tasks.project_id AS project_id, - tasks.parent_task_id, - tasks.parent_task_id IS NOT NULL AS is_sub_task, - (SELECT name FROM tasks WHERE id = tasks.parent_task_id) AS parent_task_name, - (SELECT COUNT('*') - FROM tasks - WHERE parent_task_id = tasks.id - AND archived IS FALSE) AS sub_tasks_count, - - tasks.status_id AS status, - tasks.archived, - - (SELECT name FROM task_statuses WHERE id = tasks.status_id) AS status_name, - (SELECT name FROM task_priorities WHERE id = tasks.priority_id) AS priority_name, - - (SELECT color_code - FROM sys_task_status_categories - WHERE id = (SELECT category_id FROM task_statuses WHERE id = tasks.status_id)) AS status_color, - - (SELECT get_task_assignees(tasks.id)) AS assignees, - - (SELECT name - FROM project_phases - WHERE id = (SELECT phase_id FROM task_phase WHERE task_id = tasks.id)) AS phase_name, - (SELECT color_code - FROM project_phases - WHERE id = (SELECT phase_id FROM task_phase WHERE task_id = tasks.id)) AS phase_color_code, - (SELECT phase_id FROM task_phase WHERE task_id = tasks.id) AS phase_id, - - (SELECT COALESCE(ROW_TO_JSON(r), '{}'::JSON) - FROM (SELECT is_done, is_doing, is_todo - FROM sys_task_status_categories - WHERE id = (SELECT category_id FROM task_statuses WHERE id = tasks.status_id)) r) AS status_category, - - (SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))) - FROM (SELECT task_labels.label_id AS id, - (SELECT name FROM team_labels WHERE id = task_labels.label_id) AS name, - (SELECT color_code FROM team_labels WHERE id = task_labels.label_id) - FROM task_labels - WHERE task_id = tasks.id - ORDER BY name) r) AS labels, - - (SELECT name FROM users WHERE id = reporter_id) AS reporter, - (SELECT id FROM task_priorities WHERE id = tasks.priority_id) AS priority, - (SELECT value FROM task_priorities WHERE id = tasks.priority_id) AS priority_value, - total_minutes, - (SELECT SUM(time_spent) FROM task_work_log WHERE task_id = tasks.id) AS total_minutes_spent, - start_date, - end_date, - sort_order, - (SELECT color_code FROM projects WHERE projects.id = tasks.project_id) AS project_color, - (SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))) - FROM (SELECT task_statuses.id AS id, - task_statuses.name AS name, - (SELECT color_code - FROM sys_task_status_categories - WHERE id = task_statuses.category_id) - FROM task_statuses - WHERE task_statuses.project_id = tasks.project_id) r) AS project_statuses - FROM tasks - WHERE id = _id) rec; - - RETURN _result; -END -$$; - -CREATE OR REPLACE FUNCTION get_task_assignees(_task_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _output JSON; -BEGIN - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM (SELECT team_member_id, - project_member_id, - (SELECT name FROM team_member_info_view WHERE team_member_info_view.team_member_id = tm.id), - (SELECT email_notifications_enabled - FROM notification_settings - WHERE team_id = tm.team_id - AND notification_settings.user_id = u.id) AS email_notifications_enabled, - u.avatar_url, - u.id AS user_id, - u.email, - u.socket_id AS socket_id, - tm.team_id AS team_id - FROM tasks_assignees - INNER JOIN team_members tm ON tm.id = tasks_assignees.team_member_id - LEFT JOIN users u ON tm.user_id = u.id - WHERE task_id = _task_id) rec - INTO _output; - - RETURN _output; -END; -$$; - -CREATE OR REPLACE FUNCTION get_task_complete_info(_task_id uuid, _status_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _color_code TEXT; - _members JSON; -BEGIN - SELECT color_code - FROM sys_task_status_categories - WHERE id = (SELECT category_id FROM task_statuses WHERE id = _status_id) - INTO _color_code; - - SELECT get_task_assignees(_task_id) INTO _members; - - RETURN JSON_BUILD_OBJECT( - 'color_code', _color_code, - 'members', _members - ); -END; -$$; - -CREATE OR REPLACE FUNCTION get_task_complete_ratio(_task_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _parent_task_done FLOAT = 0; - _sub_tasks_done FLOAT = 0; - _sub_tasks_count FLOAT = 0; - _total_completed FLOAT = 0; - _total_tasks FLOAT = 0; - _ratio FLOAT = 0; -BEGIN - SELECT (CASE - WHEN EXISTS(SELECT 1 - FROM tasks_with_status_view - WHERE tasks_with_status_view.task_id = _task_id - AND is_done IS TRUE) THEN 1 - ELSE 0 END) - INTO _parent_task_done; - SELECT COUNT(*) FROM tasks WHERE parent_task_id = _task_id AND archived IS FALSE INTO _sub_tasks_count; - - SELECT COUNT(*) - FROM tasks_with_status_view - WHERE parent_task_id = _task_id - AND is_done IS TRUE - INTO _sub_tasks_done; - - _total_completed = _parent_task_done + _sub_tasks_done; - _total_tasks = _sub_tasks_count + 1; -- +1 for the parent task - _ratio = (_total_completed / _total_tasks) * 100; - - RETURN JSON_BUILD_OBJECT( - 'ratio', _ratio, - 'total_completed', _total_completed, - 'total_tasks', _total_tasks - ); -END -$$; - -CREATE OR REPLACE FUNCTION get_task_form_view_model(_user_id uuid, _team_id uuid, _task_id uuid, _project_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _task JSON; - _priorities JSON; - _projects JSON; - _statuses JSON; - _team_members JSON; - _assignees JSON; - _phases JSON; -BEGIN - - -- Select task info - SELECT COALESCE(ROW_TO_JSON(rec), '{}'::JSON) - INTO _task - FROM (SELECT id, - name, - description, - start_date, - end_date, - done, - total_minutes, - priority_id, - project_id, - created_at, - updated_at, - status_id, - parent_task_id, - sort_order, - (SELECT phase_id FROM task_phase WHERE task_id = tasks.id) AS phase_id, - CONCAT((SELECT key FROM projects WHERE id = tasks.project_id), '-', task_no) AS task_key, - (SELECT start_time - FROM task_timers - WHERE task_id = tasks.id - AND user_id = _user_id) AS timer_start_time, - parent_task_id IS NOT NULL AS is_sub_task, - (SELECT COUNT('*') - FROM tasks - WHERE parent_task_id = tasks.id - AND archived IS FALSE) AS sub_tasks_count, - (SELECT COUNT(*) - FROM tasks_with_status_view tt - WHERE (tt.parent_task_id = tasks.id OR tt.task_id = tasks.id) - AND tt.is_done IS TRUE) - AS completed_count, - (SELECT COUNT(*) FROM task_attachments WHERE task_id = tasks.id) AS attachments_count, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) - FROM (SELECT task_labels.label_id AS id, - (SELECT name FROM team_labels WHERE id = task_labels.label_id), - (SELECT color_code FROM team_labels WHERE id = task_labels.label_id) - FROM task_labels - WHERE task_id = tasks.id - ORDER BY name) r) AS labels, - (SELECT color_code - FROM sys_task_status_categories - WHERE id = (SELECT category_id FROM task_statuses WHERE id = tasks.status_id)) AS status_color, - (SELECT COUNT(*) FROM tasks WHERE parent_task_id = _task_id) AS sub_tasks_count, - (SELECT name FROM users WHERE id = tasks.reporter_id) AS reporter, - (SELECT get_task_assignees(tasks.id)) AS assignees, - (SELECT id FROM team_members WHERE user_id = _user_id AND team_id = _team_id) AS team_member_id - FROM tasks - WHERE id = _task_id) rec; - - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _priorities - FROM (SELECT id, name FROM task_priorities ORDER BY value) rec; - - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _phases - FROM (SELECT id, name FROM project_phases WHERE project_id = _project_id ORDER BY name) rec; - - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _projects - FROM (SELECT id, name - FROM projects - WHERE team_id = _team_id - AND (CASE - WHEN (is_owner(_user_id, _team_id) OR is_admin(_user_id, _team_id) IS TRUE) THEN TRUE - ELSE is_member_of_project(projects.id, _user_id, _team_id) END) - ORDER BY name) rec; - - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _statuses - FROM (SELECT id, name FROM task_statuses WHERE project_id = _project_id) rec; - - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _team_members - FROM (SELECT team_members.id, - (SELECT name FROM team_member_info_view WHERE team_member_info_view.team_member_id = team_members.id), - (SELECT email FROM team_member_info_view WHERE team_member_info_view.team_member_id = team_members.id), - (SELECT avatar_url - FROM team_member_info_view - WHERE team_member_info_view.team_member_id = team_members.id), - CASE WHEN EXISTS ( - SELECT 1 FROM email_invitations WHERE team_member_id = team_members.id - ) THEN TRUE - ELSE FALSE - END AS is_pending - FROM team_members - LEFT JOIN users u ON team_members.user_id = u.id - WHERE team_id = _team_id - AND team_members.active IS TRUE) rec; - - SELECT get_task_assignees(_task_id) INTO _assignees; - - RETURN JSON_BUILD_OBJECT( - 'task', _task, - 'priorities', _priorities, - 'projects', _projects, - 'statuses', _statuses, - 'team_members', _team_members, - 'assignees', _assignees, - 'phases', _phases - ); -END; -$$; - -CREATE OR REPLACE FUNCTION get_task_updates() RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; -BEGIN - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _result - FROM (SELECT name, - email, - (SELECT id - FROM team_members - WHERE team_id = users.active_team - AND user_id = users.id) AS team_member_id, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) AS teams - FROM (SELECT id, - name, - (SELECT team_member_id - FROM team_member_info_view - WHERE team_id = teams.id - AND user_id = users.id) AS team_member_id, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) AS projects - FROM (SELECT id, - name, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) AS tasks - FROM (SELECT t.id, - t.name AS name, - (SELECT name FROM users WHERE id = task_updates.reporter_id) AS updater_name, - (SELECT STRING_AGG(DISTINCT - (SELECT name - FROM team_member_info_view - WHERE team_member_id = tasks_assignees.team_member_id), - ', ') - FROM tasks_assignees - WHERE task_id = task_updates.task_id) AS members - FROM task_updates - INNER JOIN tasks t ON task_updates.task_id = t.id - WHERE task_updates.user_id = users.id - AND task_updates.project_id = projects.id - AND task_updates.type = 'ASSIGN' - AND is_sent IS FALSE - ORDER BY task_updates.created_at) r) - FROM projects - WHERE team_id = teams.id - AND EXISTS(SELECT 1 - FROM task_updates - WHERE project_id = projects.id - AND type = 'ASSIGN' - AND is_sent IS FALSE)) r) - FROM teams - WHERE EXISTS(SELECT 1 FROM team_members WHERE team_id = teams.id AND user_id = users.id) - AND (SELECT email_notifications_enabled - FROM notification_settings - WHERE team_id = teams.id - AND user_id = users.id) IS TRUE) r) - FROM users - WHERE EXISTS(SELECT 1 FROM task_updates WHERE user_id = users.id)) rec; - - UPDATE task_updates SET is_sent = TRUE; - - RETURN _result; -END -$$; - -CREATE OR REPLACE FUNCTION get_tasks_by_project_member(_project_id uuid, _team_member_id uuid, _archived boolean) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; -BEGIN - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON) - INTO _result - FROM (SELECT id, - name, - status_id, - (SELECT name FROM task_statuses WHERE status_id = task_statuses.id) AS status, - start_date, - end_date, - completed_at, - total_minutes, - (SELECT color_code - FROM sys_task_status_categories - WHERE id = (SELECT category_id FROM task_statuses WHERE id = status_id)) AS status_color, - CASE - WHEN CURRENT_DATE::DATE > end_date::DATE - AND status_id NOT IN (SELECT id - FROM task_statuses - WHERE project_id = _project_id - AND category_id IN - (SELECT id - FROM sys_task_status_categories - WHERE sys_task_status_categories.is_done IS FALSE)) - THEN FALSE - ELSE - CASE - WHEN CURRENT_DATE::DATE > end_date::DATE - THEN TRUE - ELSE FALSE - END END AS is_overdue, - CASE - WHEN status_id - NOT IN (SELECT id - FROM task_statuses - WHERE project_id = _project_id - AND category_id IN - (SELECT id - FROM sys_task_status_categories - WHERE sys_task_status_categories.is_done IS FALSE)) - THEN 0 - ELSE - CASE - WHEN CURRENT_DATE::DATE > end_date::DATE - THEN NOW()::DATE - end_date::DATE - ELSE 0 - END END AS days_overdue, - CASE - WHEN (NOW()::DATE > end_date) - AND status_id - NOT IN (SELECT id - FROM task_statuses - WHERE project_id = _project_id - AND category_id IN - (SELECT id - FROM sys_task_status_categories - WHERE sys_task_status_categories.is_done IS FALSE)) - THEN 0 - ELSE COALESCE(CASE - WHEN - ((SELECT SUM(time_spent) FROM task_work_log WHERE task_id = tasks.id) > - (total_minutes * 60)) THEN - (SELECT SUM(time_spent) * 60 - FROM task_work_log - WHERE task_id = tasks.id) - - (total_minutes * 60) - ELSE 0 END, 0) END AS overlogged_time, - COALESCE(completed_at::DATE - end_date::DATE, 0) AS late_days - FROM tasks - WHERE project_id = _project_id - AND CASE - WHEN (_archived IS TRUE) THEN project_id IS NOT NULL - ELSE archived IS FALSE END - AND id IN - (SELECT task_id FROM tasks_assignees WHERE team_member_id = _team_member_id)) r; - RETURN _result; -END -$$; - -CREATE OR REPLACE FUNCTION get_tasks_by_status(_id uuid, _status text) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _tasks JSON; - _team_id UUID; -BEGIN - SELECT team_id FROM projects WHERE id = _id INTO _team_id; - - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _tasks - FROM (SELECT id, - name, - - t.parent_task_id, - t.parent_task_id IS NOT NULL AS is_sub_task, - (SELECT name FROM tasks WHERE id = t.parent_task_id) AS parent_task_name, - (SELECT COUNT('*') FROM tasks WHERE parent_task_id = t.id) AS sub_tasks_count, - - status_id AS status, - sort_order, - - (SELECT COUNT(*) - FROM tasks_with_status_view tt - WHERE (tt.parent_task_id = t.id OR tt.task_id = t.id) - AND tt.is_done IS TRUE) - AS completed_count, - - (SELECT get_task_assignees(t.id)) AS assignees, - - (SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))) - FROM (SELECT task_labels.label_id AS id, - (SELECT name FROM team_labels WHERE id = task_labels.label_id), - (SELECT color_code FROM team_labels WHERE id = task_labels.label_id) - FROM task_labels - WHERE task_id = t.id - ORDER BY name) r) AS labels, - - (SELECT name FROM users WHERE id = reporter_id) AS reporter, - (SELECT task_priorities.name FROM task_priorities WHERE id = t.priority_id) AS priority, - start_date, - end_date - FROM tasks t - WHERE archived IS FALSE - AND (project_id = _id) - AND (t.status_id IN (SELECT id FROM task_statuses WHERE name = _status)) - ORDER BY sort_order) rec; - - RETURN _tasks; -END; -$$; - -CREATE OR REPLACE FUNCTION get_tasks_total_and_completed_counts(_task_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _total_tasks INT; - _total_completed INT; -BEGIN - SELECT COUNT(*) - FROM tasks - WHERE (parent_task_id = _task_id OR id = _task_id) - AND archived IS FALSE - INTO _total_tasks; - - SELECT COUNT(*) - FROM tasks_with_status_view tt - WHERE (tt.parent_task_id = _task_id - OR tt.task_id = _task_id) - AND tt.is_done IS TRUE - INTO _total_completed; - - RETURN JSON_BUILD_OBJECT( - 'total_tasks', _total_tasks, - 'total_completed', _total_completed - ); -END -$$; - -CREATE OR REPLACE FUNCTION get_team_id_from_task(_id uuid) RETURNS uuid - LANGUAGE plpgsql -AS -$$ -DECLARE - _team_id UUID; -BEGIN - SELECT team_id INTO _team_id FROM projects WHERE id = (SELECT project_id FROM tasks WHERE tasks.id = _id); - RETURN _team_id; -END -$$; - -CREATE OR REPLACE FUNCTION get_team_members(_team_id uuid, _project_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _result JSON; -BEGIN - - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _result - FROM ( - -- - WITH mbers AS (SELECT team_members.id, - tmiv.name AS name, - tmiv.email, - tmiv.avatar_url, - team_members.user_id, - EXISTS(SELECT 1 - FROM project_members - WHERE project_id = _project_id - AND project_members.team_member_id = team_members.id) AS exists_in_project, - 0 AS usage, - CASE - WHEN EXISTS (SELECT 1 - FROM email_invitations - WHERE team_member_id = team_members.id) THEN TRUE - ELSE FALSE - END AS is_pending - FROM team_members - LEFT JOIN users u ON team_members.user_id = u.id - LEFT JOIN team_member_info_view tmiv ON team_members.id = tmiv.team_member_id - WHERE team_members.team_id = _team_id - AND active IS TRUE - ORDER BY tmiv.name) - SELECT id, name, user_id, email, avatar_url, usage, is_pending - FROM mbers - ORDER BY exists_in_project DESC - -- - ) rec; - - RETURN _result; -END; -$$; - -CREATE OR REPLACE FUNCTION get_team_wise_resources(_start_date date, _end_date date, _team_id uuid) RETURNS text - LANGUAGE plpgsql -AS -$$ -DECLARE - _projects JSON; - -BEGIN - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM (SELECT id, - FALSE AS collapsed, - (SELECT name FROM users WHERE user_id = users.id), - (SELECT email - FROM email_invitations - WHERE team_members.id = email_invitations.team_member_id) AS invitee_email, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM (SELECT date_series, - SUM(total_minutes / 60), - JSON_AGG(JSON_BUILD_OBJECT('id', tasks_.id, - 'name', tasks_.name, - 'project_id', tasks_.project_id, - 'project_name', (SELECT projects.name - FROM projects - WHERE projects.id = tasks_.project_id) - )) AS scheduled_tasks - FROM GENERATE_SERIES( - _start_date::DATE, - _end_date::DATE, - '1 day' - ) AS date_series - CROSS JOIN (SELECT id, name, project_id, total_minutes, start_date, end_date - FROM tasks) AS tasks_, - tasks_assignees - WHERE (date_series BETWEEN tasks_.start_date::DATE AND tasks_.end_date::DATE) - AND tasks_assignees.team_member_id = team_members.id - AND tasks_.id = tasks_assignees.task_id - GROUP BY date_series) rec) AS schedule, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM (SELECT projects.id, - name, - projects.color_code, - (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM (SELECT date_series, - project_id, - SUM(total_minutes / 60), - JSON_AGG(JSON_BUILD_OBJECT('id', tasks_.id, - 'name', tasks_.name - )) AS scheduled_tasks - FROM GENERATE_SERIES( - _start_date::DATE, - _end_date::DATE, - '1 day' - ) AS date_series - CROSS JOIN (SELECT id, name, project_id, total_minutes, start_date, end_date - FROM tasks) AS tasks_, - tasks_assignees - WHERE (date_series BETWEEN tasks_.start_date::DATE AND tasks_.end_date::DATE) - AND tasks_assignees.team_member_id = team_members.id - AND tasks_.id = tasks_assignees.task_id - AND tasks_.project_id = projects.id - GROUP BY date_series, project_id) rec) AS tasks - FROM projects, - project_members - WHERE project_id = projects.id - AND team_members.id IN (project_members.team_member_id) - ORDER BY updated_at DESC) rec) AS project_members - FROM team_members - WHERE team_id = _team_id - AND user_id IS NOT NULL - ORDER BY (SELECT name FROM users WHERE user_id = users.id)) rec - INTO _projects; - - RETURN _projects; -END; -$$; - -CREATE OR REPLACE FUNCTION get_unselected_tasks(_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _tasks JSON; -BEGIN - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _tasks - FROM (SELECT t.id, - t.name, - t.start_date AT TIME ZONE 'Asia/Colombo' AS start, - t.end_date AS "end", - t.project_id, - (SELECT name FROM task_priorities WHERE task_priorities.id = t.priority_id) AS priority, - t.priority_id, - t.done, - t.created_at, - t.status_id, - (SELECT ARRAY_AGG(ROW_TO_JSON(rec)) - FROM (SELECT project_member_id AS id, - u2.name AS name, - (SELECT avatar_url FROM users WHERE id = tm2.user_id), - COALESCE((u2.email), (SELECT email - FROM email_invitations - WHERE email_invitations.team_member_id = tm2.id)) AS email - FROM tasks_assignees - INNER JOIN project_members pm ON pm.id = tasks_assignees.project_member_id - INNER JOIN team_members tm2 ON pm.team_member_id = tm2.id - LEFT JOIN users u2 ON tm2.user_id = u2.id - WHERE project_id = _id::UUID - AND project_member_id = pm.id - AND t.id = tasks_assignees.task_id - ORDER BY name) rec) AS assignees - FROM tasks t - WHERE archived IS FALSE - AND project_id = _id::UUID - AND (t.start_date IS NULL - OR t.end_date IS NULL - OR t.id NOT IN (SELECT task_id FROM tasks_assignees))) rec; - - RETURN _tasks; -END; -$$; - -CREATE OR REPLACE FUNCTION handle_on_pt_task_phase_change(_task_id uuid, _phase_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _name TEXT; - _color_code TEXT; -BEGIN - IF NOT EXISTS(SELECT 1 FROM cpt_task_phases WHERE task_id = _task_id) - THEN - IF (is_null_or_empty(_phase_id) IS FALSE) - THEN - INSERT INTO cpt_task_phases (task_id, phase_id) VALUES (_task_id, _phase_id); - END IF; - ELSE - IF (is_null_or_empty(_phase_id) IS TRUE) - THEN - DELETE FROM cpt_task_phases WHERE task_id = _task_id; - ELSE - UPDATE cpt_task_phases SET phase_id = _phase_id WHERE task_id = _task_id; - END IF; - END IF; - IF (is_null_or_empty(_phase_id) IS FALSE) - THEN - SELECT name FROM cpt_phases WHERE id = _phase_id INTO _name; - SELECT color_code FROM cpt_phases WHERE id = _phase_id INTO _color_code; - END IF; - RETURN JSON_BUILD_OBJECT('name', _name, 'color_code', _color_code); -END -$$; - -CREATE OR REPLACE FUNCTION handle_on_pt_task_status_change(_task_id uuid, _status_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _task_info JSON; - _status_category JSON; - _task_name TEXT; - _previous_status_name TEXT; - _new_status_name TEXT; -BEGIN - SELECT name FROM cpt_tasks WHERE id = _task_id INTO _task_name; - - SELECT name - FROM cpt_task_statuses - WHERE id = (SELECT status_id FROM cpt_tasks WHERE id = _task_id) - INTO _previous_status_name; - - SELECT name FROM cpt_task_statuses WHERE id = _status_id INTO _new_status_name; - - IF (_previous_status_name != _new_status_name) - THEN - UPDATE cpt_tasks SET status_id = _status_id WHERE id = _task_id; - END IF; - - SELECT COALESCE(ROW_TO_JSON(r), '{}'::JSON) - FROM (SELECT is_done, is_doing, is_todo - FROM sys_task_status_categories - WHERE id = (SELECT category_id FROM cpt_task_statuses WHERE id = _status_id)) r - INTO _status_category; - - RETURN JSON_BUILD_OBJECT( - 'template_id', (SELECT template_id FROM cpt_tasks WHERE id = _task_id), - 'color_code', (SELECT color_code FROM sys_task_status_categories WHERE id = (SELECT category_id FROM cpt_task_statuses WHERE id = _status_id))::TEXT, - 'status_category', _status_category - ); -END -$$; - -CREATE OR REPLACE FUNCTION handle_on_task_phase_change(_task_id uuid, _phase_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _name TEXT; - _color_code TEXT; -BEGIN - IF NOT EXISTS(SELECT 1 FROM task_phase WHERE task_id = _task_id) - THEN - IF (is_null_or_empty(_phase_id) IS FALSE) - THEN - INSERT INTO task_phase (task_id, phase_id) VALUES (_task_id, _phase_id); - END IF; - ELSE - IF (is_null_or_empty(_phase_id) IS TRUE) - THEN - DELETE FROM task_phase WHERE task_id = _task_id; - ELSE - UPDATE task_phase SET phase_id = _phase_id WHERE task_id = _task_id; - END IF; - END IF; - - IF (is_null_or_empty(_phase_id) IS FALSE) - THEN - SELECT name FROM project_phases WHERE id = _phase_id INTO _name; - SELECT color_code FROM project_phases WHERE id = _phase_id INTO _color_code; - END IF; - - RETURN JSON_BUILD_OBJECT('name', _name, 'color_code', _color_code); -END -$$; - -CREATE OR REPLACE FUNCTION handle_on_task_status_change(_user_id uuid, _task_id uuid, _status_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _updater_name TEXT; - _task_name TEXT; - _previous_status_name TEXT; - _new_status_name TEXT; - _message TEXT; - _task_info JSON; - _status_category JSON; - _task_completed_at TIMESTAMPTZ; -BEGIN - SELECT name FROM tasks WHERE id = _task_id INTO _task_name; - - SELECT name - FROM task_statuses - WHERE id = (SELECT status_id FROM tasks WHERE id = _task_id) - INTO _previous_status_name; - - SELECT name FROM task_statuses WHERE id = _status_id INTO _new_status_name; - - IF (_previous_status_name != _new_status_name) - THEN - UPDATE tasks SET status_id = _status_id WHERE id = _task_id; - - SELECT get_task_complete_info(_task_id, _status_id) INTO _task_info; - - SELECT name FROM users WHERE id = _user_id INTO _updater_name; - - _message = CONCAT(_updater_name, ' transitioned "', _task_name, '" from ', _previous_status_name, ' ⟶ ', - _new_status_name); - END IF; - - SELECT completed_at FROM tasks WHERE id = _task_id INTO _task_completed_at; - - SELECT COALESCE(ROW_TO_JSON(r), '{}'::JSON) - FROM (SELECT is_done, is_doing, is_todo - FROM sys_task_status_categories - WHERE id = (SELECT category_id FROM task_statuses WHERE id = _status_id)) r - INTO _status_category; - - RETURN JSON_BUILD_OBJECT( - 'message', _message, - 'project_id', (SELECT project_id FROM tasks WHERE id = _task_id), - 'parent_done', (CASE - WHEN EXISTS(SELECT 1 - FROM tasks_with_status_view - WHERE tasks_with_status_view.task_id = _task_id - AND is_done IS TRUE) THEN 1 - ELSE 0 END), - 'color_code', (_task_info ->> 'color_code')::TEXT, - 'total_tasks', (_task_info ->> 'total_tasks')::INT, - 'total_completed', (_task_info ->> 'total_completed')::INT, - 'members', (_task_info ->> 'members')::JSON, - 'completed_at', _task_completed_at, - 'status_category', _status_category - ); -END -$$; - -CREATE OR REPLACE FUNCTION handle_pt_task_list_sort_between_groups(_from_index integer, _to_index integer, _task_id uuid, _template_id uuid) RETURNS void - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - - IF (_to_index = -1) - THEN - _to_index = COALESCE((SELECT MAX(sort_order) + 1 FROM cpt_tasks WHERE template_id = _template_id), 0); - END IF; - - IF _to_index > _from_index - THEN - UPDATE cpt_tasks - SET sort_order = sort_order - 1 - WHERE template_id = _template_id - AND sort_order > _from_index - AND sort_order < _to_index; - - UPDATE cpt_tasks SET sort_order = _to_index - 1 WHERE id = _task_id AND template_id = _template_id; - END IF; - - IF _to_index < _from_index - THEN - UPDATE cpt_tasks - SET sort_order = sort_order + 1 - WHERE template_id = _template_id - AND sort_order > _to_index - AND sort_order < _from_index; - - UPDATE cpt_tasks SET sort_order = _to_index + 1 WHERE id = _task_id AND template_id = _template_id; - END IF; -END -$$; - -CREATE OR REPLACE FUNCTION handle_pt_task_list_sort_inside_group(_from_index integer, _to_index integer, _task_id uuid, _template_id uuid) RETURNS void - LANGUAGE plpgsql -AS -$$ -BEGIN - IF _to_index > _from_index - THEN - UPDATE cpt_tasks - SET sort_order = sort_order - 1 - WHERE template_id = _template_id - AND sort_order > _from_index - AND sort_order <= _to_index; - END IF; - - IF _to_index < _from_index - THEN - UPDATE cpt_tasks - SET sort_order = sort_order + 1 - WHERE template_id = _template_id - AND sort_order >= _to_index - AND sort_order < _from_index; - END IF; - - UPDATE cpt_tasks SET sort_order = _to_index WHERE id = _task_id AND template_id = _template_id; -END -$$; - -CREATE OR REPLACE FUNCTION handle_pt_task_list_sort_order_change(_body json) RETURNS void - LANGUAGE plpgsql -AS -$$ -DECLARE - _from_index INT; - _to_index INT; - _task_id UUID; - _template_id UUID; - _from_group UUID; - _to_group UUID; - _group_by TEXT; -BEGIN - _template_id = (_body ->> 'template_id')::UUID; - _task_id = (_body ->> 'task_id')::UUID; - _from_index = (_body ->> 'from_index')::INT; - _to_index = (_body ->> 'to_index')::INT; - _from_group = (_body ->> 'from_group')::UUID; - _to_group = (_body ->> 'to_group')::UUID; - _group_by = (_body ->> 'group_by')::TEXT; - - IF (_from_group <> _to_group OR (_from_group <> _to_group) IS NULL) - THEN - IF (_group_by = 'status') - THEN - UPDATE cpt_tasks SET status_id = _to_group WHERE id = _task_id AND status_id = _from_group; - END IF; - - IF (_group_by = 'priority') - THEN - UPDATE cpt_tasks SET priority_id = _to_group WHERE id = _task_id AND priority_id = _from_group; - END IF; - - IF (_group_by = 'phase') - THEN - IF (is_null_or_empty(_to_group) IS FALSE) - THEN - INSERT INTO cpt_task_phases (task_id, phase_id) - VALUES (_task_id, _to_group) - ON CONFLICT (task_id) DO UPDATE SET phase_id = _to_group; - END IF; - IF (is_null_or_empty(_to_group) IS TRUE) - THEN - DELETE - FROM cpt_task_phases - WHERE task_id = _task_id; - END IF; - - END IF; - - IF ((_body ->> 'to_last_index')::BOOLEAN IS TRUE AND _from_index < _to_index) - THEN - PERFORM handle_pt_task_list_sort_inside_group(_from_index, _to_index, _task_id, _template_id); - ELSE - PERFORM handle_pt_task_list_sort_between_groups(_from_index, _to_index, _task_id, _template_id); - END IF; - ELSE - PERFORM handle_pt_task_list_sort_inside_group(_from_index, _to_index, _task_id, _template_id); - END IF; -END -$$; - -CREATE OR REPLACE FUNCTION handle_task_list_sort_between_groups(_from_index integer, _to_index integer, _task_id uuid, _project_id uuid) RETURNS void - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - - IF (_to_index = -1) - THEN - _to_index = COALESCE((SELECT MAX(sort_order) + 1 FROM tasks WHERE project_id = _project_id), 0); - END IF; - - IF _to_index > _from_index - THEN - UPDATE tasks - SET sort_order = sort_order - 1 - WHERE project_id = _project_id - AND sort_order > _from_index - AND sort_order < _to_index; - - UPDATE tasks SET sort_order = _to_index - 1 WHERE id = _task_id AND project_id = _project_id; - END IF; - - IF _to_index < _from_index - THEN - UPDATE tasks - SET sort_order = sort_order + 1 - WHERE project_id = _project_id - AND sort_order > _to_index - AND sort_order < _from_index; - - UPDATE tasks SET sort_order = _to_index + 1 WHERE id = _task_id AND project_id = _project_id; - END IF; -END -$$; - -CREATE OR REPLACE FUNCTION handle_task_list_sort_inside_group(_from_index integer, _to_index integer, _task_id uuid, _project_id uuid) RETURNS void - LANGUAGE plpgsql -AS -$$ -BEGIN - IF _to_index > _from_index - THEN - UPDATE tasks - SET sort_order = sort_order - 1 - WHERE project_id = _project_id - AND sort_order > _from_index - AND sort_order <= _to_index; - END IF; - - IF _to_index < _from_index - THEN - UPDATE tasks - SET sort_order = sort_order + 1 - WHERE project_id = _project_id - AND sort_order >= _to_index - AND sort_order < _from_index; - END IF; - - UPDATE tasks SET sort_order = _to_index WHERE id = _task_id AND project_id = _project_id; -END -$$; - -CREATE OR REPLACE FUNCTION handle_task_list_sort_order_change(_body json) RETURNS void - LANGUAGE plpgsql -AS -$$ -DECLARE - _from_index INT; - _to_index INT; - _task_id UUID; - _project_id UUID; - _from_group UUID; - _to_group UUID; - _group_by TEXT; -BEGIN - _project_id = (_body ->> 'project_id')::UUID; - _task_id = (_body ->> 'task_id')::UUID; - - _from_index = (_body ->> 'from_index')::INT; -- from sort_order - _to_index = (_body ->> 'to_index')::INT; -- to sort_order - - _from_group = (_body ->> 'from_group')::UUID; - _to_group = (_body ->> 'to_group')::UUID; - - _group_by = (_body ->> 'group_by')::TEXT; - - IF (_from_group <> _to_group OR (_from_group <> _to_group) IS NULL) - THEN - IF (_group_by = 'status') - THEN - UPDATE tasks SET status_id = _to_group WHERE id = _task_id AND status_id = _from_group; - END IF; - - IF (_group_by = 'priority') - THEN - UPDATE tasks SET priority_id = _to_group WHERE id = _task_id AND priority_id = _from_group; - END IF; - - IF (_group_by = 'phase') - THEN - IF (is_null_or_empty(_to_group) IS FALSE) - THEN - INSERT INTO task_phase (task_id, phase_id) - VALUES (_task_id, _to_group) - ON CONFLICT (task_id) DO UPDATE SET phase_id = _to_group; - END IF; - IF (is_null_or_empty(_to_group) IS TRUE) - THEN - DELETE - FROM task_phase - WHERE task_id = _task_id; - END IF; - END IF; - - IF ((_body ->> 'to_last_index')::BOOLEAN IS TRUE AND _from_index < _to_index) - THEN - PERFORM handle_task_list_sort_inside_group(_from_index, _to_index, _task_id, _project_id); - ELSE - PERFORM handle_task_list_sort_between_groups(_from_index, _to_index, _task_id, _project_id); - END IF; - ELSE - PERFORM handle_task_list_sort_inside_group(_from_index, _to_index, _task_id, _project_id); - END IF; -END -$$; - -CREATE OR REPLACE FUNCTION handle_task_name_change(_task_id uuid, _task_name text, _user_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _name TEXT; - _previous_name TEXT; - _user_name TEXT; - _message TEXT; - _assignees JSON; -BEGIN - - IF (is_null_or_empty(_task_name)) - THEN - SELECT name FROM tasks WHERE id = _task_id INTO _name; - ELSE - SELECT name FROM tasks WHERE id = _task_id INTO _previous_name; - UPDATE tasks SET name = _task_name WHERE id = _task_id RETURNING name INTO _name; - END IF; - - IF (_name != _previous_name) - THEN - SELECT get_task_assignees(_task_id) INTO _assignees; - - SELECT name FROM users WHERE id = _user_id INTO _user_name; - - _message = CONCAT(_user_name, ' has renamed "', _previous_name, '" → "', _name, '"'); - END IF; - - RETURN JSON_BUILD_OBJECT( - 'name', _name, - 'previous_name', _previous_name, - 'project_id', (SELECT project_id FROM tasks WHERE id = _task_id), - 'message', _message, - 'members', _assignees - ); -END -$$; - -CREATE OR REPLACE FUNCTION handle_task_roadmap_sort_order(_from_index integer, _to_index integer, _task_id uuid) RETURNS void - LANGUAGE plpgsql -AS -$$ -DECLARE - _project_id uuid; -BEGIN - SELECT project_id INTO _project_id FROM tasks WHERE id = _task_id; - - IF _to_index > _from_index - THEN - UPDATE tasks - SET roadmap_sort_order = roadmap_sort_order - 1 - WHERE project_id = _project_id - AND roadmap_sort_order > _from_index - AND roadmap_sort_order <= _to_index; - END IF; - - IF _to_index < _from_index - THEN - UPDATE tasks - SET roadmap_sort_order = roadmap_sort_order + 1 - WHERE project_id = _project_id - AND roadmap_sort_order >= _to_index - AND roadmap_sort_order < _from_index; - END IF; - - UPDATE tasks SET roadmap_sort_order = _to_index WHERE id = _task_id AND project_id = _project_id; -END -$$; - -CREATE OR REPLACE FUNCTION home_task_form_view_model(_user_id uuid, _team_id uuid, _task_id uuid, _project_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _task JSON; -BEGIN - - -- Select task info - SELECT COALESCE(ROW_TO_JSON(rec), '{}'::JSON) - INTO _task - FROM (SELECT id, - name, - start_date, - end_date, - done, - priority_id, - project_id, - created_at, - updated_at, - status_id, - parent_task_id, - parent_task_id IS NOT NULL AS is_sub_task, - (SELECT COUNT(*) FROM tasks WHERE parent_task_id = _task_id) AS sub_tasks_count, - (SELECT name FROM users WHERE id = tasks.reporter_id) AS reporter, - (SELECT get_task_assignees(tasks.id)) AS assignees, - (SELECT id FROM team_members WHERE user_id = _user_id AND team_id = _team_id) AS team_member_id - FROM tasks - WHERE id = _task_id) rec; - - RETURN JSON_BUILD_OBJECT( - 'task', _task - ); -END; -$$; - -CREATE OR REPLACE FUNCTION import_tasks_from_template(_project_id uuid, _user_id uuid, _tasks json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _task JSON; - _max_sort INT; -BEGIN - - SELECT COALESCE((SELECT MAX(sort_order) FROM tasks WHERE project_id = _project_id), 0) INTO _max_sort; - - -- insert tasks for task templates - FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS(_tasks) - LOOP - _max_sort = _max_sort + 1; - INSERT INTO tasks (name, priority_id, project_id, reporter_id, status_id, sort_order, total_minutes) - VALUES (TRIM((_task ->> 'name')::TEXT), - (SELECT id FROM task_priorities WHERE value = 1), - _project_id, - _user_id, - - -- This should be came from client side later - (SELECT id - FROM task_statuses - WHERE project_id = _project_id::UUID - AND category_id IN (SELECT id FROM sys_task_status_categories WHERE is_todo IS TRUE) - LIMIT 1), _max_sort, (_task ->> 'total_minutes')::NUMERIC); - END LOOP; - - RETURN JSON_BUILD_OBJECT('id', _project_id); -END; -$$; - -CREATE OR REPLACE FUNCTION in_organization(_team_id_in uuid, _team_id uuid) RETURNS boolean - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - RETURN (SELECT _team_id_in IN (SELECT id - FROM teams - WHERE user_id = (SELECT user_id FROM teams WHERE id = _team_id))); -END -$$; - -CREATE OR REPLACE FUNCTION insert_job_title(_job_title text, _team_id uuid) RETURNS uuid - LANGUAGE plpgsql -AS -$$ -DECLARE - _job_title_id UUID; -BEGIN - IF EXISTS(SELECT name FROM job_titles WHERE name = _job_title AND team_id = _team_id) - THEN - SELECT id FROM job_titles WHERE name = _job_title AND team_id = _team_id INTO _job_title_id; - ELSE - INSERT INTO job_titles (name, team_id) - VALUES (_job_title, _team_id) - RETURNING id INTO _job_title_id; - END IF; - - RETURN _job_title_id; -END; -$$; - -CREATE OR REPLACE FUNCTION insert_task_list_columns(_project_id uuid) RETURNS void - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) - VALUES (_project_id, 'Key', 'KEY', 0, FALSE); - INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) - VALUES (_project_id, 'Description', 'DESCRIPTION', 2, FALSE); - INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) - VALUES (_project_id, 'Progress', 'PROGRESS', 3, TRUE); - INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) - VALUES (_project_id, 'Members', 'ASSIGNEES', 4, TRUE); - INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) - VALUES (_project_id, 'Labels', 'LABELS', 5, TRUE); - INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) - VALUES (_project_id, 'Status', 'STATUS', 6, TRUE); - INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) - VALUES (_project_id, 'Priority', 'PRIORITY', 7, TRUE); - INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) - VALUES (_project_id, 'Time Tracking', 'TIME_TRACKING', 8, TRUE); - INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) - VALUES (_project_id, 'Estimation', 'ESTIMATION', 9, FALSE); - INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) - VALUES (_project_id, 'Start Date', 'START_DATE', 10, FALSE); - INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) - VALUES (_project_id, 'Due Date', 'DUE_DATE', 11, TRUE); - INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) - VALUES (_project_id, 'Completed Date', 'COMPLETED_DATE', 12, FALSE); - INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) - VALUES (_project_id, 'Created Date', 'CREATED_DATE', 13, FALSE); - INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) - VALUES (_project_id, 'Last Updated', 'LAST_UPDATED', 14, FALSE); - INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) - VALUES (_project_id, 'Reporter', 'REPORTER', 15, FALSE); - INSERT INTO project_task_list_cols (project_id, name, key, index, pinned) - VALUES (_project_id, 'Phase', 'PHASE', 16, FALSE); -END -$$; - -CREATE OR REPLACE FUNCTION is_admin(_user_id uuid, _team_id uuid) RETURNS boolean - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - RETURN EXISTS(SELECT 1 - FROM team_members - WHERE team_id = _team_id - AND user_id = _user_id - AND role_id = (SELECT id - FROM roles - WHERE id = team_members.role_id - AND admin_role IS TRUE)); -END -$$; - -CREATE OR REPLACE FUNCTION is_completed(_status_id uuid, _project_id uuid) RETURNS boolean - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - RETURN (SELECT _status_id IN (SELECT id - FROM task_statuses - WHERE project_id = _project_id - AND category_id = - (SELECT id FROM sys_task_status_categories WHERE is_done IS TRUE))); -END -$$; - -CREATE OR REPLACE FUNCTION is_doing(_status_id uuid, _project_id uuid) RETURNS boolean - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - RETURN (SELECT _status_id IN (SELECT id - FROM task_statuses - WHERE project_id = _project_id - AND category_id = - (SELECT id FROM sys_task_status_categories WHERE is_doing IS TRUE))); -END -$$; - -CREATE OR REPLACE FUNCTION is_member_of_project(_project_id uuid, _user_id uuid, _team_id uuid) RETURNS boolean - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - RETURN EXISTS(SELECT 1 - FROM project_members - WHERE project_id = _project_id - AND team_member_id = (SELECT id FROM team_members WHERE team_id = _team_id AND user_id = _user_id)); -END -$$; - -CREATE OR REPLACE FUNCTION is_null_or_empty(_value anyelement) RETURNS boolean - LANGUAGE plpgsql -AS -$$ -BEGIN - RETURN COALESCE(TRIM(_value::TEXT), '') = ''; -END; -$$; - -CREATE OR REPLACE FUNCTION is_owner(_user_id uuid, _team_id uuid) RETURNS boolean - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - RETURN EXISTS(SELECT 1 - FROM teams - WHERE teams.user_id = _user_id - AND teams.id = _team_id); -END -$$; - -CREATE OR REPLACE FUNCTION is_todo(_status_id uuid, _project_id uuid) RETURNS boolean - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - RETURN (SELECT _status_id IN (SELECT id - FROM task_statuses - WHERE project_id = _project_id - AND category_id = - (SELECT id FROM sys_task_status_categories WHERE is_todo IS TRUE))); -END -$$; - -CREATE OR REPLACE FUNCTION lower_email() RETURNS trigger - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - - IF (is_null_or_empty(NEW.email) IS FALSE) - THEN - NEW.email = LOWER(TRIM(NEW.email)); - END IF; - - RETURN NEW; -END -$$; - -CREATE OR REPLACE FUNCTION migrate_member_allocations(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _member JSON; - _output JSON; -BEGIN - FOR _member IN SELECT * FROM JSON_ARRAY_ELEMENTS(_body::JSON) - LOOP - INSERT INTO project_member_allocations(project_id, team_member_id, allocated_from, allocated_to) - VALUES ((_member ->> 'project_id')::UUID, (_member ->> 'team_member_id')::UUID, - (_member ->> 'allocated_from')::DATE, - (_member ->> 'allocated_to')::DATE); - END LOOP; - RETURN _output; -END; -$$; - -CREATE OR REPLACE FUNCTION move_tasks_and_delete_status(_body json) RETURNS void - LANGUAGE plpgsql -AS -$$ -DECLARE - _status_id UUID; - _category_id UUID; -BEGIN - _status_id = (_body ->> 'id')::UUID; - SELECT category_id FROM task_statuses WHERE id = _status_id INTO _category_id; - - IF (is_null_or_empty(_body ->> 'replacing_status') IS FALSE) - THEN - UPDATE tasks - SET status_id = (_body ->> 'replacing_status')::UUID - WHERE project_id = (_body ->> 'project_id')::UUID - AND status_id = _status_id; - END IF; - - IF ((SELECT COUNT(*) - FROM task_statuses - WHERE category_id = _category_id - AND project_id = (_body ->> 'project_id')::UUID) < 2) - THEN - RAISE 'ERROR_ONE_SHOULD_EXISTS'; - END IF; - - DELETE FROM task_statuses WHERE id = _status_id AND project_id = (_body ->> 'project_id')::UUID; -END -$$; - -CREATE OR REPLACE FUNCTION notification_settings_delete_trigger_fn() RETURNS trigger - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - DELETE FROM notification_settings WHERE user_id = OLD.user_id AND team_id = OLD.team_id; - RETURN OLD; -END -$$; - -CREATE OR REPLACE FUNCTION notification_settings_insert_trigger_fn() RETURNS trigger - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - - IF (NOT EXISTS(SELECT 1 FROM notification_settings WHERE team_id = NEW.team_id AND user_id = NEW.user_id)) AND - (is_null_or_empty(NEW.user_id) IS FALSE) AND (EXISTS(SELECT 1 FROM users WHERE id = NEW.user_id)) - THEN - INSERT INTO notification_settings (popup_notifications_enabled, show_unread_items_count, user_id, - team_id) - VALUES (TRUE, TRUE, NEW.user_id, NEW.team_id); - END IF; - - RETURN NEW; -END -$$; - -CREATE OR REPLACE FUNCTION notify_task_assignment_update(_type text, _reporter_id uuid, _task_id uuid, _user_id uuid, _team_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _reporter_name TEXT; - _task_name TEXT; - _message TEXT; - _team_id UUID; - _project_id UUID; -BEGIN - IF (is_null_or_empty(_task_id) IS FALSE) - THEN - - SELECT project_id FROM tasks WHERE id = _task_id INTO _project_id; - SELECT team_id FROM projects WHERE id = _project_id INTO _team_id; - - INSERT INTO task_updates (type, reporter_id, task_id, user_id, team_id, project_id) - VALUES (_type, _reporter_id, _task_id, _user_id, _team_id, (SELECT project_id FROM tasks WHERE id = _task_id)); - - SELECT name FROM users WHERE id = _reporter_id INTO _reporter_name; - SELECT name FROM tasks WHERE id = _task_id INTO _task_name; - - IF (_type = 'ASSIGN') - THEN - _message = CONCAT('', _reporter_name, ' has assigned you in ', _task_name, ''); - ELSE - _message = CONCAT('', _reporter_name, ' has removed you from ', _task_name, ''); - END IF; - - PERFORM create_notification(_user_id, _team_id, _task_id, _project_id, _message); - - RETURN JSON_BUILD_OBJECT( - 'receiver_socket_id', (SELECT socket_id FROM users WHERE id = _user_id), - 'team', (SELECT name FROM teams WHERE id = _team_id), - 'team_id', _team_id, - 'project', (SELECT name FROM projects WHERE id = _project_id), - 'project_color', (SELECT color_code FROM projects WHERE id = _project_id), - 'project_id', _project_id, - 'task_id', _task_id, - 'message', _message - ); - - END IF; - - RETURN NULL; -END -$$; - -CREATE OR REPLACE FUNCTION register_google_user(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _user_id UUID; - _organization_id UUID; - _team_id UUID; - _role_id UUID; - - _name TEXT; - _email TEXT; - _google_id TEXT; -BEGIN - _name = (_body ->> 'displayName')::TEXT; - _email = (_body ->> 'email')::TEXT; - _google_id = (_body ->> 'id'); - - INSERT INTO users (name, email, google_id, timezone_id) - VALUES (_name, _email, _google_id, COALESCE((SELECT id FROM timezones WHERE name = (_body ->> 'timezone')), - (SELECT id FROM timezones WHERE name = 'UTC'))) - RETURNING id INTO _user_id; - - --insert organization data - INSERT INTO organizations (user_id, organization_name, contact_number, contact_number_secondary, trial_in_progress, - trial_expire_date, subscription_status) - VALUES (_user_id, TRIM((_body ->> 'displayName')::TEXT), NULL, NULL, TRUE, CURRENT_DATE + INTERVAL '14 days', - 'trialing') - RETURNING id INTO _organization_id; - - INSERT INTO teams (name, user_id, organization_id) - VALUES (_name, _user_id, _organization_id) - RETURNING id INTO _team_id; - - -- insert default roles - INSERT INTO roles (name, team_id, default_role) VALUES ('Member', _team_id, TRUE); - INSERT INTO roles (name, team_id, admin_role) VALUES ('Admin', _team_id, TRUE); - INSERT INTO roles (name, team_id, owner) VALUES ('Owner', _team_id, TRUE) RETURNING id INTO _role_id; - - INSERT INTO team_members (user_id, team_id, role_id) - VALUES (_user_id, _team_id, _role_id); - - IF (is_null_or_empty(_body ->> 'team') OR is_null_or_empty(_body ->> 'member_id')) - THEN - UPDATE users SET active_team = _team_id WHERE id = _user_id; - ELSE - -- Verify team member - IF EXISTS(SELECT id - FROM team_members - WHERE id = (_body ->> 'member_id')::UUID - AND team_id = (_body ->> 'team')::UUID) - THEN - UPDATE team_members - SET user_id = _user_id - WHERE id = (_body ->> 'member_id')::UUID - AND team_id = (_body ->> 'team')::UUID; - - DELETE - FROM email_invitations - WHERE team_id = (_body ->> 'team')::UUID - AND team_member_id = (_body ->> 'member_id')::UUID; - - UPDATE users SET active_team = (_body ->> 'team')::UUID WHERE id = _user_id; - END IF; - END IF; - - RETURN JSON_BUILD_OBJECT( - 'id', _user_id, - 'email', _email, - 'google_id', _google_id - ); -END -$$; - -CREATE OR REPLACE FUNCTION register_user(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _user_id UUID; - _organization_id UUID; - _team_id UUID; - _role_id UUID; - _trimmed_email TEXT; - _trimmed_name TEXT; - _trimmed_team_name TEXT; -BEGIN - - _trimmed_email = LOWER(TRIM((_body ->> 'email'))); - _trimmed_name = TRIM((_body ->> 'name')); - _trimmed_team_name = TRIM((_body ->> 'team_name')); - - -- check user exists - IF EXISTS(SELECT email FROM users WHERE email = _trimmed_email) - THEN - RAISE 'EMAIL_EXISTS_ERROR:%', (_body ->> 'email'); - END IF; - - -- insert user - INSERT INTO users (name, email, password, timezone_id) - VALUES (_trimmed_name, _trimmed_email, (_body ->> 'password'), - COALESCE((SELECT id FROM timezones WHERE name = (_body ->> 'timezone')), - (SELECT id FROM timezones WHERE name = 'UTC'))) - RETURNING id INTO _user_id; - - --insert organization data - INSERT INTO organizations (user_id, organization_name, contact_number, contact_number_secondary, trial_in_progress, - trial_expire_date, subscription_status) - VALUES (_user_id, TRIM((_body ->> 'team_name')::TEXT), NULL, NULL, TRUE, CURRENT_DATE + INTERVAL '14 days', - 'trialing') - RETURNING id INTO _organization_id; - - -- insert team - INSERT INTO teams (name, user_id, organization_id) - VALUES (_trimmed_team_name, _user_id, _organization_id) - RETURNING id INTO _team_id; - - IF (is_null_or_empty((_body ->> 'invited_team_id'))) - THEN - UPDATE users SET active_team = _team_id WHERE id = _user_id; - ELSE - IF NOT EXISTS(SELECT id - FROM email_invitations - WHERE team_id = (_body ->> 'invited_team_id')::UUID - AND email = _trimmed_email) - THEN - RAISE 'ERROR_INVALID_JOINING_EMAIL'; - END IF; - UPDATE users SET active_team = (_body ->> 'invited_team_id')::UUID WHERE id = _user_id; - END IF; - - -- insert default roles - INSERT INTO roles (name, team_id, default_role) VALUES ('Member', _team_id, TRUE); - INSERT INTO roles (name, team_id, admin_role) VALUES ('Admin', _team_id, TRUE); - INSERT INTO roles (name, team_id, owner) VALUES ('Owner', _team_id, TRUE) RETURNING id INTO _role_id; - - -- insert team member - INSERT INTO team_members (user_id, team_id, role_id) - VALUES (_user_id, _team_id, _role_id); - - -- update team member table with user id - IF (_body ->> 'team_member_id') IS NOT NULL - THEN - UPDATE team_members SET user_id = (_user_id)::UUID WHERE id = (_body ->> 'team_member_id')::UUID; - DELETE - FROM email_invitations - WHERE email = _trimmed_email - AND team_member_id = (_body ->> 'team_member_id')::UUID; - END IF; - - RETURN JSON_BUILD_OBJECT( - 'id', _user_id, - 'name', _trimmed_name, - 'email', _trimmed_email, - 'team_id', _team_id - ); -END; -$$; - -CREATE OR REPLACE FUNCTION remove_project_member(_id uuid, _user_id uuid, _team_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _team_member_id UUID; - _project_id UUID; - _member_user_id UUID; - _notification TEXT; -BEGIN - SELECT project_id FROM project_members WHERE id = _id INTO _project_id; - SELECT team_member_id FROM project_members WHERE id = _id INTO _team_member_id; - SELECT user_id FROM team_members WHERE id = _team_member_id INTO _member_user_id; - DELETE FROM project_members WHERE id = _id; - DELETE FROM project_member_allocations WHERE project_id = _project_id AND team_member_id = _team_member_id; - - IF (_member_user_id != _user_id) - THEN - _notification = - CONCAT('You have been removed from the ', (SELECT name FROM projects WHERE id = _project_id), - ' by ', - (SELECT name FROM users WHERE id = _user_id), ''); - PERFORM create_notification( - (SELECT user_id FROM team_members WHERE id = _team_member_id), - _team_id, - NULL, - _project_id, - _notification - ); - END IF; - - RETURN JSON_BUILD_OBJECT( - 'id', _id, - 'notification', _notification, - 'socket_id', (SELECT socket_id FROM users WHERE id = _member_user_id), - 'project', (SELECT name FROM projects WHERE id = _project_id), - 'project_id', _project_id, - 'project_color', (SELECT color_code FROM projects WHERE id = _project_id), - 'team', (SELECT name FROM teams WHERE id = _team_id), - 'member_user_id', _member_user_id - ); -END; -$$; - -CREATE OR REPLACE FUNCTION remove_task_assignee(_task_id uuid, _team_member_id uuid, _project_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _team_id UUID; - _user_id UUID; -BEGIN - - SELECT team_id FROM team_members WHERE id = _team_member_id INTO _team_id; - SELECT user_id FROM team_members WHERE id = _team_member_id INTO _user_id; - - DELETE - FROM tasks_assignees - WHERE task_id = _task_id - AND project_member_id = - (SELECT id FROM project_members WHERE team_member_id = _team_member_id AND project_id = _project_id); - - RETURN JSON_BUILD_OBJECT( - 'user_id', _user_id, - 'team_id', _team_id - ); -END -$$; - -CREATE OR REPLACE FUNCTION remove_team_member(_id uuid, _user_id uuid, _team_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _removed_user_id UUID; - _removed_team_name TEXT; -BEGIN - SELECT user_id FROM team_members WHERE id = _id INTO _removed_user_id; - SELECT name FROM teams WHERE id = _team_id INTO _removed_team_name; - - UPDATE users - SET active_team = (SELECT id FROM teams WHERE user_id = _removed_user_id LIMIT 1) - WHERE active_team = _team_id - AND id = _removed_user_id; - - PERFORM create_notification( - _removed_user_id, - _team_id, - NULL, - NULL, - CONCAT('You have been removed from ', (SELECT name FROM teams WHERE id = _team_id), ' by ', - (SELECT name FROM users WHERE id = _user_id), '') - ); - - DELETE FROM team_members WHERE id = _id AND team_id = _team_id; - - RETURN JSON_BUILD_OBJECT( - 'id', _removed_user_id, - 'team', _removed_team_name, - 'socket_id', (SELECT socket_id FROM users WHERE id = _user_id) - ); -END; -$$; - -CREATE OR REPLACE FUNCTION resend_team_invitation(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _id UUID; - _team_id UUID; - _user_id UUID; - _email TEXT; - _output JSON; -BEGIN - _team_id = (_body ->> 'team_id')::UUID; - _id = (_body ->> 'id')::UUID; - - SELECT email FROM email_invitations WHERE team_member_id = _id AND team_id = _team_id INTO _email; - SELECT id FROM users WHERE email = _email INTO _user_id; - - IF is_null_or_empty(_email) IS FALSE - THEN - DELETE FROM email_invitations WHERE team_id = _team_id AND team_member_id = _id; - END IF; - - INSERT INTO email_invitations(team_id, team_member_id, email, name) - VALUES (_team_id, _id, _email, SPLIT_PART(_email, '@', 1)); - - SELECT JSON_BUILD_OBJECT( - 'name', (SELECT name FROM users WHERE id = _user_id), - 'email', _email, - 'is_new', is_null_or_empty(_user_id), - 'team_member_id', _id, - 'team_member_user_id', _user_id - ) - INTO _output; - - RETURN _output; -END; -$$; - -CREATE OR REPLACE FUNCTION set_active_team(_user_id uuid, _team_id uuid) RETURNS void - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - IF EXISTS(SELECT id FROM team_members WHERE team_id = _team_id AND user_id = _user_id) - THEN - UPDATE users SET active_team = _team_id WHERE id = _user_id; - ELSE - UPDATE users SET active_team = (SELECT id FROM teams WHERE user_id = users.id LIMIT 1) WHERE id = _user_id; - END IF; -END; -$$; - -CREATE OR REPLACE FUNCTION set_active_team_by_member_id(_team_member_id uuid) RETURNS void - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - PERFORM set_active_team( - (SELECT user_id FROM team_members WHERE id = _team_member_id), - (SELECT team_id FROM team_members WHERE id = _team_member_id) - ); -END; -$$; - -CREATE OR REPLACE FUNCTION set_task_updated_at_trigger_fn() RETURNS trigger - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - NEW.updated_at = CURRENT_TIMESTAMP; - RETURN NEW; -END -$$; - -CREATE OR REPLACE FUNCTION slugify(value text) RETURNS text - IMMUTABLE - STRICT - LANGUAGE sql -AS -$$ - -- removes accents (diacritic signs) from a given string -- -WITH "unaccented" AS (SELECT unaccent("value") AS "value"), - -- lowercase the string - "lowercase" AS (SELECT LOWER("value") AS "value" - FROM "unaccented"), - -- replaces anything that's not a letter, number, hyphen('-'), or underscore('_') with a hyphen('-') - "hyphenated" AS (SELECT REGEXP_REPLACE("value", '[^a-z0-9\\-_.''`"]+', '-', 'gi') AS "value" - FROM "lowercase"), - -- trims hyphens('-') if they exist on the head or tail of the string - "trimmed" AS (SELECT REGEXP_REPLACE(REGEXP_REPLACE("value", '\\-+$', ''), '^\\-', '') AS "value" - FROM "hyphenated") -SELECT "value" -FROM "trimmed"; -$$; - -CREATE OR REPLACE FUNCTION sys_insert_project_healths() RETURNS void - LANGUAGE plpgsql -AS -$$ -BEGIN - INSERT INTO sys_project_healths (name, color_code, sort_order, is_default) - VALUES ('Not Set', '#a9a9a9', 0, TRUE); - INSERT INTO sys_project_healths (name, color_code, sort_order, is_default) - VALUES ('Needs Attention', '#fbc84c', 1, FALSE); - INSERT INTO sys_project_healths (name, color_code, sort_order, is_default) - VALUES ('At Risk', '#f37070', 2, FALSE); - INSERT INTO sys_project_healths (name, color_code, sort_order, is_default) - VALUES ('Good', '#75c997', 3, FALSE); -END; -$$; - -CREATE OR REPLACE FUNCTION task_status_change_trigger_fn() RETURNS trigger - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - IF ((SELECT is_done - FROM sys_task_status_categories - WHERE id = (SELECT category_id FROM task_statuses WHERE id = NEW.status_id)) IS TRUE) - THEN - UPDATE tasks SET completed_at = CURRENT_TIMESTAMP WHERE id = NEW.id; - ELSE - UPDATE tasks SET completed_at = NULL WHERE id = NEW.id; - END IF; - - RETURN NEW; -END -$$; - -CREATE OR REPLACE FUNCTION tasks_task_subscriber_notify_done_trigger() RETURNS trigger - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - IF (EXISTS(SELECT 1 - FROM sys_task_status_categories - WHERE id = (SELECT category_id FROM task_statuses WHERE id = NEW.status_id) - AND is_done IS TRUE)) - THEN - PERFORM pg_notify('db_task_status_changed', NEW.id::TEXT); - END IF; - - RETURN NEW; -END -$$; - -CREATE OR REPLACE FUNCTION to_seconds(t text) RETURNS integer - LANGUAGE plpgsql -AS -$$ -DECLARE - hs INTEGER; - ms INTEGER; - s INTEGER; -BEGIN - SELECT (EXTRACT(HOUR FROM t::TIME) * 60 * 60) INTO hs; - SELECT (EXTRACT(MINUTES FROM t::TIME) * 60) INTO ms; - SELECT (EXTRACT(SECONDS FROM t::TIME)) INTO s; - SELECT (hs + ms + s) INTO s; - RETURN s; -END; -$$; - -CREATE OR REPLACE FUNCTION toggle_archive_all_projects(_project_id uuid) RETURNS void - LANGUAGE plpgsql -AS -$$ -DECLARE - _user_id UUID; - -BEGIN - IF EXISTS(SELECT project_id FROM archived_projects WHERE project_id = _project_id) - THEN - DELETE FROM archived_projects WHERE project_id = _project_id; - ELSE - FOR _user_id IN - SELECT user_id FROM team_members WHERE team_id = (SELECT team_id FROM projects WHERE id = _project_id) - LOOP - IF NOT (is_null_or_empty(_user_id)) - THEN - INSERT INTO archived_projects (user_id, project_id) - VALUES (_user_id, _project_id); - END IF; - END LOOP; - END IF; -END -$$; - -CREATE OR REPLACE FUNCTION toggle_archive_project(_user_id uuid, _project_id uuid) RETURNS void - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - IF EXISTS(SELECT user_id FROM archived_projects WHERE user_id = _user_id AND project_id = _project_id) - THEN - DELETE FROM archived_projects WHERE user_id = _user_id AND project_id = _project_id; - ELSE - INSERT INTO archived_projects (user_id, project_id) VALUES (_user_id, _project_id); - END IF; -END -$$; - -CREATE OR REPLACE FUNCTION toggle_favorite_project(_user_id uuid, _project_id uuid) RETURNS void - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - IF EXISTS(SELECT user_id FROM favorite_projects WHERE user_id = _user_id AND project_id = _project_id) - THEN - DELETE FROM favorite_projects WHERE user_id = _user_id AND project_id = _project_id; - ELSE - INSERT INTO favorite_projects (user_id, project_id) VALUES (_user_id, _project_id); - END IF; -END -$$; - -CREATE OR REPLACE FUNCTION update_project(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _user_id UUID; - _team_id UUID; - _client_id UUID; - _project_id UUID; - _project_manager_team_member_id UUID; - _client_name TEXT; - _project_name TEXT; -BEGIN - -- need a test, can be throw errors - _client_name = TRIM((_body ->> 'client_name')::TEXT); - _project_name = TRIM((_body ->> 'name')::TEXT); - - -- add inside the controller - _user_id = (_body ->> 'user_id')::UUID; - _team_id = (_body ->> 'team_id')::UUID; - _project_manager_team_member_id = (_body ->> 'team_member_id')::UUID; - - -- cache exists client if exists - SELECT id FROM clients WHERE LOWER(name) = LOWER(_client_name) AND team_id = _team_id INTO _client_id; - - -- insert client if not exists - IF is_null_or_empty(_client_id) IS TRUE AND is_null_or_empty(_client_name) IS FALSE - THEN - INSERT INTO clients (name, team_id) VALUES (_client_name, _team_id) RETURNING id INTO _client_id; - END IF; - - -- check whether the project name is already in - IF EXISTS( - SELECT name FROM projects WHERE LOWER(name) = LOWER(_project_name) - AND team_id = _team_id AND id != (_body ->> 'id')::UUID - ) - THEN - RAISE 'PROJECT_EXISTS_ERROR:%', _project_name; - END IF; - - -- update the project - UPDATE projects - SET name = _project_name, - notes = (_body ->> 'notes')::TEXT, - color_code = (_body ->> 'color_code')::TEXT, - status_id = (_body ->> 'status_id')::UUID, - health_id = (_body ->> 'health_id')::UUID, - key = (_body ->> 'key')::TEXT, - start_date = (_body ->> 'start_date')::TIMESTAMPTZ, - end_date = (_body ->> 'end_date')::TIMESTAMPTZ, - client_id = _client_id, - folder_id = (_body ->> 'folder_id')::UUID, - category_id = (_body ->> 'category_id')::UUID, - updated_at = CURRENT_TIMESTAMP, - estimated_working_days = (_body ->> 'working_days')::INTEGER, - estimated_man_days = (_body ->> 'man_days')::INTEGER, - hours_per_day = (_body ->> 'hours_per_day')::INTEGER - WHERE id = (_body ->> 'id')::UUID - AND team_id = _team_id - RETURNING id INTO _project_id; - - UPDATE project_members SET project_access_level_id = (SELECT id FROM project_access_levels WHERE key = 'MEMBER') WHERE project_id = _project_id; - - IF NOT (_project_manager_team_member_id IS NULL) - THEN - PERFORM update_project_manager(_project_manager_team_member_id, _project_id::UUID); - END IF; - - RETURN JSON_BUILD_OBJECT( - 'id', _project_id, - 'name', (_body ->> 'name')::TEXT, - 'project_manager_id', _project_manager_team_member_id::UUID - ); -END; -$$; - -CREATE OR REPLACE FUNCTION update_project_manager(_team_member_id uuid, _project_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _project_member_id UUID; - _team_id UUID; - _user_id UUID; - _project_member JSON; -BEGIN - - SELECT id - FROM project_members - WHERE team_member_id = _team_member_id - AND project_id = _project_id - INTO _project_member_id; - - SELECT team_id FROM team_members WHERE id = _team_member_id INTO _team_id; - SELECT user_id FROM team_members WHERE id = _team_member_id INTO _user_id; - - IF is_null_or_empty(_project_member_id) - THEN - SELECT create_project_member(JSON_BUILD_OBJECT( - 'team_member_id', _team_member_id, - 'team_id', _team_id, - 'project_id', _project_id, - 'user_id', _user_id, - 'access_level', 'PROJECT_MANAGER'::TEXT - )) - INTO _project_member; - END IF; - - UPDATE project_members SET project_access_level_id = (SELECT id FROM project_access_levels WHERE key = 'PROJECT_MANAGER') WHERE id = _project_member_id AND project_id = _project_id; - - RETURN JSON_BUILD_OBJECT( - 'project_member_id', _project_member_id, - 'team_member_id', _team_member_id, - 'team_id', _team_id, - 'user_id', _user_id - ); -END -$$; - -CREATE OR REPLACE FUNCTION update_project_tasks_counter_trigger_fn() RETURNS trigger - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - - UPDATE projects SET tasks_counter = (tasks_counter + 1) WHERE id = NEW.project_id; - NEW.task_no = (SELECT tasks_counter FROM projects WHERE id = NEW.project_id); - - RETURN NEW; -END -$$; - -CREATE OR REPLACE FUNCTION update_status_order(_status_ids json) RETURNS void - LANGUAGE plpgsql -AS -$$ -DECLARE - _iterator NUMERIC := 0; - _status_id TEXT; -BEGIN - - FOR _status_id IN SELECT * FROM JSON_ARRAY_ELEMENTS((_status_ids)::JSON) - LOOP - UPDATE task_statuses - SET sort_order = _iterator - WHERE id = (SELECT TRIM(BOTH '"' FROM _status_id))::UUID; - _iterator := _iterator + 1; - END LOOP; - - RETURN; -END -$$; - -CREATE OR REPLACE FUNCTION update_task(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _assignee TEXT; - _assignee_id UUID; - _label JSON; - _old_assignees JSON; - _new_assignees JSON; -BEGIN - UPDATE tasks - SET name = TRIM((_body ->> 'name')::TEXT), - start_date = (_body ->> 'start')::TIMESTAMPTZ, - end_date = (_body ->> 'end')::TIMESTAMPTZ, - priority_id = (_body ->> 'priority_id')::UUID, - description = COALESCE(TRIM((_body ->> 'description')::TEXT), description), - total_minutes = (_body ->> 'total_minutes')::NUMERIC, - status_id = (_body ->> 'status_id')::UUID - WHERE id = (_body ->> 'id')::UUID; - - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _old_assignees - FROM ( - -- - SELECT team_member_id, - (SELECT user_id FROM team_members WHERE id = tasks_assignees.team_member_id), - (SELECT team_id FROM team_members WHERE id = tasks_assignees.team_member_id) - FROM tasks_assignees - WHERE task_id = (_body ->> 'id')::UUID - -- - ) rec; - - -- delete existing task assignees - DELETE FROM tasks_assignees WHERE task_id = (_body ->> 'id')::UUID; - - -- insert task assignees - FOR _assignee IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'assignees')::JSON) - LOOP - _assignee_id = TRIM('"' FROM _assignee)::UUID; - PERFORM create_task_assignee(_assignee_id, (_body ->> 'project_id')::UUID, (_body ->> 'id')::UUID, - (_body ->> 'reporter_id')::UUID); - END LOOP; - - IF ((_body ->> 'inline')::BOOLEAN IS FALSE) - THEN - DELETE FROM task_labels WHERE task_id = (_body ->> 'id')::UUID; - FOR _label IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'labels')::JSON) - LOOP - PERFORM assign_or_create_label((_body ->> 'team_id')::UUID, (_body ->> 'id')::UUID, - (_label ->> 'name')::TEXT, - (_label ->> 'color')::TEXT); - END LOOP; - END IF; - - - SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - INTO _new_assignees - FROM ( - -- - SELECT team_member_id, - (SELECT user_id FROM team_members WHERE id = tasks_assignees.team_member_id), - (SELECT team_id FROM team_members WHERE id = tasks_assignees.team_member_id) - FROM tasks_assignees - WHERE task_id = (_body ->> 'id')::UUID - -- - ) rec; - - RETURN JSON_BUILD_OBJECT( - 'id', (_body ->> 'id')::UUID, - 'name', (_body ->> 'name')::TEXT, - 'old_assignees', _old_assignees, - 'new_assignees', _new_assignees - ); -END; -$$; - -CREATE OR REPLACE FUNCTION update_task_status(_updated_task_id uuid, _status_id uuid, _task_ids json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _iterator NUMERIC := 0; - _task_id TEXT; -BEGIN - UPDATE tasks SET status_id = _status_id WHERE id = _updated_task_id; - - FOR _task_id IN SELECT * FROM JSON_ARRAY_ELEMENTS((_task_ids)::JSON) - LOOP - UPDATE tasks - SET sort_order = _iterator - WHERE id = (SELECT TRIM(BOTH '"' FROM _task_id))::UUID; - _iterator := _iterator + 1; - END LOOP; - - RETURN JSON_BUILD_OBJECT( - 'id', _updated_task_id - ); -END; -$$; - -CREATE OR REPLACE FUNCTION update_task_status(_task_id uuid, _project_id uuid, _status_id uuid, _from_index integer, _to_index integer) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE -BEGIN - UPDATE tasks SET status_id = _status_id WHERE id = _task_id AND project_id = _project_id; - - IF (_from_index != _to_index) - THEN - IF _to_index > _from_index - THEN - UPDATE tasks - SET sort_order = sort_order - 1 - WHERE project_id = _project_id - AND sort_order > _from_index - AND sort_order <= _to_index; - END IF; - - IF _to_index < _from_index - THEN - UPDATE tasks - SET sort_order = sort_order + 1 - WHERE project_id = _project_id - AND sort_order >= _to_index - AND sort_order < _from_index; - END IF; - - UPDATE tasks SET sort_order = _to_index WHERE id = _task_id AND project_id = _project_id; - END IF; - - RETURN JSON_BUILD_OBJECT( - 'id', _task_id - ); -END; -$$; - -CREATE OR REPLACE FUNCTION update_task_template(_id uuid, _name text, _tasks json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _task JSON; - -BEGIN - UPDATE task_templates SET name = _name, updated_at = NOW() WHERE id = _id; - - -- delete all existing tasks for the selected template - DELETE FROM task_templates_tasks WHERE template_id = _id; - - -- insert tasks for task templates - FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS(_tasks) - LOOP - INSERT INTO task_templates_tasks (template_id, name) VALUES (_id, (_task ->> 'name')::TEXT); - END LOOP; - - RETURN JSON_BUILD_OBJECT( - 'id', _id, - 'template_name', _name - ); -END -$$; - -CREATE OR REPLACE FUNCTION update_team_member(_body json) RETURNS void - LANGUAGE plpgsql -AS -$$ -DECLARE - _team_id UUID; - _job_title_id UUID; - _role_id UUID; -BEGIN - _team_id = (_body ->> 'team_id')::UUID; - - IF ((_body ->> 'is_admin')::BOOLEAN IS TRUE) - THEN - SELECT id FROM roles WHERE admin_role IS TRUE INTO _role_id; - ELSE - SELECT id FROM roles WHERE default_role IS TRUE INTO _role_id; - END IF; - - IF is_null_or_empty((_body ->> 'job_title')) IS FALSE - THEN - SELECT insert_job_title((_body ->> 'job_title')::TEXT, _team_id) INTO _job_title_id; - ELSE - _job_title_id = NULL; - END IF; - - UPDATE team_members - SET job_title_id = _job_title_id, - role_id = _role_id, - updated_at = CURRENT_TIMESTAMP - WHERE id = (_body ->> 'id')::UUID - AND team_id = _team_id; -END; -$$; - -CREATE OR REPLACE FUNCTION update_team_name(_body json) RETURNS void - LANGUAGE plpgsql -AS -$$ -DECLARE - _name TEXT; -BEGIN - _name = (_body ->> 'name')::TEXT; - - IF ((SELECT name FROM teams WHERE id = (_body ->> 'id')::UUID) != _name) - THEN - UPDATE teams - SET name = _name, - updated_at = CURRENT_TIMESTAMP - WHERE id = (_body ->> 'id')::UUID - AND user_id = (_body ->> 'user_id')::UUID; - END IF; -END; -$$; - -CREATE OR REPLACE FUNCTION update_existing_phase_colors(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - result JSON; - _phase JSON; -BEGIN - - FOR _phase IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'phases')::JSON) - LOOP - UPDATE project_phases SET color_code = (_phase ->> 'color_code')::WL_HEX_COLOR WHERE id = (_phase ->> 'id')::UUID; - END LOOP; - - SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))) - FROM (SELECT * FROM project_phases) rec - INTO result; - - RETURN result; -END; -$$; - -CREATE OR REPLACE FUNCTION update_existing_phase_sort_order(_body json) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - result JSON; - _phase JSON; -BEGIN - - FOR _phase IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'phases')::JSON) - LOOP - UPDATE project_phases SET sort_index = (_phase ->> 'sort_number')::INT WHERE id = (_phase ->> 'id')::UUID AND project_id = (_phase ->> 'project_id')::UUID; - END LOOP; - - SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))) - FROM (SELECT * FROM project_phases) rec - INTO result; - - RETURN result; -END; -$$; - -CREATE OR REPLACE FUNCTION handle_phase_sort_order(_body json) RETURNS void - LANGUAGE plpgsql -AS -$$ -DECLARE - _phase JSON; - _sort_index INT := 0; -BEGIN - - FOR _phase IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'phases')::JSON) - LOOP - UPDATE project_phases SET sort_index = _sort_index::INT WHERE id = (_phase ->> 'id')::UUID AND project_id = (_body ->> 'project_id')::UUID; - _sort_index = _sort_index + 1; - END LOOP; -END -$$; - -CREATE OR REPLACE FUNCTION bulk_change_tasks_status(_body json, _userid uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _task JSON; - _output JSON; - _previous_status UUID; -BEGIN - - FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) - LOOP - _previous_status = (SELECT status_id FROM tasks WHERE id = (_task ->> 'id')::UUID); - - UPDATE tasks SET status_id = (_body ->> 'status_id')::UUID WHERE id = (_task ->> 'id')::UUID; - - IF (_previous_status IS DISTINCT FROM (_body ->> 'status_id')::UUID) - THEN - INSERT INTO task_activity_logs (task_id, team_id, attribute_type, user_id, log_type, old_value, new_value, project_id) - VALUES ( - (_task ->> 'id')::UUID, - (SELECT team_id FROM projects WHERE id = (SELECT project_id FROM tasks WHERE id = (_task ->> 'id')::UUID)), - 'status', - _userId, - 'update', - _previous_status, - (_body ->> 'status_id')::UUID, - (SELECT project_id FROM tasks WHERE id = (_task ->> 'id')::UUID) - ); - END IF; - END LOOP; - RETURN _output; -END -$$; - -CREATE OR REPLACE FUNCTION bulk_change_tasks_priority(_body json, _userid uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _task JSON; - _output JSON; - _previous_priority UUID; -BEGIN - FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) - LOOP - _previous_priority = (SELECT priority_id FROM tasks WHERE id = (_task ->> 'id')::UUID); - - UPDATE tasks SET priority_id = (_body ->> 'priority_id')::UUID WHERE id = (_task ->> 'id')::UUID; - - IF (_previous_priority IS DISTINCT FROM (_body ->> 'priority_id')::UUID) - THEN - INSERT INTO task_activity_logs (task_id, team_id, attribute_type, user_id, log_type, old_value, new_value, project_id) - VALUES ( - (_task ->> 'id')::UUID, - (SELECT team_id FROM projects WHERE id = (SELECT project_id FROM tasks WHERE id = (_task ->> 'id')::UUID)), - 'priority', - _userId, - 'update', - _previous_priority, - (_body ->> 'priority_id')::UUID, - (SELECT project_id FROM tasks WHERE id = (_task ->> 'id')::UUID) - ); - END IF; - END LOOP; - RETURN _output; -END; -$$; - -CREATE OR REPLACE FUNCTION bulk_change_tasks_phase(_body json, _userid uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _task JSON; - _output JSON; - _previous_phase UUID; -BEGIN - FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) - LOOP - _previous_phase = (SELECT phase_id FROM task_phase WHERE task_id = (_task ->> 'id')::UUID); - - IF NOT EXISTS(SELECT 1 FROM task_phase WHERE task_id = (_task ->> 'id')::UUID) - THEN - INSERT INTO task_phase (task_id, phase_id) VALUES ((_task ->> 'id')::UUID, (_body ->> 'phase_id')::UUID); - ELSE - UPDATE task_phase SET phase_id = (_body ->> 'phase_id')::UUID WHERE task_id = (_task ->> 'id')::UUID; - END IF; - - IF (_previous_phase IS DISTINCT FROM (_body ->> 'phase_id')::UUID) - THEN - INSERT INTO task_activity_logs (task_id, team_id, attribute_type, user_id, log_type, old_value, new_value, project_id) - VALUES ( - (_task ->> 'id')::UUID, - (SELECT team_id FROM projects WHERE id = (SELECT project_id FROM tasks WHERE id = (_task ->> 'id')::UUID)), - 'phase', - _userId, - 'update', - _previous_phase, - (_body ->> 'phase_id')::UUID, - (SELECT project_id FROM tasks WHERE id = (_task ->> 'id')::UUID) - ); - END IF; - - - END LOOP; - RETURN _output; -END; -$$; - -CREATE OR REPLACE FUNCTION bulk_assign_label(_body json, _user_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _task JSON; - _label JSON; - _output JSON; -BEGIN - FOR _task IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'tasks')::JSON) - LOOP - FOR _label IN SELECT * FROM JSON_ARRAY_ELEMENTS((_body ->> 'labels')::JSON) - LOOP - - DELETE FROM task_labels WHERE task_id = (_task ->> 'id')::UUID AND label_id = (_label ->> 'id')::UUID; - - INSERT INTO task_labels (task_id, label_id) VALUES ((_task ->> 'id')::UUID, (_label ->> 'id')::UUID); - - INSERT INTO task_activity_logs - (task_id, team_id, attribute_type, user_id, log_type, old_value, new_value, next_string, project_id) - VALUES - ( - (_task ->> 'id')::UUID, - (SELECT team_id FROM projects WHERE id = (SELECT project_id FROM tasks WHERE tasks.id = (_task ->> 'id')::UUID)), - 'label', - _user_id::UUID, - 'create', - NULL, - (_label ->> 'id')::UUID, - NULL, - (SELECT project_id FROM tasks WHERE tasks.id = (_task ->> 'id')::UUID) - ); - - END LOOP; - END LOOP; - - RETURN _output; -END; -$$; - -CREATE OR REPLACE FUNCTION update_phase_name(_phase_id uuid, _phase_name text, _template_id uuid) RETURNS json - LANGUAGE plpgsql -AS -$$ -DECLARE - _color_code TEXT; -BEGIN - IF EXISTS(SELECT name - FROM cpt_phases - WHERE name = _phase_name - AND template_id = _template_id) - THEN - RAISE 'PHASE_EXISTS_ERROR:%', _phase_name::TEXT; - END IF; - UPDATE cpt_phases - SET name = _phase_name - WHERE id = _phase_id - AND template_id = _template_id - RETURNING color_code INTO _color_code; - - RETURN JSON_BUILD_OBJECT( - 'color_code', _color_code, - 'id', _phase_id, - 'name', _phase_name - ); -END -$$; - -CREATE OR REPLACE FUNCTION is_overdue_for_date(_task_id UUID, _end_date DATE) RETURNS BOOL AS -$$ -DECLARE -BEGIN - RETURN EXISTS(SELECT 1 - FROM tasks - WHERE id = _task_id - AND end_date < _end_date - AND is_completed(tasks.status_id, tasks.project_id) IS FALSE); -END -$$ LANGUAGE plpgsql; - - -CREATE OR REPLACE FUNCTION is_overdue(_task_id UUID) RETURNS BOOL AS -$$ -DECLARE -BEGIN - RETURN EXISTS(SELECT 1 - FROM tasks - WHERE id = _task_id - AND end_date < CURRENT_TIMESTAMP - AND is_completed(tasks.status_id, tasks.project_id) IS FALSE); -END -$$ LANGUAGE plpgsql; - - -CREATE OR REPLACE FUNCTION is_completed_between(_task_id UUID, _start_date DATE, _end_date DATE) RETURNS BOOL AS -$$ -DECLARE - BEGIN - RETURN EXISTS ( SELECT 1 FROM tasks WHERE id = _task_id AND completed_at::DATE >= _start_date::DATE AND completed_at::DATE <= _end_date::DATE); -END -$$ LANGUAGE plpgsql; diff --git a/worklenz-backend/database/6_user-permission.sql b/worklenz-backend/database/6_user-permission.sql deleted file mode 100644 index 2d90e111..00000000 --- a/worklenz-backend/database/6_user-permission.sql +++ /dev/null @@ -1,35 +0,0 @@ --- Default ROLE : worklenz_client --- Default USER : worklenz_backend --- Change DATABASE_NAME, ROLE, PASSWORD and USER as needed. - -REVOKE CREATE ON SCHEMA public FROM PUBLIC; -CREATE ROLE worklenz_client; - -GRANT CONNECT ON DATABASE 'DATABASE_NAME' TO worklenz_client; -GRANT INSERT, SELECT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO worklenz_client; - -GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO worklenz_client; - -REVOKE ALL PRIVILEGES ON task_priorities FROM worklenz_client; -GRANT SELECT ON task_priorities TO worklenz_client; - -REVOKE ALL PRIVILEGES ON project_access_levels FROM worklenz_client; -GRANT SELECT ON project_access_levels TO worklenz_client; - -REVOKE ALL PRIVILEGES ON timezones FROM worklenz_client; -GRANT SELECT ON timezones TO worklenz_client; - -REVOKE ALL PRIVILEGES ON worklenz_alerts FROM worklenz_client; -GRANT SELECT ON worklenz_alerts TO worklenz_client; - -REVOKE ALL PRIVILEGES ON sys_task_status_categories FROM worklenz_client; -GRANT SELECT ON sys_task_status_categories TO worklenz_client; - -REVOKE ALL PRIVILEGES ON sys_project_statuses FROM worklenz_client; -GRANT SELECT ON sys_project_statuses TO worklenz_client; - -REVOKE ALL PRIVILEGES ON sys_project_healths FROM worklenz_client; -GRANT SELECT ON sys_project_healths TO worklenz_client; - -CREATE USER worklenz_backend WITH PASSWORD 'PASSWORD'; -GRANT worklenz_client TO worklenz_backend; diff --git a/worklenz-backend/database/indexes.sql b/worklenz-backend/database/indexes.sql new file mode 100644 index 00000000..3c44dd41 --- /dev/null +++ b/worklenz-backend/database/indexes.sql @@ -0,0 +1,115 @@ +-- Indexes +CREATE UNIQUE INDEX IF NOT EXISTS permissions_name_uindex + ON permissions (name); + +CREATE UNIQUE INDEX IF NOT EXISTS bounced_emails_email_uindex + ON bounced_emails (email); + +CREATE INDEX IF NOT EXISTS clients_id_team_id_index + ON clients (id, team_id); + +CREATE UNIQUE INDEX IF NOT EXISTS clients_name_team_id_uindex + ON clients (name, team_id); + +CREATE UNIQUE INDEX IF NOT EXISTS cpt_phases_name_project_uindex + ON cpt_phases (name, template_id); + +CREATE UNIQUE INDEX IF NOT EXISTS cpt_task_phase_cpt_task_phase_uindex + ON cpt_task_phases (task_id, phase_id); + +CREATE UNIQUE INDEX IF NOT EXISTS cpt_task_phase_task_id_uindex + ON cpt_task_phases (task_id); + +CREATE UNIQUE INDEX IF NOT EXISTS cpt_task_statuses_template_id_name_uindex + ON cpt_task_statuses (template_id, name); + +CREATE UNIQUE INDEX IF NOT EXISTS custom_project_templates_name_team_id_uindex + ON custom_project_templates (name, team_id); + +CREATE UNIQUE INDEX IF NOT EXISTS job_titles_name_team_id_uindex + ON job_titles (name, team_id); + +CREATE INDEX IF NOT EXISTS job_titles_team_id_index + ON job_titles (team_id); + +CREATE UNIQUE INDEX IF NOT EXISTS licensing_coupon_codes_coupon_code_uindex + ON licensing_coupon_codes (coupon_code); + +CREATE INDEX IF NOT EXISTS licensing_coupon_codes_redeemed_by_index + ON licensing_coupon_codes (redeemed_by); + +CREATE UNIQUE INDEX IF NOT EXISTS licensing_pricing_plans_uindex + ON licensing_pricing_plans (id); + +CREATE UNIQUE INDEX IF NOT EXISTS licensing_user_plans_uindex + ON licensing_user_subscriptions (id); + +CREATE INDEX IF NOT EXISTS licensing_user_subscriptions_user_id_index + ON licensing_user_subscriptions (user_id); + +CREATE INDEX IF NOT EXISTS notification_settings_team_user_id_index + ON notification_settings (team_id, user_id); + +CREATE UNIQUE INDEX IF NOT EXISTS personal_todo_list_index_uindex + ON personal_todo_list (user_id, index); + +CREATE UNIQUE INDEX IF NOT EXISTS project_categories_name_team_id_uindex + ON project_categories (name, team_id); + +CREATE INDEX IF NOT EXISTS project_comments_project_id_index + ON project_comments (project_id); + +CREATE UNIQUE INDEX IF NOT EXISTS project_folders_team_id_key_uindex + ON project_folders (team_id, key); + +CREATE UNIQUE INDEX IF NOT EXISTS project_folders_team_id_name_uindex + ON project_folders (team_id, name); + +CREATE INDEX IF NOT EXISTS project_members_project_id_index + ON project_members (project_id); + +CREATE INDEX IF NOT EXISTS project_members_project_id_member_id_index + ON project_members (project_id, team_member_id); + +CREATE INDEX IF NOT EXISTS project_members_team_member_id_index + ON project_members (team_member_id); + +CREATE UNIQUE INDEX IF NOT EXISTS project_members_team_member_project_uindex + ON project_members (team_member_id, project_id); + +CREATE UNIQUE INDEX IF NOT EXISTS project_phases_name_project_uindex + ON project_phases (name, project_id); + +CREATE UNIQUE INDEX IF NOT EXISTS project_subscribers_user_task_team_member_uindex + ON project_subscribers (user_id, project_id, team_member_id); + +CREATE INDEX IF NOT EXISTS project_task_list_cols_index + ON project_task_list_cols (project_id, index); + +CREATE UNIQUE INDEX IF NOT EXISTS project_task_list_cols_key_project_uindex + ON project_task_list_cols (key, project_id); + +CREATE INDEX IF NOT EXISTS projects_folder_id_index + ON projects (folder_id); + +CREATE INDEX IF NOT EXISTS projects_id_team_id_index + ON projects (id, team_id); + +CREATE UNIQUE INDEX IF NOT EXISTS projects_key_team_id_uindex + ON projects (key, team_id); + +CREATE INDEX IF NOT EXISTS projects_name_index + ON projects (name); + +CREATE UNIQUE INDEX IF NOT EXISTS projects_name_team_id_uindex + ON projects (name, team_id); + +CREATE INDEX IF NOT EXISTS projects_team_id_folder_id_index + ON projects (team_id, folder_id); + +CREATE INDEX IF NOT EXISTS projects_team_id_index + ON projects (team_id); + +CREATE INDEX IF NOT EXISTS projects_team_id_name_index + ON projects (team_id, name); + diff --git a/worklenz-frontend/src/app/DTOs/.gitkeep b/worklenz-backend/database/migrations/.gitkeep similarity index 100% rename from worklenz-frontend/src/app/DTOs/.gitkeep rename to worklenz-backend/database/migrations/.gitkeep diff --git a/worklenz-frontend/src/app/administrator/admin-center/sidebar/sidebar.component.scss b/worklenz-backend/database/migrations/MIGRATION-NOTE.md similarity index 100% rename from worklenz-frontend/src/app/administrator/admin-center/sidebar/sidebar.component.scss rename to worklenz-backend/database/migrations/MIGRATION-NOTE.md diff --git a/worklenz-backend/database/migrations/README.md b/worklenz-backend/database/migrations/README.md new file mode 100644 index 00000000..482ad595 --- /dev/null +++ b/worklenz-backend/database/migrations/README.md @@ -0,0 +1,2 @@ +Migrations should be executed out in the sequence specified by the filename. They should be removed once the migrations +have been released to all databases. diff --git a/worklenz-backend/database/text_length_checks.sql b/worklenz-backend/database/text_length_checks.sql new file mode 100644 index 00000000..d67b2d9d --- /dev/null +++ b/worklenz-backend/database/text_length_checks.sql @@ -0,0 +1,47 @@ +ALTER TABLE teams + ADD CONSTRAINT teams_name_check CHECK (CHAR_LENGTH(name) <= 55); + +ALTER TABLE clients + ADD CONSTRAINT clients_name_check CHECK (CHAR_LENGTH(name) <= 60); + +ALTER TABLE job_titles + ADD CONSTRAINT job_titles_name_check CHECK (CHAR_LENGTH(name) <= 55); + +ALTER TABLE users + ADD CONSTRAINT users_name_check CHECK (CHAR_LENGTH(name) <= 55); + +ALTER TABLE users + ADD CONSTRAINT users_email_check CHECK (CHAR_LENGTH(email) <= 255); + +ALTER TABLE projects + ADD CONSTRAINT projects_name_check CHECK (CHAR_LENGTH(name) <= 100); + +ALTER TABLE projects + ADD CONSTRAINT projects_notes_check CHECK (CHAR_LENGTH(notes) <= 500); + +ALTER TABLE task_statuses + ADD CONSTRAINT task_statuses_name_check CHECK (CHAR_LENGTH(name) <= 50); + +ALTER TABLE tasks + ADD CONSTRAINT tasks_name_check CHECK (CHAR_LENGTH(name) <= 500); + +ALTER TABLE tasks + ADD CONSTRAINT tasks_description_check CHECK (CHAR_LENGTH(description) <= 500000); + +ALTER TABLE team_labels + ADD CONSTRAINT team_labels_name_check CHECK (CHAR_LENGTH(name) <= 40); + +ALTER TABLE personal_todo_list + ADD CONSTRAINT personal_todo_list_name_check CHECK (CHAR_LENGTH(name) <= 100); + +ALTER TABLE personal_todo_list + ADD CONSTRAINT personal_todo_list_description_check CHECK (CHAR_LENGTH(description) <= 200); + +ALTER TABLE task_work_log + ADD CONSTRAINT task_work_log_description_check CHECK (CHAR_LENGTH(description) <= 500); + +ALTER TABLE task_comment_contents + ADD CONSTRAINT task_comment_contents_name_check CHECK (CHAR_LENGTH(text_content) <= 500); + +ALTER TABLE task_attachments + ADD CONSTRAINT task_attachments_name_check CHECK (CHAR_LENGTH(name) <= 110); diff --git a/worklenz-backend/database/2_triggers.sql b/worklenz-backend/database/triggers.sql similarity index 100% rename from worklenz-backend/database/2_triggers.sql rename to worklenz-backend/database/triggers.sql diff --git a/worklenz-backend/database/truncate.sql b/worklenz-backend/database/truncate.sql new file mode 100644 index 00000000..1edbc353 --- /dev/null +++ b/worklenz-backend/database/truncate.sql @@ -0,0 +1,75 @@ +TRUNCATE TABLE public.pg_sessions RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.email_invitations RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.task_labels RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.team_labels RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.tasks_assignees RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.project_members RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.project_access_levels RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.role_permissions RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.permissions RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.project_logs RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.personal_todo_list RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.user_notifications RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.task_work_log RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.task_comment_contents RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.task_comments RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.team_members RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.job_titles RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.roles RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.task_attachments RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.worklenz_alerts RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.favorite_projects RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.archived_projects RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.shared_projects RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.task_templates_tasks RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.task_templates RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.notification_settings RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.task_updates RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.task_timers RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.tasks RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.task_priorities RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.task_statuses RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.sys_task_status_categories RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.project_task_list_cols RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.projects RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.clients RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.teams, public.users RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.timezones RESTART IDENTITY CASCADE; + +TRUNCATE TABLE public.sys_project_statuses RESTART IDENTITY CASCADE; diff --git a/worklenz-backend/database/worklenz_db_revision_1.svg b/worklenz-backend/database/worklenz_db_revision_1.svg new file mode 100644 index 00000000..c07e4516 --- /dev/null +++ b/worklenz-backend/database/worklenz_db_revision_1.svg @@ -0,0 +1,2533 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + uuid + + + + + + + + + + team_member_id + + + + + + uuid + + + + + + + + + + project_access_level_id + + + + + + uuid + + + + + + + + + + project_id + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + project_members + + + + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + uuid + + + + + + + + + + team_id + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + job_titles + + + + + + + + + + + + + + + json + + + + + + + + + + sess + + + + + + timestamp(6) + + + + + + + + + + expire + + + + + + + + + + + + varchar + + + + + + + + + + sid + + + + + + + + + + + + + + pg_sessions + + + + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + text + + + + + + + + + + color_code + + + + + + text + + + + + + + + + + notes + + + + + + uuid + + + + + + + + + + team_id + + + + + + uuid + + + + + + + + + + client_id + + + + + + uuid + + + + + + + + + + owner_id + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + timestamp with time zone + + + + + + + + + + updated_at + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + projects + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + uuid + + + + + + + + + + job_title_id + + + + + + uuid + + + + + + + + + + user_id + + + + + + uuid + + + + + + + + + + team_id + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + team_members + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + boolean + + + + + + + + + + done + + + + + + timestamp with time zone + + + + + + + + + + start_date + + + + + + timestamp with time zone + + + + + + + + + + end_state + + + + + + uuid + + + + + + + + + + priority_id + + + + + + uuid + + + + + + + + + + project_id + + + + + + uuid + + + + + + + + + + reporter_id + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + timestamp with time zone + + + + + + + + + + updated_at + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + tasks + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + integer + + + + + + + + + + value + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + task_priorities + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + text + + + + + + + + + + key + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + project_access_levels + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + text + + + + + + + + + + url + + + + + + uuid + + + + + + + + + + user_id + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + timestamp with time zone + + + + + + + + + + updated_at + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + teams + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + uuid + + + + + + + + + + team_id + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + timestamp with time zone + + + + + + + + + + updated_at + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + clients + + + + + + + + + + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + timestamp with time zone + + + + + + + + + + updated_at + + + + + + + + + + + + uuid + + + + + + + + + + task_id + + + + + + uuid + + + + + + + + + + project_member_id + + + + + + + + + + + + + + tasks_assignees + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + text + + + + + + + + + + email + + + + + + text + + + + + + + + + + password + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + timestamp with time zone + + + + + + + + + + updated_at + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + users + + + + + + + + + + project_access_level_id:id + + + project_id:id + + + user_id:id + + + reporter_id:id + + + team_id:id + + + team_id:id + + + client_id:id + + + task_id:id + + + project_member_id:id + + + job_title_id:id + + + priority_id:id + + + user_id:id + + + team_id:id + + + team_member_id:id + + + project_id:id + + + owner_id:id + + + team_id:id + + + diff --git a/worklenz-backend/database/worklenz_db_revision_2.svg b/worklenz-backend/database/worklenz_db_revision_2.svg new file mode 100644 index 00000000..f6e54596 --- /dev/null +++ b/worklenz-backend/database/worklenz_db_revision_2.svg @@ -0,0 +1,7392 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text + + + + + + + + + + avatar_url + + + + + + text + + + + + + + + + + email + + + + + + text + + + + + + + + + + name + + + + + + uuid + + + + + + + + + + user_id + + + + + + uuid + + + + + + + + + + team_member_id + + + + + + uuid + + + + + + + + + + team_id + + + + + + + + + + + + + + team_member_info_view + + + + + + + + + + + + + + + uuid + + + + + + + + + + team_id + + + + + + uuid + + + + + + + + + + team_member_id + + + + + + text + + + + + + + + + + email + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + timestamp with time zone + + + + + + + + + + updated_at + + + + + + text + + + + + + + + + + name + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + email_invitations + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + boolean + + + + + + + + + + done + + + + + + timestamp with time zone + + + + + + + + + + start_date + + + + + + timestamp with time zone + + + + + + + + + + end_date + + + + + + uuid + + + + + + + + + + priority_id + + + + + + uuid + + + + + + + + + + project_id + + + + + + uuid + + + + + + + + + + reporter_id + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + timestamp with time zone + + + + + + + + + + updated_at + + + + + + text + + + + + + + + + + description + + + + + + numeric + + + + + + + + + + total_minutes + + + + + + numeric + + + + + + + + + + task_order + + + + + + uuid + + + + + + + + + + parent_task_id + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + tasks + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + text + + + + + + + + + + description + + + + + + + + + + + + text + + + + + + + + + + id + + + + + + + + + + + + + + permissions + + + + + + + + + + + + + + + integer + + + + + + + + + + index + + + + + + uuid + + + + + + + + + + comment_id + + + + + + uuid + + + + + + + + + + team_member_id + + + + + + text + + + + + + + + + + text_content + + + + + + + + + + + + + + task_comment_contents + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + integer + + + + + + + + + + order_index + + + + + + timestamp with time zone + + + + + + + + + + date_created + + + + + + timestamp with time zone + + + + + + + + + + date_updated + + + + + + boolean + + + + + + + + + + default_status + + + + + + text + + + + + + + + + + color_code + + + + + + uuid + + + + + + + + + + team_id + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + task_status_categories + + + + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + uuid + + + + + + + + + + team_id + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + job_titles + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + text + + + + + + + + + + color_code + + + + + + text + + + + + + + + + + notes + + + + + + uuid + + + + + + + + + + team_id + + + + + + uuid + + + + + + + + + + client_id + + + + + + uuid + + + + + + + + + + owner_id + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + timestamp with time zone + + + + + + + + + + updated_at + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + projects + + + + + + + + + + + + + + + text + + + + + + + + + + description + + + + + + + + + + + + + + maintenance_schedules + + + + + + + + + + + + + + + uuid + + + + + + + + + + user_id + + + + + + uuid + + + + + + + + + + project_id + + + + + + + + + + + + + + favorite_projects + + + + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + uuid + + + + + + + + + + team_id + + + + + + boolean + + + + + + + + + + default_role + + + + + + boolean + + + + + + + + + + admin_role + + + + + + boolean + + + + + + + + + + owner + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + roles + + + + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + text + + + + + + + + + + abbrev + + + + + + interval + + + + + + + + + + utc_offset + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + timezones + + + + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + text + + + + + + + + + + description + + + + + + text + + + + + + + + + + color_code + + + + + + boolean + + + + + + + + + + done + + + + + + uuid + + + + + + + + + + user_id + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + timestamp with time zone + + + + + + + + + + updated_at + + + + + + integer + + + + + + + + + + index + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + personal_todo_list + + + + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + bigint + + + + + + + + + + size + + + + + + text + + + + + + + + + + type + + + + + + uuid + + + + + + + + + + task_id + + + + + + uuid + + + + + + + + + + team_id + + + + + + uuid + + + + + + + + + + project_id + + + + + + uuid + + + + + + + + + + uploaded_by + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + task_attachments + + + + + + + + + + + + + + + + + + uuid + + + + + + + + + + user_id + + + + + + uuid + + + + + + + + + + project_id + + + + + + + + + + + + + + archived_projects + + + + + + + + + + + + + + + uuid + + + + + + + + + + task_id + + + + + + uuid + + + + + + + + + + task_status_category_id + + + + + + + + + + + + + + task_statuses + + + + + + + + + + + + + + + + + + numeric + + + + + + + + + + time_spent + + + + + + text + + + + + + + + + + description + + + + + + uuid + + + + + + + + + + task_id + + + + + + uuid + + + + + + + + + + user_id + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + timestamp with time zone + + + + + + + + + + updated_at + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + task_work_log + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + uuid + + + + + + + + + + team_id + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + timestamp with time zone + + + + + + + + + + updated_at + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + clients + + + + + + + + + + + + + + + + + + uuid + + + + + + + + + + team_member_id + + + + + + uuid + + + + + + + + + + project_access_level_id + + + + + + uuid + + + + + + + + + + project_id + + + + + + uuid + + + + + + + + + + role_id + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + project_members + + + + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + text + + + + + + + + + + key + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + project_access_levels + + + + + + + + + + + + + + + + + + uuid + + + + + + + + + + team_id + + + + + + uuid + + + + + + + + + + project_id + + + + + + text + + + + + + + + + + description + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + project_logs + + + + + + + + + + + + + + + + + + uuid + + + + + + + + + + user_id + + + + + + uuid + + + + + + + + + + team_member_id + + + + + + uuid + + + + + + + + + + task_id + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + timestamp with time zone + + + + + + + + + + updated_at + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + task_comments + + + + + + + + + + + + + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + timestamp with time zone + + + + + + + + + + updated_at + + + + + + uuid + + + + + + + + + + team_member_id + + + + + + + + + + + + uuid + + + + + + + + + + task_id + + + + + + uuid + + + + + + + + + + project_member_id + + + + + + + + + + + + + + tasks_assignees + + + + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + uuid + + + + + + + + + + user_id + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + timestamp with time zone + + + + + + + + + + updated_at + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + teams + + + + + + + + + + + + + + + json + + + + + + + + + + sess + + + + + + timestamp(6) + + + + + + + + + + expire + + + + + + + + + + + + varchar + + + + + + + + + + sid + + + + + + + + + + + + + + pg_sessions + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + integer + + + + + + + + + + value + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + task_priorities + + + + + + + + + + + + + + + + + + text + + + + + + + + + + name + + + + + + text + + + + + + + + + + email + + + + + + text + + + + + + + + + + password + + + + + + uuid + + + + + + + + + + active_team + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + timestamp with time zone + + + + + + + + + + updated_at + + + + + + text + + + + + + + + + + google_id + + + + + + text + + + + + + + + + + socket_id + + + + + + uuid + + + + + + + + + + timezone_id + + + + + + text + + + + + + + + + + avatar_url + + + + + + boolean + + + + + + + + + + setup_completed + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + users + + + + + + + + + + + + + + + + + + uuid + + + + + + + + + + job_title_id + + + + + + uuid + + + + + + + + + + user_id + + + + + + uuid + + + + + + + + + + team_id + + + + + + uuid + + + + + + + + + + role_id + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + timestamp with time zone + + + + + + + + + + updated_at + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + team_members + + + + + + + + + + + + + + + uuid + + + + + + + + + + user_id + + + + + + uuid + + + + + + + + + + project_id + + + + + + + + + + + + + + user_demo_projects + + + /* contains user and the demo project */ + + + + + + + + + + + + + + + text + + + + + + + + + + message + + + + + + uuid + + + + + + + + + + user_id + + + + + + uuid + + + + + + + + + + team_id + + + + + + boolean + + + + + + + + + + read + + + + + + timestamp with time zone + + + + + + + + + + created_at + + + + + + timestamp with time zone + + + + + + + + + + updated_at + + + + + + + + + + + + uuid + + + + + + + + + + id + + + + + + + + + + + + + + user_notifications + + + + + + + + + + + + + + + uuid + + + + + + + + + + role_id + + + + + + text + + + + + + + + + + permission_id + + + + + + + + + + + + + + role_permissions + + + + + + + + + + project_id:id + + + active_team:id + + + priority_id:id + + + team_member_id:id + + + task_id:id + + + team_id:id + + + uploaded_by:id + + + team_member_id:id + + + project_id:id + + + project_member_id:id + + + client_id:id + + + user_id:id + + + team_member_id:id + + + project_id:id + + + role_id:id + + + user_id:id + + + user_id:id + + + team_id:id + + + project_id:id + + + task_id:id + + + project_id:id + + + user_id:id + + + team_id:id + + + project_id:id + + + team_id:id + + + team_member_id:id + + + team_id:id + + + task_status_category_id:id + + + project_access_level_id:id + + + permission_id:id + + + team_id:id + + + user_id:id + + + timezone_id:id + + + job_title_id:id + + + team_member_id:id + + + role_id:id + + + task_id:id + + + team_member_id:id + + + task_id:id + + + user_id:id + + + comment_id:id + + + user_id:id + + + project_id:id + + + parent_task_id:id + + + owner_id:id + + + user_id:id + + + user_id:id + + + team_id:id + + + team_id:id + + + task_id:id + + + team_id:id + + + user_id:id + + + role_id:id + + + team_id:id + + + team_id:id + + + diff --git a/worklenz-backend/package-lock.json b/worklenz-backend/package-lock.json index 532cbfec..bab8daf0 100644 --- a/worklenz-backend/package-lock.json +++ b/worklenz-backend/package-lock.json @@ -12,15 +12,18 @@ "@aws-sdk/client-ses": "^3.378.0", "@aws-sdk/s3-request-presigner": "^3.378.0", "@aws-sdk/util-format-url": "^3.357.0", + "@azure/storage-blob": "^12.27.0", "axios": "^1.6.0", "bcrypt": "^5.1.0", "bluebird": "^3.7.2", + "chartjs-to-image": "^1.2.1", "compression": "^1.7.4", "connect-flash": "^0.1.1", "connect-pg-simple": "^7.0.0", "cookie-parser": "~1.4.4", "cors": "^2.8.5", "cron": "^2.4.0", + "crypto-js": "^4.1.1", "csurf": "^1.11.0", "debug": "^4.3.4", "dotenv": "^16.3.1", @@ -40,13 +43,13 @@ "moment-timezone": "^0.5.43", "morgan": "^1.10.0", "nanoid": "^3.3.6", - "passport": "^0.5.3", + "passport": "^0.7.0", "passport-google-oauth2": "^0.2.0", "passport-google-oauth20": "^2.0.0", "passport-local": "^1.0.0", "path": "^0.12.7", - "pg": "^8.11.1", - "pg-native": "^3.0.1", + "pg": "^8.14.1", + "pg-native": "^3.3.0", "pug": "^3.0.2", "redis": "^4.6.7", "sanitize-html": "^2.11.0", @@ -67,13 +70,12 @@ "@types/connect-flash": "^0.0.37", "@types/cookie-parser": "^1.4.3", "@types/cron": "^2.0.1", + "@types/crypto-js": "^4.2.2", "@types/csurf": "^1.11.2", "@types/express": "^4.17.17", "@types/express-brute": "^1.0.2", "@types/express-brute-redis": "^0.0.4", - "@types/express-rate-limit": "^6.0.0", "@types/express-session": "^1.17.7", - "@types/express-validator": "^3.0.0", "@types/fs-extra": "^9.0.13", "@types/hpp": "^0.2.2", "@types/http-errors": "^1.8.2", @@ -83,15 +85,13 @@ "@types/mime-types": "^2.1.1", "@types/morgan": "^1.9.4", "@types/node": "^18.17.1", - "@types/passport": "^1.0.12", - "@types/passport-google-oauth20": "^2.0.11", - "@types/passport-local": "^1.0.35", - "@types/pg": "^8.10.2", + "@types/passport": "^1.0.17", + "@types/passport-google-oauth20": "^2.0.16", + "@types/passport-local": "^1.0.38", + "@types/pg": "^8.11.11", "@types/pug": "^2.0.6", - "@types/redis": "^4.0.11", "@types/sanitize-html": "^2.9.0", "@types/sharp": "^0.31.1", - "@types/socket.io": "^3.0.2", "@types/swagger-jsdoc": "^6.0.1", "@types/toobusy-js": "^0.5.2", "@types/uglify-js": "^3.17.1", @@ -106,7 +106,6 @@ "eslint-plugin-security": "^1.7.1", "fs-extra": "^10.1.0", "grunt": "^1.6.1", - "grunt-cli": "^1.4.3", "grunt-contrib-clean": "^2.0.1", "grunt-contrib-compress": "^2.0.0", "grunt-contrib-copy": "^1.0.0", @@ -1012,6 +1011,236 @@ "node": ">=14.0.0" } }, + "node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.9.0.tgz", + "integrity": "sha512-FPwHpZywuyasDSLMqJ6fhbOK3TqUdviZNF8OqRGA4W5Ewib2lEEZ+pBsYcBa88B2NGO/SEnYPGhyBqNlE8ilSw==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-util": "^1.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.9.3.tgz", + "integrity": "sha512-/wGw8fJ4mdpJ1Cum7s1S+VQyXt1ihwKLzfabS1O/RDADnmzVc01dHn44qD0BvGH6KlZNzOMW95tEpKqhkCChPA==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.9.1", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.6.1", + "@azure/logger": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-http-compat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-2.2.0.tgz", + "integrity": "sha512-1kW8ZhN0CfbNOG6C688z5uh2yrzALE7dDXHiR9dY4vt+EbhGZQSbjDa5bQd2rf3X2pdWMsXbqbArxUyeNdvtmg==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-client": "^1.3.0", + "@azure/core-rest-pipeline": "^1.19.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-lro": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz", + "integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-util": "^1.2.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-paging": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.6.2.tgz", + "integrity": "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.19.1.tgz", + "integrity": "sha512-zHeoI3NCs53lLBbWNzQycjnYKsA1CVKlnzSNuSFcUDwBp8HHVObePxrM7HaX+Ha5Ks639H7chNC9HOaIhNS03w==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.8.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@azure/core-rest-pipeline/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.2.0.tgz", + "integrity": "sha512-UKTiEJPkWcESPYJz3X5uKRYyOcJD+4nYph+KpfdPRnQJVrZfk0KJgdnaAWKfhsBBtAf/D58Az4AvCJEmWgIBAg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.11.0.tgz", + "integrity": "sha512-DxOSLua+NdpWoSqULhjDyAZTXFdP/LKkqtYuxxz1SCN289zk3OG8UOpnCQAz/tygyACBtWp/BoO72ptK7msY8g==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-xml": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@azure/core-xml/-/core-xml-1.4.5.tgz", + "integrity": "sha512-gT4H8mTaSXRz7eGTuQyq1aIJnJqeXzpOe9Ay7Z3FrCouer14CbV3VzjnJrNrQfbBpGBLO9oy8BmrY75A0p53cA==", + "license": "MIT", + "dependencies": { + "fast-xml-parser": "^5.0.7", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-xml/node_modules/fast-xml-parser": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.0.9.tgz", + "integrity": "sha512-2mBwCiuW3ycKQQ6SOesSB8WeF+fIGb6I/GG5vU5/XEptwFFhp9PE8b9O7fbs2dpq9fXn4ULR3UsfydNUCntf5A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@azure/core-xml/node_modules/strnum": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.0.5.tgz", + "integrity": "sha512-YAT3K/sgpCUxhxNMrrdhtod3jckkpYwH6JAuwmUdXZsmzH1wUyzTMrrK2wYCEEqlKwrWDd35NeuUkbBy/1iK+Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/@azure/logger": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.1.4.tgz", + "integrity": "sha512-4IXXzcCdLdlXuCG+8UKEwLA1T1NHqUfanhXYHiQTn+6sfWCZXduqbtXDGceg3Ce5QxTGo7EqmbV6Bi+aqKuClQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/storage-blob": { + "version": "12.27.0", + "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.27.0.tgz", + "integrity": "sha512-IQjj9RIzAKatmNca3D6bT0qJ+Pkox1WZGOg2esJF2YLHb45pQKOwGPIAV+w3rfgkj7zV3RMxpn/c6iftzSOZJQ==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.4.0", + "@azure/core-client": "^1.6.2", + "@azure/core-http-compat": "^2.0.0", + "@azure/core-lro": "^2.2.0", + "@azure/core-paging": "^1.1.1", + "@azure/core-rest-pipeline": "^1.10.1", + "@azure/core-tracing": "^1.1.2", + "@azure/core-util": "^1.6.1", + "@azure/core-xml": "^1.4.3", + "@azure/logger": "^1.0.0", + "events": "^3.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.22.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", @@ -4982,7 +5211,8 @@ "node_modules/@types/cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "license": "MIT" }, "node_modules/@types/cookie-parser": { "version": "1.4.3", @@ -4994,9 +5224,10 @@ } }, "node_modules/@types/cors": { - "version": "2.8.13", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", - "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -5011,6 +5242,12 @@ "@types/node": "*" } }, + "node_modules/@types/crypto-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", + "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", + "dev": true + }, "node_modules/@types/csurf": { "version": "1.11.2", "resolved": "https://registry.npmjs.org/@types/csurf/-/csurf-1.11.2.tgz", @@ -5059,16 +5296,6 @@ "@types/node": "*" } }, - "node_modules/@types/express-rate-limit": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@types/express-rate-limit/-/express-rate-limit-6.0.0.tgz", - "integrity": "sha512-nZxo3nwU20EkTl/f2eGdndQkDIJYwkXIX4S3Vrp2jMdSdFJ6AWtIda8gOz0wiMuOFoeH/UUlCAiacz3x3eWNFA==", - "deprecated": "This is a stub types definition. express-rate-limit provides its own type definitions, so you do not need this installed.", - "dev": true, - "dependencies": { - "express-rate-limit": "*" - } - }, "node_modules/@types/express-serve-static-core": { "version": "4.17.35", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", @@ -5090,16 +5317,6 @@ "@types/express": "*" } }, - "node_modules/@types/express-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/express-validator/-/express-validator-3.0.0.tgz", - "integrity": "sha512-LusnB0YhTXpBT25PXyGPQlK7leE1e41Vezq1hHEUwjfkopM1Pkv2X2Ppxqh9c+w/HZ6Udzki8AJotKNjDTGdkQ==", - "deprecated": "This is a stub types definition for express-validator (https://github.com/ctavan/express-validator). express-validator provides its own type definitions, so you don't need @types/express-validator installed!", - "dev": true, - "dependencies": { - "express-validator": "*" - } - }, "node_modules/@types/fs-extra": { "version": "9.0.13", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", @@ -5230,19 +5447,21 @@ } }, "node_modules/@types/passport": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.12.tgz", - "integrity": "sha512-QFdJ2TiAEoXfEQSNDISJR1Tm51I78CymqcBa8imbjo6dNNu+l2huDxxbDEIoFIwOSKMkOfHEikyDuZ38WwWsmw==", + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz", + "integrity": "sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*" } }, "node_modules/@types/passport-google-oauth20": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.11.tgz", - "integrity": "sha512-9XMT1GfwhZL7UQEiCepLef55RNPHkbrCtsU7rsWPTEOsmu5qVIW8nSemtB4p+P24CuOhA+IKkv8LsPThYghGww==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.16.tgz", + "integrity": "sha512-ayXK2CJ7uVieqhYOc6k/pIr5pcQxOLB6kBev+QUGS7oEZeTgIs1odDobXRqgfBPvXzl0wXCQHftV5220czZCPA==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*", "@types/passport": "*", @@ -5250,10 +5469,11 @@ } }, "node_modules/@types/passport-local": { - "version": "1.0.35", - "resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.35.tgz", - "integrity": "sha512-K4eLTJ8R0yYW8TvCqkjB0pTKoqfUSdl5PfZdidTjV2ETV3604fQxtY6BHKjQWAx50WUS0lqzBvKv3LoI1ZBPeA==", + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.38.tgz", + "integrity": "sha512-nsrW4A963lYE7lNTv9cr5WmiUD1ibYJvWrpE13oxApFsRt77b0RdtZvKbCdNIY4v/QZ6TRQWaDDEwV1kCTmcXg==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*", "@types/passport": "*", @@ -5282,9 +5502,10 @@ } }, "node_modules/@types/pg": { - "version": "8.10.2", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.10.2.tgz", - "integrity": "sha512-MKFs9P6nJ+LAeHLU3V0cODEOgyThJ3OAnmOlsZsxux6sfQs3HRXR5bBn7xG5DjckEFhTAxsXi7k7cd0pCMxpJw==", + "version": "8.11.11", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.11.tgz", + "integrity": "sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==", + "license": "MIT", "dependencies": { "@types/node": "*", "pg-protocol": "*", @@ -5315,16 +5536,6 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", "dev": true }, - "node_modules/@types/redis": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/@types/redis/-/redis-4.0.11.tgz", - "integrity": "sha512-bI+gth8La8Wg/QCR1+V1fhrL9+LZUSWfcqpOj2Kc80ZQ4ffbdL173vQd5wovmoV9i071FU9oP2g6etLuEwb6Rg==", - "deprecated": "This is a stub types definition. redis provides its own type definitions, so you do not need this installed.", - "dev": true, - "dependencies": { - "redis": "*" - } - }, "node_modules/@types/sanitize-html": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.9.0.tgz", @@ -5370,16 +5581,6 @@ "@types/node": "*" } }, - "node_modules/@types/socket.io": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-3.0.2.tgz", - "integrity": "sha512-pu0sN9m5VjCxBZVK8hW37ZcMe8rjn4HHggBN5CbaRTvFwv5jOmuIRZEuddsBPa9Th0ts0SIo3Niukq+95cMBbQ==", - "deprecated": "This is a stub types definition. socket.io provides its own type definitions, so you do not need this installed.", - "dev": true, - "dependencies": { - "socket.io": "*" - } - }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -5977,9 +6178,10 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, "node_modules/assert-never": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.2.1.tgz", - "integrity": "sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.4.0.tgz", + "integrity": "sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==", + "license": "MIT" }, "node_modules/async": { "version": "3.2.4", @@ -5992,11 +6194,12 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", - "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.4", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -6210,6 +6413,7 @@ "version": "3.0.0-canary-5", "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", + "license": "MIT", "dependencies": { "@babel/types": "^7.9.6" }, @@ -6245,6 +6449,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", "engines": { "node": "^4.5.0 || >= 5.9" } @@ -6346,20 +6551,21 @@ } }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", + "qs": "6.13.0", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -6372,6 +6578,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -6380,6 +6587,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -6387,7 +6595,8 @@ "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/body/node_modules/bytes": { "version": "1.0.0", @@ -6429,12 +6638,13 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -6543,14 +6753,6 @@ "node": ">=0.10" } }, - "node_modules/buffer-writer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", - "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", - "engines": { - "node": ">=4" - } - }, "node_modules/buffers": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", @@ -6588,6 +6790,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/call-me-maybe": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", @@ -6674,6 +6905,15 @@ "is-regex": "^1.0.3" } }, + "node_modules/chartjs-to-image": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/chartjs-to-image/-/chartjs-to-image-1.2.2.tgz", + "integrity": "sha512-qnYedDlNSPsrISQyRhJk4gWciKMtK8mlx2VWbFMJIPLVokSHJBEUuoxE6LLDFGnOhdvLd3K5E6lmGap7/phWFQ==", + "dependencies": { + "axios": "^1.6.0", + "javascript-stringify": "^2.1.0" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -7001,6 +7241,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7018,19 +7259,21 @@ "dev": true }, "node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/cookie-parser": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", - "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", "dependencies": { - "cookie": "0.4.1", + "cookie": "0.7.2", "cookie-signature": "1.0.6" }, "engines": { @@ -7110,10 +7353,11 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -7123,6 +7367,12 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/csrf": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.1.0.tgz", @@ -7300,6 +7550,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -7376,7 +7627,8 @@ "node_modules/doctypes": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", - "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==" + "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", + "license": "MIT" }, "node_modules/dom-serializer": { "version": "2.0.0", @@ -7440,6 +7692,20 @@ "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -7518,9 +7784,10 @@ "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -7534,29 +7801,31 @@ } }, "node_modules/engine.io": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.1.tgz", - "integrity": "sha512-mGqhI+D7YxS9KJMppR6Iuo37Ed3abhU8NdfgSvJSDUafQutrN+sPTncJYTyM9+tkhSmWodKtVYGPPHyXJEwEQA==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", + "license": "MIT", "dependencies": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "~0.4.1", + "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.3.1", - "engine.io-parser": "~5.1.0", - "ws": "~8.11.0" + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" }, "engines": { - "node": ">=10.0.0" + "node": ">=10.2.0" } }, "node_modules/engine.io-parser": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.1.0.tgz", - "integrity": "sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", "engines": { "node": ">=10.0.0" } @@ -7590,6 +7859,36 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", @@ -7673,7 +7972,8 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "1.0.5", @@ -8012,6 +8312,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -8022,6 +8323,15 @@ "integrity": "sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==", "dev": true }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/exceljs": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.3.0.tgz", @@ -8122,36 +8432,37 @@ } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -8160,6 +8471,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express-rate-limit": { @@ -8174,12 +8489,13 @@ } }, "node_modules/express-session": { - "version": "1.17.3", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz", - "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", + "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", + "license": "MIT", "dependencies": { - "cookie": "0.4.2", - "cookie-signature": "1.0.6", + "cookie": "0.7.2", + "cookie-signature": "1.0.7", "debug": "2.6.9", "depd": "~2.0.0", "on-headers": "~1.0.2", @@ -8191,13 +8507,11 @@ "node": ">= 0.8.0" } }, - "node_modules/express-session/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "engines": { - "node": ">= 0.6" - } + "node_modules/express-session/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" }, "node_modules/express-session/node_modules/debug": { "version": "2.6.9", @@ -8244,9 +8558,10 @@ } }, "node_modules/express/node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -8435,10 +8750,11 @@ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -8447,12 +8763,13 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -8467,6 +8784,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -8474,7 +8792,8 @@ "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/find-up": { "version": "5.0.0", @@ -8557,15 +8876,16 @@ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -8621,6 +8941,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -8727,9 +9048,13 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/gauge": { "version": "3.0.2", @@ -8789,14 +9114,24 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8985,6 +9320,18 @@ "node": "*" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -9740,21 +10087,11 @@ "node": ">=4" } }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -9781,6 +10118,18 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/helmet": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/helmet/-/helmet-6.2.0.tgz", @@ -9867,6 +10216,28 @@ "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", "dev": true }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -9892,6 +10263,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -10114,6 +10486,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -10358,6 +10731,11 @@ "node": ">=8" } }, + "node_modules/javascript-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz", + "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==" + }, "node_modules/jest": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", @@ -11950,7 +12328,8 @@ "node_modules/js-stringify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", - "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==" + "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==", + "license": "MIT" }, "node_modules/js-tokens": { "version": "4.0.0", @@ -12217,13 +12596,14 @@ } }, "node_modules/libpq": { - "version": "1.8.12", - "resolved": "https://registry.npmjs.org/libpq/-/libpq-1.8.12.tgz", - "integrity": "sha512-4lUY9BD9suz76mVS0kH4rRgRy620g/c9YZH5GYC3smfIpjtj6KiPuQ4IwQSHSZMMMhMM3tBFrYUrw8mHOOZVeg==", + "version": "1.8.14", + "resolved": "https://registry.npmjs.org/libpq/-/libpq-1.8.14.tgz", + "integrity": "sha512-/DDvQCiXP0KBMZ31U2mmURKaxoKt9kNqqgrSO2RuBKS+OJjw5b7uHi5jFoV8zPAUa2TNtq2XfcWL1OWDEyjwlg==", "hasInstallScript": true, + "license": "MIT", "dependencies": { "bindings": "1.5.0", - "nan": "^2.14.0" + "nan": "2.22.0" } }, "node_modules/lie": { @@ -12480,6 +12860,15 @@ "node": ">=0.10.0" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/maxmin": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-3.0.0.tgz", @@ -12583,9 +12972,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -12611,12 +13004,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -12627,6 +13021,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -12808,20 +13203,22 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/nan": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==" + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", + "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==", + "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -13024,9 +13421,13 @@ } }, "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -13080,6 +13481,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -13217,11 +13619,6 @@ "node": ">=6" } }, - "node_modules/packet-reader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", - "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" - }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -13294,12 +13691,14 @@ } }, "node_modules/passport": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/passport/-/passport-0.5.3.tgz", - "integrity": "sha512-gGc+70h4gGdBWNsR3FuV3byLDY6KBTJAIExGFXTpQaYfbbcHCBlRRKx7RBQSpqEqc5Hh2qVzRs7ssvSfOpkUEA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "license": "MIT", "dependencies": { "passport-strategy": "1.x.x", - "pause": "0.0.1" + "pause": "0.0.1", + "utils-merge": "^1.0.1" }, "engines": { "node": ">= 0.4.0" @@ -13428,9 +13827,10 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" }, "node_modules/path-type": { "version": "4.0.0", @@ -13447,15 +13847,14 @@ "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" }, "node_modules/pg": { - "version": "8.11.1", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.1.tgz", - "integrity": "sha512-utdq2obft07MxaDg0zBJI+l/M3mBRfIpEN3iSemsz0G5F2/VXx+XzqF4oxrbIZXQxt2AZzIUzyVg/YM6xOP/WQ==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.14.1.tgz", + "integrity": "sha512-0TdbqfjwIun9Fm/r89oB7RFQ0bLgduAhiIqIXOsyKoiC/L54DbuAAzIEN/9Op0f1Po9X7iCPXGoa/Ah+2aI8Xw==", + "license": "MIT", "dependencies": { - "buffer-writer": "2.0.0", - "packet-reader": "1.0.0", - "pg-connection-string": "^2.6.1", - "pg-pool": "^3.6.1", - "pg-protocol": "^1.6.0", + "pg-connection-string": "^2.7.0", + "pg-pool": "^3.8.0", + "pg-protocol": "^1.8.0", "pg-types": "^2.1.0", "pgpass": "1.x" }, @@ -13481,9 +13880,10 @@ "optional": true }, "node_modules/pg-connection-string": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.1.tgz", - "integrity": "sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz", + "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==", + "license": "MIT" }, "node_modules/pg-int8": { "version": "1.0.1", @@ -13494,44 +13894,45 @@ } }, "node_modules/pg-native": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/pg-native/-/pg-native-3.0.1.tgz", - "integrity": "sha512-LBVNWkNh0fVx/cienARRP2y22J5OpUsKBe0TpxzAx3arEUUdIs77aLSAHS3scS7SMaqc+OkG40CEu5fN0/cjIw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pg-native/-/pg-native-3.3.0.tgz", + "integrity": "sha512-8GHZOx20B/wceRebDG2KK2KZbmDmwkoLvWz4X7BQIF1fjRLCNp48oHsEHSk1lTw36GFGMksLiJ3qZcmSAgVdYA==", + "license": "MIT", "dependencies": { - "libpq": "^1.8.10", - "pg-types": "^1.12.1", - "readable-stream": "1.0.31" + "libpq": "1.8.14", + "pg-types": "^2.1.0" } }, - "node_modules/pg-native/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" - }, "node_modules/pg-native/node_modules/pg-types": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.13.0.tgz", - "integrity": "sha512-lfKli0Gkl/+za/+b6lzENajczwZHc7D5kiUCZfgm914jipD2kIOIvEkAhZ8GrW3/TUoP9w8FHjwpPObBye5KQQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", "dependencies": { "pg-int8": "1.0.1", - "postgres-array": "~1.0.0", + "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.0", + "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" } }, "node_modules/pg-native/node_modules/postgres-array": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.3.tgz", - "integrity": "sha512-5wClXrAP0+78mcsNX3/ithQ5exKvCyK5lr5NEEEeGwwM6NJdQgzIJBVxLvRW+huFpX92F2QnZ5CcokH0VhK2qQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, "node_modules/pg-native/node_modules/postgres-bytea": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -13540,6 +13941,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -13548,6 +13950,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", "dependencies": { "xtend": "^4.0.0" }, @@ -13555,22 +13958,6 @@ "node": ">=0.10.0" } }, - "node_modules/pg-native/node_modules/readable-stream": { - "version": "1.0.31", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.31.tgz", - "integrity": "sha512-tco/Dwv1f/sgIgN6CWdj/restacPKNskK6yps1981ivH2ZmLYcs5o5rVzL3qaO/cSkhN8hYOMWs7+glzOLSgRg==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/pg-native/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" - }, "node_modules/pg-numeric": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz", @@ -13580,17 +13967,19 @@ } }, "node_modules/pg-pool": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz", - "integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.8.0.tgz", + "integrity": "sha512-VBw3jiVm6ZOdLBTIcXLNdSotb6Iy3uOCwDGFAksZCXmi10nyRvnP2v3jl4d+IsLYRyXf6o9hIm/ZtUzlByNUdw==", + "license": "MIT", "peerDependencies": { "pg": ">=8.0" } }, "node_modules/pg-protocol": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", - "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.8.0.tgz", + "integrity": "sha512-jvuYlEkL03NRvOoyoRktBK7+qU5kOvlAwvmrH8sr3wbLrOdVWsRxQfz8mMy9sZFsqJ1hEWNfdWKI4SAmoL+j7g==", + "license": "MIT" }, "node_modules/pg-types": { "version": "4.0.1", @@ -13958,11 +14347,12 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "node_modules/pug": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.2.tgz", - "integrity": "sha512-bp0I/hiK1D1vChHh6EfDxtndHji55XP/ZJKwsRqrz6lRia6ZC2OZbdAymlxdVFwd1L70ebrVJw4/eZ79skrIaw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.3.tgz", + "integrity": "sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==", + "license": "MIT", "dependencies": { - "pug-code-gen": "^3.0.2", + "pug-code-gen": "^3.0.3", "pug-filters": "^4.0.0", "pug-lexer": "^5.0.1", "pug-linker": "^4.0.0", @@ -13976,6 +14366,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", + "license": "MIT", "dependencies": { "constantinople": "^4.0.1", "js-stringify": "^1.0.2", @@ -13983,24 +14374,26 @@ } }, "node_modules/pug-code-gen": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.2.tgz", - "integrity": "sha512-nJMhW16MbiGRiyR4miDTQMRWDgKplnHyeLvioEJYbk1RsPI3FuA3saEP8uwnTb2nTJEKBU90NFVWJBk4OU5qyg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.3.tgz", + "integrity": "sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw==", + "license": "MIT", "dependencies": { "constantinople": "^4.0.1", "doctypes": "^1.1.0", "js-stringify": "^1.0.2", "pug-attrs": "^3.0.0", - "pug-error": "^2.0.0", - "pug-runtime": "^3.0.0", + "pug-error": "^2.1.0", + "pug-runtime": "^3.0.1", "void-elements": "^3.1.0", "with": "^7.0.0" } }, "node_modules/pug-error": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.0.0.tgz", - "integrity": "sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz", + "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==", + "license": "MIT" }, "node_modules/pug-filters": { "version": "4.0.0", @@ -14054,7 +14447,8 @@ "node_modules/pug-runtime": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", - "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==" + "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==", + "license": "MIT" }, "node_modules/pug-strip-comments": { "version": "2.0.0", @@ -14088,11 +14482,12 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -14138,14 +14533,16 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -14160,6 +14557,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -14512,9 +14910,10 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sanitize-html": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.11.0.tgz", - "integrity": "sha512-BG68EDHRaGKqlsNjJ2xUB7gpInPA8gVx/mvjO743hZaeMCZ2DwzW7xvsqZ+KNU4QKwj86HJ3uu2liISf2qBBUA==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.14.0.tgz", + "integrity": "sha512-CafX+IUPxZshXqqRaG9ZClSlfPVjSxI0td7n07hk8QO2oO+9JDnlcL8iM8TWeOXOIBFgIOx6zioTzM53AOMn3g==", + "license": "MIT", "dependencies": { "deepmerge": "^4.2.2", "escape-string-regexp": "^4.0.0", @@ -14573,9 +14972,10 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -14599,6 +14999,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -14606,22 +15007,34 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -14741,13 +15154,72 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -14838,28 +15310,31 @@ } }, "node_modules/socket.io": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.1.tgz", - "integrity": "sha512-W+utHys2w//dhFjy7iQQu9sGd3eokCjGbl2r59tyLqNiJJBdIebn3GAKEXBr3osqHTObJi2die/25bCx2zsaaw==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "license": "MIT", "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.5.0", + "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" }, "engines": { - "node": ">=10.0.0" + "node": ">=10.2.0" } }, "node_modules/socket.io-adapter": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", - "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "license": "MIT", "dependencies": { - "ws": "~8.11.0" + "debug": "~4.3.4", + "ws": "~8.17.1" } }, "node_modules/socket.io-parser": { @@ -15185,9 +15660,10 @@ } }, "node_modules/tar": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", - "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -15330,6 +15806,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -15491,9 +15968,10 @@ } }, "node_modules/tslib": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", - "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, "node_modules/tslint": { "version": "6.1.3", @@ -15799,6 +16277,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -15981,6 +16460,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -16092,6 +16572,7 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", + "license": "MIT", "dependencies": { "@babel/parser": "^7.9.6", "@babel/types": "^7.9.6", @@ -16171,15 +16652,16 @@ } }, "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { diff --git a/worklenz-backend/package.json b/worklenz-backend/package.json index db238e1b..27b21c88 100644 --- a/worklenz-backend/package.json +++ b/worklenz-backend/package.json @@ -15,6 +15,7 @@ "tcs": "grunt build:tsc", "build": "grunt build", "watch": "grunt watch", + "dev": "grunt dev", "es": "esbuild `find src -type f -name '*.ts'` --platform=node --minify=true --watch=true --target=esnext --format=cjs --tsconfig=tsconfig.prod.json --outdir=dist", "copy": "grunt copy", "sonar": "sonar-scanner -Dproject.settings=sonar-project-dev.properties", @@ -32,15 +33,18 @@ "@aws-sdk/client-ses": "^3.378.0", "@aws-sdk/s3-request-presigner": "^3.378.0", "@aws-sdk/util-format-url": "^3.357.0", + "@azure/storage-blob": "^12.27.0", "axios": "^1.6.0", "bcrypt": "^5.1.0", "bluebird": "^3.7.2", + "chartjs-to-image": "^1.2.1", "compression": "^1.7.4", "connect-flash": "^0.1.1", "connect-pg-simple": "^7.0.0", "cookie-parser": "~1.4.4", "cors": "^2.8.5", "cron": "^2.4.0", + "crypto-js": "^4.1.1", "csurf": "^1.11.0", "debug": "^4.3.4", "dotenv": "^16.3.1", @@ -60,13 +64,13 @@ "moment-timezone": "^0.5.43", "morgan": "^1.10.0", "nanoid": "^3.3.6", - "passport": "^0.5.3", + "passport": "^0.7.0", "passport-google-oauth2": "^0.2.0", "passport-google-oauth20": "^2.0.0", "passport-local": "^1.0.0", "path": "^0.12.7", - "pg": "^8.11.1", - "pg-native": "^3.0.1", + "pg": "^8.14.1", + "pg-native": "^3.3.0", "pug": "^3.0.2", "redis": "^4.6.7", "sanitize-html": "^2.11.0", @@ -87,13 +91,12 @@ "@types/connect-flash": "^0.0.37", "@types/cookie-parser": "^1.4.3", "@types/cron": "^2.0.1", + "@types/crypto-js": "^4.2.2", "@types/csurf": "^1.11.2", "@types/express": "^4.17.17", "@types/express-brute": "^1.0.2", "@types/express-brute-redis": "^0.0.4", - "@types/express-rate-limit": "^6.0.0", "@types/express-session": "^1.17.7", - "@types/express-validator": "^3.0.0", "@types/fs-extra": "^9.0.13", "@types/hpp": "^0.2.2", "@types/http-errors": "^1.8.2", @@ -103,15 +106,13 @@ "@types/mime-types": "^2.1.1", "@types/morgan": "^1.9.4", "@types/node": "^18.17.1", - "@types/passport": "^1.0.12", - "@types/passport-google-oauth20": "^2.0.11", - "@types/passport-local": "^1.0.35", - "@types/pg": "^8.10.2", + "@types/passport": "^1.0.17", + "@types/passport-google-oauth20": "^2.0.16", + "@types/passport-local": "^1.0.38", + "@types/pg": "^8.11.11", "@types/pug": "^2.0.6", - "@types/redis": "^4.0.11", "@types/sanitize-html": "^2.9.0", "@types/sharp": "^0.31.1", - "@types/socket.io": "^3.0.2", "@types/swagger-jsdoc": "^6.0.1", "@types/toobusy-js": "^0.5.2", "@types/uglify-js": "^3.17.1", @@ -126,7 +127,6 @@ "eslint-plugin-security": "^1.7.1", "fs-extra": "^10.1.0", "grunt": "^1.6.1", - "grunt-cli": "^1.4.3", "grunt-contrib-clean": "^2.0.1", "grunt-contrib-compress": "^2.0.0", "grunt-contrib-copy": "^1.0.0", diff --git a/worklenz-backend/release b/worklenz-backend/release index c3398755..71ed48c5 100644 --- a/worklenz-backend/release +++ b/worklenz-backend/release @@ -1 +1 @@ -901 \ No newline at end of file +954 \ No newline at end of file diff --git a/worklenz-backend/src/app.ts b/worklenz-backend/src/app.ts index bd8e266b..a45fd12b 100644 --- a/worklenz-backend/src/app.ts +++ b/worklenz-backend/src/app.ts @@ -1,5 +1,5 @@ import createError from "http-errors"; -import express, {NextFunction, Request, Response} from "express"; +import express, { NextFunction, Request, Response } from "express"; import path from "path"; import cookieParser from "cookie-parser"; import logger from "morgan"; @@ -9,101 +9,176 @@ import passport from "passport"; import csurf from "csurf"; import rateLimit from "express-rate-limit"; import cors from "cors"; -import uglify from "uglify-js"; import flash from "connect-flash"; import hpp from "hpp"; import passportConfig from "./passport"; -import indexRouter from "./routes/index"; import apiRouter from "./routes/apis"; import authRouter from "./routes/auth"; import emailTemplatesRouter from "./routes/email-templates"; - import public_router from "./routes/public"; -import {isInternalServer, isProduction} from "./shared/utils"; +import { isInternalServer, isProduction } from "./shared/utils"; import sessionMiddleware from "./middlewares/session-middleware"; -import {send_to_slack} from "./shared/slack"; -import {CSP_POLICIES} from "./shared/csp"; import safeControllerFunction from "./shared/safe-controller-function"; import AwsSesController from "./controllers/aws-ses-controller"; +import { CSP_POLICIES } from "./shared/csp"; const app = express(); -app.use(compression()); -app.use(helmet({crossOriginResourcePolicy: false, crossOriginEmbedderPolicy: false})); +// Trust first proxy if behind reverse proxy +app.set("trust proxy", 1); +// Basic middleware setup +app.use(compression()); +app.use(logger("dev")); +app.use(express.json({ limit: "50mb" })); +app.use(express.urlencoded({ extended: false, limit: "50mb" })); +app.use(cookieParser(process.env.COOKIE_SECRET)); +app.use(hpp()); + +// Helmet security headers +app.use(helmet({ + crossOriginEmbedderPolicy: false, + crossOriginResourcePolicy: false, +})); + +// Custom security headers app.use((_req: Request, res: Response, next: NextFunction) => { res.setHeader("X-XSS-Protection", "1; mode=block"); res.removeHeader("server"); + res.setHeader("Content-Security-Policy", CSP_POLICIES); next(); }); +// CORS configuration +const allowedOrigins = [ + isProduction() + ? [ + `https://react.worklenz.com`, + `https://v2.worklenz.com`, + `https://dev.worklenz.com` + ] + : [ + "http://localhost:3000", + "http://localhost:5173", + "http://127.0.0.1:5173", + "http://127.0.0.1:3000" + ] +].flat(); + +app.use(cors({ + origin: (origin, callback) => { + if (!origin || allowedOrigins.includes(origin)) { + callback(null, true); + } else { + console.log("Blocked origin:", origin); + callback(new Error("Not allowed by CORS")); + } + }, + credentials: true, + methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"], + allowedHeaders: [ + "Origin", + "X-Requested-With", + "Content-Type", + "Accept", + "Authorization", + "X-CSRF-Token" + ], + exposedHeaders: ["Set-Cookie", "X-CSRF-Token"] +})); + +// Handle preflight requests +app.options("*", cors()); + +// Session setup - must be before passport and CSRF +app.use(sessionMiddleware); + +// Passport initialization +passportConfig(passport); +app.use(passport.initialize()); +app.use(passport.session()); + +// Flash messages +app.use(flash()); + +// Auth check middleware function isLoggedIn(req: Request, _res: Response, next: NextFunction) { return req.user ? next() : next(createError(401)); } -passportConfig(passport); - -// eslint-disable-next-line @typescript-eslint/no-var-requires -require("pug").filters = { - /** - * ```pug - * script - * :minify_js - * // JavaScript Syntax - * ``` - * @param {String} text - * @param {Object} options - */ - minify_js(text: string) { - if (!text) return; - // return text; - return uglify.minify({"script.js": text}).code; - } -}; - -// view engine setup -app.set("views", path.join(__dirname, "views")); -app.set("view engine", "pug"); - -app.use(logger("dev")); -app.use(express.json({limit: "50mb"})); -app.use(express.urlencoded({extended: false, limit: "50mb"})); -// Prevent HTTP Parameter Pollution -app.use(hpp()); -app.use(cookieParser(process.env.COOKIE_SECRET)); - -app.use(cors({ - origin: [`https://${process.env.HOSTNAME}`], - methods: "GET,PUT,POST,DELETE", - preflightContinue: false, - credentials: true -})); - -app.post("/-/csp", (req: express.Request, res: express.Response) => { - send_to_slack({ - type: "⚠️ CSP Report", - body: req.body - }); - res.sendStatus(200); +// CSRF configuration +const csrfProtection = csurf({ + cookie: { + key: "XSRF-TOKEN", + path: "/", + httpOnly: false, + secure: isProduction(), // Only secure in production + sameSite: isProduction() ? "none" : "lax", // Different settings for dev vs prod + domain: isProduction() ? ".worklenz.com" : undefined // Only set domain in production + }, + ignoreMethods: ["HEAD", "OPTIONS"] }); +// Apply CSRF selectively (exclude webhooks and public routes) +app.use((req, res, next) => { + if ( + req.path.startsWith("/webhook/") || + req.path.startsWith("/secure/") || + req.path.startsWith("/api/") || + req.path.startsWith("/public/") + ) { + next(); + } else { + csrfProtection(req, res, next); + } +}); + +// Set CSRF token cookie +app.use((req: Request, res: Response, next: NextFunction) => { + if (req.csrfToken) { + const token = req.csrfToken(); + res.cookie("XSRF-TOKEN", token, { + httpOnly: false, + secure: isProduction(), + sameSite: isProduction() ? "none" : "lax", + domain: isProduction() ? ".worklenz.com" : undefined, + path: "/" + }); + } + next(); +}); + +// CSRF token refresh endpoint +app.get("/csrf-token", (req: Request, res: Response) => { + if (req.csrfToken) { + const token = req.csrfToken(); + res.cookie("XSRF-TOKEN", token, { + httpOnly: false, + secure: isProduction(), + sameSite: isProduction() ? "none" : "lax", + domain: isProduction() ? ".worklenz.com" : undefined, + path: "/" + }); + res.status(200).json({ done: true, message: "CSRF token refreshed" }); + } else { + res.status(500).json({ done: false, message: "Failed to generate CSRF token" }); + } +}); + +// Webhook endpoints (no CSRF required) app.post("/webhook/emails/bounce", safeControllerFunction(AwsSesController.handleBounceResponse)); app.post("/webhook/emails/complaints", safeControllerFunction(AwsSesController.handleComplaintResponse)); app.post("/webhook/emails/reply", safeControllerFunction(AwsSesController.handleReplies)); -app.use(flash()); -app.use(csurf({cookie: true})); - -app.use((req: Request, res: Response, next: NextFunction) => { - res.setHeader("Content-Security-Policy", CSP_POLICIES); - const token = req.csrfToken(); - res.cookie("XSRF-TOKEN", token); - res.locals.csrf = token; - next(); -}); - +// Static file serving if (isProduction()) { + app.use(express.static(path.join(__dirname, "build"), { + maxAge: "1y", + etag: false, + })); + + // Handle compressed files app.get("*.js", (req, res, next) => { if (req.header("Accept-Encoding")?.includes("br")) { req.url = `${req.url}.br`; @@ -116,61 +191,62 @@ if (isProduction()) { } next(); }); +} else { + app.use(express.static(path.join(__dirname, "public"))); } -app.use(express.static(path.join(__dirname, "public"))); -app.set("trust proxy", 1); -app.use(sessionMiddleware); - -app.use(passport.initialize()); -app.use(passport.session()); - +// API rate limiting const apiLimiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 1500, // Limit each IP to 2000 requests per `window` (here, per 15 minutes) - standardHeaders: false, // Return rate limit info in the `RateLimit-*` headers - legacyHeaders: false, // Disable the `X-RateLimit-*` headers -}); - -app.use((req, res, next) => { - const {send} = res; - res.send = function (obj) { - if (req.headers.accept?.includes("application/json")) - return send.call(this, `)]}',\n${JSON.stringify(obj)}`); - return send.call(this, obj); - }; - next(); + windowMs: 15 * 60 * 1000, + max: 1500, + standardHeaders: false, + legacyHeaders: false, }); +// Routes +app.use("/api/v1", apiLimiter, isLoggedIn, apiRouter); app.use("/secure", authRouter); app.use("/public", public_router); -app.use("/api/v1", isLoggedIn, apiRouter); -app.use("/", indexRouter); -if (isInternalServer()) +if (isInternalServer()) { app.use("/email-templates", emailTemplatesRouter); +} - -// catch 404 and forward to error handler -app.use((req: Request, res: Response) => { - res.locals.error_title = "404 Not Found."; - res.locals.error_message = `The requested URL ${req.url} was not found on this server.`; - res.locals.error_image = "/assets/images/404.webp"; - res.status(400); - res.render("error"); +// CSRF error handler +app.use((err: any, req: Request, res: Response, next: NextFunction) => { + if (err.code === "EBADCSRFTOKEN") { + return res.status(403).json({ + done: false, + message: "Invalid CSRF token", + body: null + }); + } + next(err); }); -// error handler -app.use((err: { message: string; status: number; }, _req: Request, res: Response) => { - // set locals, only providing error in development - res.locals.error_title = "500 Internal Server Error."; - res.locals.error_message = "Oops, something went wrong."; - res.locals.error_message2 = "Try to refresh this page or feel free to contact us if the problem persists."; - res.locals.error_image = "/assets/images/500.png"; - - // render the error page - res.status(err.status || 500); - res.render("error"); +// React app handling - serve index.html for all non-API routes +app.get("*", (req: Request, res: Response, next: NextFunction) => { + if (req.path.startsWith("/api/")) return next(); + res.sendFile(path.join(__dirname, isProduction() ? "build" : "public", "index.html")); }); -export default app; +// Global error handler +app.use((err: any, _req: Request, res: Response, _next: NextFunction) => { + const status = err.status || 500; + + if (res.headersSent) { + return; + } + + res.status(status); + + // Send structured error response + res.json({ + done: false, + message: isProduction() ? "Internal Server Error" : err.message, + body: null, + ...(process.env.NODE_ENV === "development" ? { stack: err.stack } : {}) + }); +}); + +export default app; \ No newline at end of file diff --git a/worklenz-backend/src/bin/www.ts b/worklenz-backend/src/bin/www.ts index 9e2061c0..4c18b967 100644 --- a/worklenz-backend/src/bin/www.ts +++ b/worklenz-backend/src/bin/www.ts @@ -95,8 +95,7 @@ function onListening() { ? `pipe ${addr}` : `port ${addr.port}`; - startCronJobs(); - // TODO - uncomment initRedis() + process.env.ENABLE_EMAIL_CRONJOBS === "true" && startCronJobs(); // void initRedis(); FileConstants.init(); void DbTaskStatusChangeListener.connect(); diff --git a/worklenz-backend/src/controllers/admin-center-controller.ts b/worklenz-backend/src/controllers/admin-center-controller.ts index 48e6e317..3c50c858 100644 --- a/worklenz-backend/src/controllers/admin-center-controller.ts +++ b/worklenz-backend/src/controllers/admin-center-controller.ts @@ -5,8 +5,19 @@ import db from "../config/db"; import {ServerResponse} from "../models/server-response"; import WorklenzControllerBase from "./worklenz-controller-base"; import HandleExceptions from "../decorators/handle-exceptions"; -import {getColor} from "../shared/utils"; +import {calculateMonthDays, getColor, megabytesToBytes} from "../shared/utils"; +import moment from "moment"; import {calculateStorage} from "../shared/s3"; +import {checkTeamSubscriptionStatus, getActiveTeamMemberCount, getCurrentProjectsCount, getFreePlanSettings, getOwnerIdByTeam, getTeamMemberCount, getUsedStorage} from "../shared/paddle-utils"; +import { + addModifier, + cancelSubscription, + changePlan, + generatePayLinkRequest, + pauseOrResumeSubscription, + updateUsers +} from "../shared/paddle-requests"; +import {statusExclude} from "../shared/constants"; import {NotificationsService} from "../services/notifications/notifications.service"; import {SocketEvents} from "../socket.io/events"; import {IO} from "../shared/io"; @@ -262,6 +273,384 @@ export default class AdminCenterController extends WorklenzControllerBase { return res.status(200).send(new ServerResponse(true, [], "Team updated successfully")); } + @HandleExceptions() + public static async getBillingInfo(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const q = `SELECT get_billing_info($1) AS billing_info;`; + const result = await db.query(q, [req.user?.owner_id]); + const [data] = result.rows; + + const validTillDate = moment(data.billing_info.trial_expire_date); + + const daysDifference = validTillDate.diff(moment(), "days"); + const dateString = calculateMonthDays(moment().format("YYYY-MM-DD"), data.billing_info.trial_expire_date); + + data.billing_info.expire_date_string = dateString; + + if (daysDifference < 0) { + data.billing_info.expire_date_string = `Your trial plan expired ${dateString} ago`; + } else if (daysDifference === 0 && daysDifference < 7) { + data.billing_info.expire_date_string = `Your trial plan expires today`; + } else { + data.billing_info.expire_date_string = `Your trial plan expires in ${dateString}.`; + } + + if (data.billing_info.billing_type === "year") data.billing_info.unit_price_per_month = data.billing_info.unit_price / 12; + + const teamMemberData = await getTeamMemberCount(req.user?.owner_id ?? ""); + const subscriptionData = await checkTeamSubscriptionStatus(req.user?.team_id ?? ""); + + data.billing_info.total_used = teamMemberData.user_count; + data.billing_info.total_seats = subscriptionData.quantity; + + return res.status(200).send(new ServerResponse(true, data.billing_info)); + } + + @HandleExceptions() + public static async getBillingTransactions(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const q = `SELECT subscription_payment_id, + event_time::date, + (next_bill_date::DATE - INTERVAL '1 day')::DATE AS next_bill_date, + currency, + receipt_url, + payment_method, + status, + payment_status + FROM licensing_payment_details + WHERE user_id = $1 + ORDER BY created_at DESC;`; + const result = await db.query(q, [req.user?.owner_id]); + + return res.status(200).send(new ServerResponse(true, result.rows)); + } + + @HandleExceptions() + public static async getBillingCharges(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const q = `SELECT (SELECT name FROM licensing_pricing_plans lpp WHERE id = lus.plan_id), + unit_price::numeric, + currency, + status, + quantity, + unit_price::numeric * quantity AS amount, + (SELECT event_time + FROM licensing_payment_details lpd + WHERE lpd.user_id = lus.user_id + ORDER BY created_at DESC + LIMIT 1)::DATE AS start_date, + (next_bill_date::DATE - INTERVAL '1 day')::DATE AS end_date + FROM licensing_user_subscriptions lus + WHERE user_id = $1;`; + const result = await db.query(q, [req.user?.owner_id]); + + const countQ = `SELECT subscription_id + FROM licensing_user_subscription_modifiers + WHERE subscription_id = (SELECT subscription_id + FROM licensing_user_subscriptions + WHERE user_id = $1 + AND status != 'deleted' + LIMIT 1)::INT;`; + const countResult = await db.query(countQ, [req.user?.owner_id]); + + return res.status(200).send(new ServerResponse(true, {plan_charges: result.rows, modifiers: countResult.rows})); + } + + @HandleExceptions() + public static async getBillingModifiers(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const q = `SELECT created_at + FROM licensing_user_subscription_modifiers + WHERE subscription_id = (SELECT subscription_id + FROM licensing_user_subscriptions + WHERE user_id = $1 + AND status != 'deleted' + LIMIT 1)::INT;`; + const result = await db.query(q, [req.user?.owner_id]); + + return res.status(200).send(new ServerResponse(true, result.rows)); + } + + @HandleExceptions() + public static async getBillingConfiguration(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const q = `SELECT name, + email, + organization_name AS company_name, + contact_number AS phone, + address_line_1, + address_line_2, + city, + state, + postal_code, + country + FROM organizations + LEFT JOIN users u ON organizations.user_id = u.id + WHERE u.id = $1;`; + const result = await db.query(q, [req.user?.owner_id]); + const [data] = result.rows; + + return res.status(200).send(new ServerResponse(true, data)); + } + + @HandleExceptions() + public static async updateBillingConfiguration(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const {company_name, phone, address_line_1, address_line_2, city, state, postal_code, country} = req.body; + const q = `UPDATE organizations + SET organization_name = $1, + contact_number = $2, + address_line_1 = $3, + address_line_2 = $4, + city = $5, + state = $6, + postal_code = $7, + country = $8 + WHERE user_id = $9;`; + const result = await db.query(q, [company_name, phone, address_line_1, address_line_2, city, state, postal_code, country, req.user?.owner_id]); + const [data] = result.rows; + + return res.status(200).send(new ServerResponse(true, data, "Configuration Updated")); + } + + @HandleExceptions() + public static async upgradePlan(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const {plan} = req.query; + + const obj = await getTeamMemberCount(req.user?.owner_id ?? ""); + const axiosResponse = await generatePayLinkRequest(obj, plan as string, req.user?.owner_id, req.user?.id); + + return res.status(200).send(new ServerResponse(true, axiosResponse.body)); + } + + @HandleExceptions() + public static async getPlans(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const q = `SELECT + ls.default_monthly_plan AS monthly_plan_id, + lp_monthly.name AS monthly_plan_name, + ls.default_annual_plan AS annual_plan_id, + lp_monthly.recurring_price AS monthly_price, + lp_annual.name AS annual_plan_name, + lp_annual.recurring_price AS annual_price, + ls.team_member_limit, + ls.projects_limit, + ls.free_tier_storage + FROM + licensing_settings ls + JOIN + licensing_pricing_plans lp_monthly ON ls.default_monthly_plan = lp_monthly.id + JOIN + licensing_pricing_plans lp_annual ON ls.default_annual_plan = lp_annual.id;`; + const result = await db.query(q, []); + const [data] = result.rows; + + const obj = await getTeamMemberCount(req.user?.owner_id ?? ""); + + data.team_member_limit = data.team_member_limit === 0 ? "Unlimited" : data.team_member_limit; + data.projects_limit = data.projects_limit === 0 ? "Unlimited" : data.projects_limit; + data.free_tier_storage = `${data.free_tier_storage}MB`; + data.current_user_count = obj.user_count; + data.annual_price = (data.annual_price / 12).toFixed(2); + + return res.status(200).send(new ServerResponse(true, data)); + } + + @HandleExceptions() + public static async purchaseStorage(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const q = `SELECT subscription_id + FROM licensing_user_subscriptions lus + WHERE user_id = $1;`; + const result = await db.query(q, [req.user?.owner_id]); + const [data] = result.rows; + + await addModifier(data.subscription_id); + + return res.status(200).send(new ServerResponse(true, data)); + } + + @HandleExceptions() + public static async changePlan(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const {plan} = req.query; + + const q = `SELECT subscription_id + FROM licensing_user_subscriptions lus + WHERE user_id = $1;`; + const result = await db.query(q, [req.user?.owner_id]); + const [data] = result.rows; + + const axiosResponse = await changePlan(plan as string, data.subscription_id); + + return res.status(200).send(new ServerResponse(true, axiosResponse.body)); + } + + @HandleExceptions() + public static async cancelPlan(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + if (!req.user?.owner_id) return res.status(200).send(new ServerResponse(false, "Invalid Request.")); + + const q = `SELECT subscription_id + FROM licensing_user_subscriptions lus + WHERE user_id = $1;`; + const result = await db.query(q, [req.user?.owner_id]); + const [data] = result.rows; + + const axiosResponse = await cancelSubscription(data.subscription_id, req.user?.owner_id); + + return res.status(200).send(new ServerResponse(true, axiosResponse.body)); + } + + @HandleExceptions() + public static async pauseSubscription(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + if (!req.user?.owner_id) return res.status(200).send(new ServerResponse(false, "Invalid Request.")); + + const q = `SELECT subscription_id + FROM licensing_user_subscriptions lus + WHERE user_id = $1;`; + const result = await db.query(q, [req.user?.owner_id]); + const [data] = result.rows; + + const axiosResponse = await pauseOrResumeSubscription(data.subscription_id, req.user?.owner_id, true); + + return res.status(200).send(new ServerResponse(true, axiosResponse.body)); + } + + @HandleExceptions() + public static async resumeSubscription(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + if (!req.user?.owner_id) return res.status(200).send(new ServerResponse(false, "Invalid Request.")); + + const q = `SELECT subscription_id + FROM licensing_user_subscriptions lus + WHERE user_id = $1;`; + const result = await db.query(q, [req.user?.owner_id]); + const [data] = result.rows; + + const axiosResponse = await pauseOrResumeSubscription(data.subscription_id, req.user?.owner_id, false); + + return res.status(200).send(new ServerResponse(true, axiosResponse.body)); + } + + @HandleExceptions() + public static async getBillingStorageInfo(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const q = `SELECT trial_in_progress, + trial_expire_date, + ud.storage, + (SELECT name AS plan_name FROM licensing_pricing_plans WHERE id = lus.plan_id), + (SELECT default_trial_storage FROM licensing_settings), + (SELECT storage_addon_size FROM licensing_settings), + (SELECT storage_addon_price FROM licensing_settings) + FROM organizations ud + LEFT JOIN users u ON ud.user_id = u.id + LEFT JOIN licensing_user_subscriptions lus ON u.id = lus.user_id + WHERE ud.user_id = $1;`; + const result = await db.query(q, [req.user?.owner_id]); + const [data] = result.rows; + + return res.status(200).send(new ServerResponse(true, data)); + } + + @HandleExceptions() + public static async getAccountStorage(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const teamsQ = `SELECT id + FROM teams + WHERE user_id = $1;`; + const teamsResponse = await db.query(teamsQ, [req.user?.owner_id]); + + const storageQ = `SELECT storage + FROM organizations + WHERE user_id = $1;`; + const result = await db.query(storageQ, [req.user?.owner_id]); + const [data] = result.rows; + + const storage: any = {}; + storage.used = 0; + storage.total = data.storage; + + for (const team of teamsResponse.rows) { + storage.used += await calculateStorage(team.id); + } + + storage.remaining = (storage.total * 1024 * 1024 * 1024) - storage.used; + storage.used_percent = Math.ceil((storage.used / (storage.total * 1024 * 1024 * 1024)) * 10000) / 100; + + return res.status(200).send(new ServerResponse(true, storage)); + } + + @HandleExceptions() + public static async getCountries(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const q = `SELECT id, name, code + FROM countries + ORDER BY name;`; + const result = await db.query(q, []); + + return res.status(200).send(new ServerResponse(true, result.rows || [])); + } + + @HandleExceptions() + public static async switchToFreePlan(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const { id: teamId } = req.params; + + const limits = await getFreePlanSettings(); + const ownerId = await getOwnerIdByTeam(teamId); + + if (limits && ownerId) { + if (parseInt(limits.team_member_limit) !== 0) { + const teamMemberCount = await getTeamMemberCount(ownerId); + if (parseInt(teamMemberCount) > parseInt(limits.team_member_limit)) { + return res.status(200).send(new ServerResponse(false, [], `Sorry, the free plan cannot have more than ${limits.team_member_limit} members.`)); + } + } + + const projectsCount = await getCurrentProjectsCount(ownerId); + if (parseInt(projectsCount) > parseInt(limits.projects_limit)) { + return res.status(200).send(new ServerResponse(false, [], `Sorry, the free plan cannot have more than ${limits.projects_limit} projects.`)); + } + + const usedStorage = await getUsedStorage(ownerId); + if (parseInt(usedStorage) > megabytesToBytes(parseInt(limits.free_tier_storage))) { + return res.status(200).send(new ServerResponse(false, [], `Sorry, the free plan cannot exceed ${limits.free_tier_storage}MB of storage.`)); + } + + const update_q = `UPDATE organizations + SET license_type_id = (SELECT id FROM sys_license_types WHERE key = 'FREE'), + trial_in_progress = FALSE, + subscription_status = 'free', + storage = (SELECT free_tier_storage FROM licensing_settings) + WHERE user_id = $1;`; + await db.query(update_q, [ownerId]); + + return res.status(200).send(new ServerResponse(true, [], "Your plan has been successfully switched to the Free Plan.")); + } + return res.status(200).send(new ServerResponse(false, [], "Failed to switch to the Free Plan. Please try again later.")); + } + + @HandleExceptions() + public static async redeem(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const { code } = req.body; + + const q = `SELECT * FROM licensing_coupon_codes WHERE coupon_code = $1 AND is_redeemed IS FALSE AND is_refunded IS FALSE;`; + const result = await db.query(q, [code]); + const [data] = result.rows; + + if (!result.rows.length) + return res.status(200).send(new ServerResponse(false, [], "Redeem Code verification Failed! Please try again.")); + + const checkQ = `SELECT sum(team_members_limit) AS team_member_total FROM licensing_coupon_codes WHERE redeemed_by = $1 AND is_redeemed IS TRUE;`; + const checkResult = await db.query(checkQ, [req.user?.owner_id]); + const [total] = checkResult.rows; + + if (parseInt(total.team_member_total) > 50) + return res.status(200).send(new ServerResponse(false, [], "Maximum number of codes redeemed!")); + + const updateQ = `UPDATE licensing_coupon_codes + SET is_redeemed = TRUE, redeemed_at = CURRENT_TIMESTAMP, + redeemed_by = $1 + WHERE id = $2;`; + await db.query(updateQ, [req.user?.owner_id, data.id]); + + const updateQ2 = `UPDATE organizations + SET subscription_status = 'life_time_deal', + trial_in_progress = FALSE, + storage = (SELECT sum(storage_limit) FROM licensing_coupon_codes WHERE redeemed_by = $1), + license_type_id = (SELECT id FROM sys_license_types WHERE key = 'LIFE_TIME_DEAL') + WHERE user_id = $1;`; + await db.query(updateQ2, [req.user?.owner_id]); + + return res.status(200).send(new ServerResponse(true, [], "Code redeemed successfully!")); + } + @HandleExceptions() public static async deleteTeam(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { const {id} = req.params; @@ -284,6 +673,11 @@ export default class AdminCenterController extends WorklenzControllerBase { if (!id || !teamId) return res.status(200).send(new ServerResponse(false, "Required fields are missing.")); + // check subscription status + const subscriptionData = await checkTeamSubscriptionStatus(teamId); + if (statusExclude.includes(subscriptionData.subscription_status)) { + return res.status(200).send(new ServerResponse(false, "Please check your subscription status.")); + } const q = `SELECT remove_team_member($1, $2, $3) AS member;`; const result = await db.query(q, [id, req.user?.id, teamId]); @@ -291,6 +685,22 @@ export default class AdminCenterController extends WorklenzControllerBase { const message = `You have been removed from ${req.user?.team_name} by ${req.user?.name}`; + // if (subscriptionData.status === "trialing") break; + if (!subscriptionData.is_credit && !subscriptionData.is_custom) { + if (subscriptionData.subscription_status === "active" && subscriptionData.quantity > 0) { + + const obj = await getActiveTeamMemberCount(req.user?.owner_id ?? ""); + + const userActiveInOtherTeams = await this.checkIfUserActiveInOtherTeams(req.user?.owner_id as string, req.query?.email as string); + + if (!userActiveInOtherTeams) { + const response = await updateUsers(subscriptionData.subscription_id, obj.user_count); + if (!response.body.subscription_id) return res.status(200).send(new ServerResponse(false, response.message || "Please check your subscription.")); + } + + } + } + NotificationsService.sendNotification({ receiver_socket_id: data.socket_id, message, @@ -305,5 +715,49 @@ export default class AdminCenterController extends WorklenzControllerBase { return res.status(200).send(new ServerResponse(true, result.rows)); } + @HandleExceptions() + public static async getFreePlanLimits(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const limits = await getFreePlanSettings(); + return res.status(200).send(new ServerResponse(true, limits || {})); + } + + @HandleExceptions() + public static async getOrganizationProjects(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const { searchQuery, size, offset } = this.toPaginationOptions(req.query, ["p.name"]); + + const countQ = `SELECT COUNT(*) AS total + FROM projects p + JOIN teams t ON p.team_id = t.id + JOIN organizations o ON t.organization_id = o.id + WHERE o.user_id = $1;`; + const countResult = await db.query(countQ, [req.user?.owner_id]); + + // Query to get the project data + const dataQ = `SELECT p.id, + p.name, + t.name AS team_name, + p.created_at, + pm.member_count + FROM projects p + JOIN teams t ON p.team_id = t.id + JOIN organizations o ON t.organization_id = o.id + LEFT JOIN ( + SELECT project_id, COUNT(*) AS member_count + FROM project_members + GROUP BY project_id + ) pm ON p.id = pm.project_id + WHERE o.user_id = $1 ${searchQuery} + ORDER BY p.name + OFFSET $2 LIMIT $3;`; + + const result = await db.query(dataQ, [req.user?.owner_id, offset, size]); + + const response = { + total: countResult.rows[0]?.total ?? 0, + data: result.rows ?? [] + }; + + return res.status(200).send(new ServerResponse(true, response)); + } } diff --git a/worklenz-backend/src/controllers/attachment-controller.ts b/worklenz-backend/src/controllers/attachment-controller.ts index e8fef573..5f5bb866 100644 --- a/worklenz-backend/src/controllers/attachment-controller.ts +++ b/worklenz-backend/src/controllers/attachment-controller.ts @@ -2,7 +2,8 @@ import { IWorkLenzRequest } from "../interfaces/worklenz-request"; import { IWorkLenzResponse } from "../interfaces/worklenz-response"; import db from "../config/db"; -import { humanFileSize, log_error, smallId } from "../shared/utils"; +import { humanFileSize, smallId } from "../shared/utils"; +import { getStorageUrl } from "../shared/constants"; import { ServerResponse } from "../models/server-response"; import { createPresignedUrlWithClient, @@ -12,16 +13,10 @@ import { getRootDir, uploadBase64, uploadBuffer -} from "../shared/s3"; +} from "../shared/storage"; import WorklenzControllerBase from "./worklenz-controller-base"; import HandleExceptions from "../decorators/handle-exceptions"; -const {S3_URL} = process.env; - -if (!S3_URL) { - log_error("Invalid S3_URL. Please check .env file."); -} - export default class AttachmentController extends WorklenzControllerBase { @HandleExceptions() @@ -42,7 +37,7 @@ export default class AttachmentController extends WorklenzControllerBase { req.user?.id, size, type, - `${S3_URL}/${getRootDir()}` + `${getStorageUrl()}/${getRootDir()}` ]); const [data] = result.rows; @@ -86,7 +81,7 @@ export default class AttachmentController extends WorklenzControllerBase { FROM task_attachments WHERE task_id = $1; `; - const result = await db.query(q, [req.params.id, `${S3_URL}/${getRootDir()}`]); + const result = await db.query(q, [req.params.id, `${getStorageUrl()}/${getRootDir()}`]); for (const item of result.rows) item.size = humanFileSize(item.size); @@ -121,7 +116,7 @@ export default class AttachmentController extends WorklenzControllerBase { LEFT JOIN tasks t ON task_attachments.task_id = t.id WHERE task_attachments.project_id = $1) rec; `; - const result = await db.query(q, [req.params.id, `${S3_URL}/${getRootDir()}`, size, offset]); + const result = await db.query(q, [req.params.id, `${getStorageUrl()}/${getRootDir()}`, size, offset]); const [data] = result.rows; for (const item of data?.attachments.data || []) @@ -135,26 +130,29 @@ export default class AttachmentController extends WorklenzControllerBase { const q = `DELETE FROM task_attachments WHERE id = $1 - RETURNING CONCAT($2::TEXT, '/', team_id, '/', project_id, '/', id, '.', type) AS key;`; - const result = await db.query(q, [req.params.id, getRootDir()]); + RETURNING team_id, project_id, id, type;`; + const result = await db.query(q, [req.params.id]); const [data] = result.rows; - if (data?.key) - void deleteObject(data.key); + if (data) { + const key = getKey(data.team_id, data.project_id, data.id, data.type); + void deleteObject(key); + } return res.status(200).send(new ServerResponse(true, result.rows)); } @HandleExceptions() public static async download(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { - const q = `SELECT CONCAT($2::TEXT, '/', team_id, '/', project_id, '/', id, '.', type) AS key + const q = `SELECT team_id, project_id, id, type FROM task_attachments WHERE id = $1;`; - const result = await db.query(q, [req.query.id, getRootDir()]); + const result = await db.query(q, [req.query.id]); const [data] = result.rows; - if (data?.key) { - const url = await createPresignedUrlWithClient(data.key, req.query.file as string); + if (data) { + const key = getKey(data.team_id, data.project_id, data.id, data.type); + const url = await createPresignedUrlWithClient(key, req.query.file as string); return res.status(200).send(new ServerResponse(true, url)); } diff --git a/worklenz-backend/src/controllers/auth-controller.ts b/worklenz-backend/src/controllers/auth-controller.ts index 1a5043ed..8364d59c 100644 --- a/worklenz-backend/src/controllers/auth-controller.ts +++ b/worklenz-backend/src/controllers/auth-controller.ts @@ -12,6 +12,9 @@ import WorklenzControllerBase from "./worklenz-controller-base"; import HandleExceptions from "../decorators/handle-exceptions"; import {PasswordStrengthChecker} from "../shared/password-strength-check"; import FileConstants from "../shared/file-constants"; +import axios from "axios"; +import {log_error} from "../shared/utils"; +import {DEFAULT_ERROR_MESSAGE} from "../shared/constants"; export default class AuthController extends WorklenzControllerBase { /** This just send ok response to the client when the request came here through the sign-up-validator */ @@ -42,11 +45,20 @@ export default class AuthController extends WorklenzControllerBase { } public static logout(req: IWorkLenzRequest, res: IWorkLenzResponse) { - req.logout(() => true); - req.session.destroy(() => { - res.redirect("/"); + req.logout((err) => { + if (err) { + console.error("Logout error:", err); + return res.status(500).send(new AuthResponse(null, true, {}, "Logout failed", null)); + } + + req.session.destroy((destroyErr) => { + if (destroyErr) { + console.error("Session destroy error:", destroyErr); + } + res.status(200).send(new AuthResponse(null, req.isAuthenticated(), {}, null, null)); + }); }); - } + } private static async destroyOtherSessions(userId: string, sessionId: string) { try { @@ -138,4 +150,25 @@ export default class AuthController extends WorklenzControllerBase { } return res.status(200).send(new ServerResponse(false, null, "Invalid Request. Please try again.")); } + + @HandleExceptions({logWithError: "body"}) + public static async verifyCaptcha(req: IWorkLenzRequest, res: IWorkLenzResponse) { + const {token} = req.body; + const secretKey = process.env.GOOGLE_CAPTCHA_SECRET_KEY; + try { + const response = await axios.post( + `https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${token}` + ); + + const {success, score} = response.data; + + if (success && score > 0.5) { + return res.status(200).send(new ServerResponse(true, null, null)); + } + return res.status(400).send(new ServerResponse(false, null, "Please try again later.").withTitle("Error")); + } catch (error) { + log_error(error); + res.status(500).send(new ServerResponse(false, null, DEFAULT_ERROR_MESSAGE)); + } + } } diff --git a/worklenz-backend/src/controllers/billing-controller.ts b/worklenz-backend/src/controllers/billing-controller.ts new file mode 100644 index 00000000..cf8d7faa --- /dev/null +++ b/worklenz-backend/src/controllers/billing-controller.ts @@ -0,0 +1,288 @@ +import { IWorkLenzRequest } from "../interfaces/worklenz-request"; +import { IWorkLenzResponse } from "../interfaces/worklenz-response"; + +import db from "../config/db"; +import { ServerResponse } from "../models/server-response"; +import WorklenzControllerBase from "./worklenz-controller-base"; +import HandleExceptions from "../decorators/handle-exceptions"; +import { getTeamMemberCount } from "../shared/paddle-utils"; +import { generatePayLinkRequest, updateUsers } from "../shared/paddle-requests"; + +import CryptoJS from "crypto-js"; +import moment from "moment"; +import axios from "axios"; + +import crypto from "crypto"; +import fs from "fs"; +import path from "path"; +import { log_error } from "../shared/utils"; +import { sendEmail } from "../shared/email"; + +export default class BillingController extends WorklenzControllerBase { + public static async getInitialCharge(count: number) { + if (!count) throw new Error("No selected plan detected."); + + const baseRate = 4990; + const firstTier = 15; + const secondTierEnd = 200; + + if (count <= firstTier) { + return baseRate; + } else if (count <= secondTierEnd) { + return baseRate + (count - firstTier) * 300; + } + return baseRate + (secondTierEnd - firstTier) * 300 + (count - secondTierEnd) * 200; + + } + + public static async getBillingMonth() { + const startDate = moment().format("YYYYMMDD"); + const endDate = moment().add(1, "month").subtract(1, "day").format("YYYYMMDD"); + + return `${startDate} - ${endDate}`; + } + + public static async chargeInitialPayment(signature: string, data: any) { + const config = { + method: "post", + maxBodyLength: Infinity, + url: process.env.DP_URL, + headers: { + "Content-Type": "application/json", + "Signature": signature, + "x-api-key": process.env.DP_API_KEY + }, + data + }; + + axios.request(config) + .then((response) => { + console.log(JSON.stringify(response.data)); + + }) + .catch((error) => { + console.log(error); + }); + } + + public static async saveLocalTransaction(signature: string, data: any) { + try { + const q = `INSERT INTO transactions (status, transaction_id, transaction_status, description, date_time, reference, amount, card_number) +VALUES ($1, $2, $3);`; + const result = await db.query(q, []); + } catch (error) { + log_error(error); + } + } + + @HandleExceptions() + public static async upgradeToPaidPlan(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const { plan, seatCount } = req.query; + + const teamMemberData = await getTeamMemberCount(req.user?.owner_id ?? ""); + teamMemberData.user_count = seatCount as string; + const axiosResponse = await generatePayLinkRequest(teamMemberData, plan as string, req.user?.owner_id, req.user?.id); + + return res.status(200).send(new ServerResponse(true, axiosResponse.body)); + } + + @HandleExceptions() + public static async addMoreSeats(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const { seatCount } = req.body; + + const q = `SELECT subscription_id + FROM licensing_user_subscriptions lus + WHERE user_id = $1;`; + const result = await db.query(q, [req.user?.owner_id]); + const [data] = result.rows; + + const response = await updateUsers(data.subscription_id, seatCount); + + if (!response.body.subscription_id) { + return res.status(200).send(new ServerResponse(false, null, response.message || "Please check your subscription.")); + } + return res.status(200).send(new ServerResponse(true, null, "Your purchase has been successfully completed!").withTitle("Done")); + } + + @HandleExceptions() + public static async getDirectPayObject(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const { seatCount } = req.query; + if (!seatCount) return res.status(200).send(new ServerResponse(false, null)); + const email = req.user?.email; + const name = req.user?.name; + const amount = await this.getInitialCharge(parseInt(seatCount as string)); + const uniqueTimestamp = moment().format("YYYYMMDDHHmmss"); + const billingMonth = await this.getBillingMonth(); + + const { DP_MERCHANT_ID, DP_SECRET_KEY, DP_STAGE } = process.env; + + const payload = { + merchant_id: DP_MERCHANT_ID, + amount: 10, + type: "RECURRING", + order_id: `WORKLENZ_${email}_${uniqueTimestamp}`, + currency: "LKR", + return_url: null, + response_url: null, + first_name: name, + last_name: null, + phone: null, + email, + description: `${name} (${email})`, + page_type: "IN_APP", + logo: "https://app.worklenz.com/assets/icons/icon-96x96.png", + start_date: moment().format("YYYY-MM-DD"), + do_initial_payment: 1, + interval: 1, + }; + + const encodePayload = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(JSON.stringify(payload))); + const signature = CryptoJS.HmacSHA256(encodePayload, DP_SECRET_KEY as string); + + return res.status(200).send(new ServerResponse(true, { signature: signature.toString(CryptoJS.enc.Hex), dataString: encodePayload, stage: DP_STAGE })); + } + + @HandleExceptions() + public static async saveTransactionData(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const { status, card, transaction, seatCount } = req.body; + const { DP_MERCHANT_ID, DP_STAGE } = process.env; + + const email = req.user?.email; + + const amount = await this.getInitialCharge(parseInt(seatCount as string)); + const uniqueTimestamp = moment().format("YYYYMMDDHHmmss"); + const billingMonth = await this.getBillingMonth(); + + const values = [ + status, + card?.id, + card?.number, + card?.brand, + card?.type, + card?.issuer, + card?.expiry?.year, + card?.expiry?.month, + card?.walletId, + transaction?.id, + transaction?.status, + transaction?.amount || 0, + transaction?.currency || null, + transaction?.channel || null, + transaction?.dateTime || null, + transaction?.message || null, + transaction?.description || null, + req.user?.id, + req.user?.owner_id, + ]; + + const q = `INSERT INTO licensing_lkr_payments ( + status, card_id, card_number, card_brand, card_type, card_issuer, + card_expiry_year, card_expiry_month, wallet_id, + transaction_id, transaction_status, transaction_amount, + transaction_currency, transaction_channel, transaction_datetime, + transaction_message, transaction_description, user_id, owner_id + ) + VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19 + );`; + await db.query(q, values); + + if (transaction.status === "SUCCESS") { + const payload = { + "merchantId": DP_MERCHANT_ID, + "reference": `WORKLENZ_${email}_${uniqueTimestamp}`, + "type": "CARD_PAY", + "cardId": card.id, + "refCode": req.user?.id, + amount, + "currency": "LKR" + }; + const dataString = Object.values(payload).join(""); + const { DP_STAGE } = process.env; + + const pemFile = DP_STAGE === "PROD" ? "src/keys/PRIVATE_KEY_PROD.pem" : `src/keys/PRIVATE_KEY_DEV.pem`; + + const privateKeyTest = fs.readFileSync(path.resolve(pemFile), "utf8"); + const sign = crypto.createSign("SHA256"); + sign.update(dataString); + sign.end(); + + const signature = sign.sign(privateKeyTest); + const byteArray = new Uint8Array(signature); + let byteString = ""; + for (let i = 0; i < byteArray.byteLength; i++) { + byteString += String.fromCharCode(byteArray[i]); + } + const base64Signature = btoa(byteString); + + this.chargeInitialPayment(base64Signature, payload); + } + + return res.status(200).send(new ServerResponse(true, null, "Your purchase has been successfully completed!").withTitle("Done")); + } + + @HandleExceptions() + public static async getCardList(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const payload = { + "merchantId": "RT02300", + "reference": "1234", + "type": "LIST_CARD" + }; + + const { DP_STAGE } = process.env; + + const dataString = `RT023001234LIST_CARD`; + const pemFile = DP_STAGE === "PROD" ? "src/keys/PRIVATE_KEY_PROD.pem" : `src/keys/PRIVATE_KEY_DEV.pem`; + + const privateKeyTest = fs.readFileSync(path.resolve(pemFile), "utf8"); + const sign = crypto.createSign("SHA256"); + sign.update(dataString); + sign.end(); + + const signature = sign.sign(privateKeyTest); + const byteArray = new Uint8Array(signature); + let byteString = ""; + for (let i = 0; i < byteArray.byteLength; i++) { + byteString += String.fromCharCode(byteArray[i]); + } + const base64Signature = btoa(byteString); + // const signature = CryptoJS.HmacSHA256(dataString, DP_SECRET_KEY as string).toString(CryptoJS.enc.Hex); + + return res.status(200).send(new ServerResponse(true, { signature: base64Signature, dataString })); + } + + @HandleExceptions() + public static async contactUs(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const { contactNo } = req.query; + + if (!contactNo) { + return res.status(200).send(new ServerResponse(false, null, "Contact number is required!")); + } + + const html = ` + + + + + Worklenz Local Billing - Contact Information + + +
+

Worklenz Local Billing - Contact Information

+

Name: ${req.user?.name}

+

Contact No: ${contactNo as string}

+

Email: ${req.user?.email}

+
+ + `; + const to = [process.env.CONTACT_US_EMAIL || "chamika@ceydigital.com"]; + + sendEmail({ + to, + subject: "Worklenz - Local billing contact.", + html + }); + return res.status(200).send(new ServerResponse(true, null, "Your contact information has been sent successfully.")); + } + +} \ No newline at end of file diff --git a/worklenz-backend/src/controllers/clients-controller.ts b/worklenz-backend/src/controllers/clients-controller.ts index c654ea9f..a19fcf19 100644 --- a/worklenz-backend/src/controllers/clients-controller.ts +++ b/worklenz-backend/src/controllers/clients-controller.ts @@ -12,7 +12,7 @@ export default class ClientsController extends WorklenzControllerBase { @HandleExceptions() public static async create(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { - const q = `INSERT INTO clients (name, team_id) VALUES ($1, $2);`; + const q = `INSERT INTO clients (name, team_id) VALUES ($1, $2) RETURNING id, name;`; const result = await db.query(q, [req.body.name, req.user?.team_id || null]); const [data] = result.rows; return res.status(200).send(new ServerResponse(true, data)); diff --git a/worklenz-backend/src/controllers/custom-columns-controller.ts b/worklenz-backend/src/controllers/custom-columns-controller.ts new file mode 100644 index 00000000..962a5049 --- /dev/null +++ b/worklenz-backend/src/controllers/custom-columns-controller.ts @@ -0,0 +1,531 @@ +import { IWorkLenzRequest } from "../interfaces/worklenz-request"; +import { IWorkLenzResponse } from "../interfaces/worklenz-response"; + +import db from "../config/db"; +import { ServerResponse } from "../models/server-response"; +import WorklenzControllerBase from "./worklenz-controller-base"; +import HandleExceptions from "../decorators/handle-exceptions"; + +export default class CustomcolumnsController extends WorklenzControllerBase { + @HandleExceptions() + public static async create( + req: IWorkLenzRequest, + res: IWorkLenzResponse + ): Promise { + const { + project_id, + name, + key, + field_type, + width = 150, + is_visible = true, + configuration, + } = req.body; + + // Start a transaction since we're inserting into multiple tables + const client = await db.pool.connect(); + + try { + await client.query("BEGIN"); + + // 1. Insert the main custom column + const columnQuery = ` + INSERT INTO cc_custom_columns ( + project_id, name, key, field_type, width, is_visible, is_custom_column + ) VALUES ($1, $2, $3, $4, $5, $6, true) + RETURNING id; + `; + const columnResult = await client.query(columnQuery, [ + project_id, + name, + key, + field_type, + width, + is_visible, + ]); + const columnId = columnResult.rows[0].id; + + // 2. Insert the column configuration + const configQuery = ` + INSERT INTO cc_column_configurations ( + column_id, field_title, field_type, number_type, + decimals, label, label_position, preview_value, + expression, first_numeric_column_key, second_numeric_column_key + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) + RETURNING id; + `; + await client.query(configQuery, [ + columnId, + configuration.field_title, + configuration.field_type, + configuration.number_type || null, + configuration.decimals || null, + configuration.label || null, + configuration.label_position || null, + configuration.preview_value || null, + configuration.expression || null, + configuration.first_numeric_column_key || null, + configuration.second_numeric_column_key || null, + ]); + + // 3. Insert selection options if present + if ( + configuration.selections_list && + configuration.selections_list.length > 0 + ) { + const selectionQuery = ` + INSERT INTO cc_selection_options ( + column_id, selection_id, selection_name, selection_color, selection_order + ) VALUES ($1, $2, $3, $4, $5); + `; + for (const [ + index, + selection, + ] of configuration.selections_list.entries()) { + await client.query(selectionQuery, [ + columnId, + selection.selection_id, + selection.selection_name, + selection.selection_color, + index, + ]); + } + } + + // 4. Insert label options if present + if (configuration.labels_list && configuration.labels_list.length > 0) { + const labelQuery = ` + INSERT INTO cc_label_options ( + column_id, label_id, label_name, label_color, label_order + ) VALUES ($1, $2, $3, $4, $5); + `; + for (const [index, label] of configuration.labels_list.entries()) { + await client.query(labelQuery, [ + columnId, + label.label_id, + label.label_name, + label.label_color, + index, + ]); + } + } + + await client.query("COMMIT"); + + // Fetch the complete column data + const getColumnQuery = ` + SELECT + cc.*, + cf.field_title, + cf.number_type, + cf.decimals, + cf.label, + cf.label_position, + cf.preview_value, + cf.expression, + cf.first_numeric_column_key, + cf.second_numeric_column_key, + ( + SELECT json_agg( + json_build_object( + 'selection_id', so.selection_id, + 'selection_name', so.selection_name, + 'selection_color', so.selection_color + ) + ) + FROM cc_selection_options so + WHERE so.column_id = cc.id + ) as selections_list, + ( + SELECT json_agg( + json_build_object( + 'label_id', lo.label_id, + 'label_name', lo.label_name, + 'label_color', lo.label_color + ) + ) + FROM cc_label_options lo + WHERE lo.column_id = cc.id + ) as labels_list + FROM cc_custom_columns cc + LEFT JOIN cc_column_configurations cf ON cf.column_id = cc.id + WHERE cc.id = $1; + `; + const result = await client.query(getColumnQuery, [columnId]); + const [data] = result.rows; + + return res.status(200).send(new ServerResponse(true, data)); + } catch (error) { + await client.query("ROLLBACK"); + throw error; + } finally { + client.release(); + } + } + + @HandleExceptions() + public static async get( + req: IWorkLenzRequest, + res: IWorkLenzResponse + ): Promise { + const { project_id } = req.query; + + const q = ` + SELECT + cc.*, + cf.field_title, + cf.number_type, + cf.decimals, + cf.label, + cf.label_position, + cf.preview_value, + cf.expression, + cf.first_numeric_column_key, + cf.second_numeric_column_key, + ( + SELECT json_agg( + json_build_object( + 'selection_id', so.selection_id, + 'selection_name', so.selection_name, + 'selection_color', so.selection_color + ) + ) + FROM cc_selection_options so + WHERE so.column_id = cc.id + ) as selections_list, + ( + SELECT json_agg( + json_build_object( + 'label_id', lo.label_id, + 'label_name', lo.label_name, + 'label_color', lo.label_color + ) + ) + FROM cc_label_options lo + WHERE lo.column_id = cc.id + ) as labels_list + FROM cc_custom_columns cc + LEFT JOIN cc_column_configurations cf ON cf.column_id = cc.id + WHERE cc.project_id = $1 + ORDER BY cc.created_at DESC; + `; + const result = await db.query(q, [project_id]); + return res.status(200).send(new ServerResponse(true, result.rows)); + } + + @HandleExceptions() + public static async getById( + req: IWorkLenzRequest, + res: IWorkLenzResponse + ): Promise { + const { id } = req.params; + + const q = ` + SELECT + cc.*, + cf.field_title, + cf.number_type, + cf.decimals, + cf.label, + cf.label_position, + cf.preview_value, + cf.expression, + cf.first_numeric_column_key, + cf.second_numeric_column_key, + ( + SELECT json_agg( + json_build_object( + 'selection_id', so.selection_id, + 'selection_name', so.selection_name, + 'selection_color', so.selection_color + ) + ) + FROM cc_selection_options so + WHERE so.column_id = cc.id + ) as selections_list, + ( + SELECT json_agg( + json_build_object( + 'label_id', lo.label_id, + 'label_name', lo.label_name, + 'label_color', lo.label_color + ) + ) + FROM cc_label_options lo + WHERE lo.column_id = cc.id + ) as labels_list + FROM cc_custom_columns cc + LEFT JOIN cc_column_configurations cf ON cf.column_id = cc.id + WHERE cc.id = $1; + `; + const result = await db.query(q, [id]); + const [data] = result.rows; + return res.status(200).send(new ServerResponse(true, data)); + } + + @HandleExceptions() + public static async update( + req: IWorkLenzRequest, + res: IWorkLenzResponse + ): Promise { + const { id } = req.params; + const { name, field_type, width, is_visible, configuration } = req.body; + + const client = await db.pool.connect(); + + try { + await client.query("BEGIN"); + + // 1. Update the main custom column + const columnQuery = ` + UPDATE cc_custom_columns + SET name = $1, field_type = $2, width = $3, is_visible = $4, updated_at = CURRENT_TIMESTAMP + WHERE id = $5 + RETURNING id; + `; + await client.query(columnQuery, [ + name, + field_type, + width, + is_visible, + id, + ]); + + // 2. Update the configuration + const configQuery = ` + UPDATE cc_column_configurations + SET + field_title = $1, + field_type = $2, + number_type = $3, + decimals = $4, + label = $5, + label_position = $6, + preview_value = $7, + expression = $8, + first_numeric_column_key = $9, + second_numeric_column_key = $10, + updated_at = CURRENT_TIMESTAMP + WHERE column_id = $11; + `; + await client.query(configQuery, [ + configuration.field_title, + configuration.field_type, + configuration.number_type || null, + configuration.decimals || null, + configuration.label || null, + configuration.label_position || null, + configuration.preview_value || null, + configuration.expression || null, + configuration.first_numeric_column_key || null, + configuration.second_numeric_column_key || null, + id, + ]); + + // 3. Update selections if present + if (configuration.selections_list) { + // Delete existing selections + await client.query( + "DELETE FROM cc_selection_options WHERE column_id = $1", + [id] + ); + + // Insert new selections + if (configuration.selections_list.length > 0) { + const selectionQuery = ` + INSERT INTO cc_selection_options ( + column_id, selection_id, selection_name, selection_color, selection_order + ) VALUES ($1, $2, $3, $4, $5); + `; + for (const [ + index, + selection, + ] of configuration.selections_list.entries()) { + await client.query(selectionQuery, [ + id, + selection.selection_id, + selection.selection_name, + selection.selection_color, + index, + ]); + } + } + } + + // 4. Update labels if present + if (configuration.labels_list) { + // Delete existing labels + await client.query("DELETE FROM cc_label_options WHERE column_id = $1", [ + id, + ]); + + // Insert new labels + if (configuration.labels_list.length > 0) { + const labelQuery = ` + INSERT INTO cc_label_options ( + column_id, label_id, label_name, label_color, label_order + ) VALUES ($1, $2, $3, $4, $5); + `; + for (const [index, label] of configuration.labels_list.entries()) { + await client.query(labelQuery, [ + id, + label.label_id, + label.label_name, + label.label_color, + index, + ]); + } + } + } + + await client.query("COMMIT"); + + // Fetch the updated column data + const getColumnQuery = ` + SELECT + cc.*, + cf.field_title, + cf.number_type, + cf.decimals, + cf.label, + cf.label_position, + cf.preview_value, + cf.expression, + cf.first_numeric_column_key, + cf.second_numeric_column_key, + ( + SELECT json_agg( + json_build_object( + 'selection_id', so.selection_id, + 'selection_name', so.selection_name, + 'selection_color', so.selection_color + ) + ) + FROM cc_selection_options so + WHERE so.column_id = cc.id + ) as selections_list, + ( + SELECT json_agg( + json_build_object( + 'label_id', lo.label_id, + 'label_name', lo.label_name, + 'label_color', lo.label_color + ) + ) + FROM cc_label_options lo + WHERE lo.column_id = cc.id + ) as labels_list + FROM cc_custom_columns cc + LEFT JOIN cc_column_configurations cf ON cf.column_id = cc.id + WHERE cc.id = $1; + `; + const result = await client.query(getColumnQuery, [id]); + const [data] = result.rows; + + return res.status(200).send(new ServerResponse(true, data)); + } catch (error) { + await client.query("ROLLBACK"); + throw error; + } finally { + client.release(); + } + } + + @HandleExceptions() + public static async deleteById( + req: IWorkLenzRequest, + res: IWorkLenzResponse + ): Promise { + const { id } = req.params; + + const q = ` + DELETE FROM cc_custom_columns + WHERE id = $1 + RETURNING id; + `; + const result = await db.query(q, [id]); + return res.status(200).send(new ServerResponse(true, result.rows)); + } + + @HandleExceptions() + public static async getProjectColumns(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const { project_id } = req.params; + + const q = ` + WITH column_data AS ( + SELECT + cc.id, + cc.key, + cc.name, + cc.field_type, + cc.width, + cc.is_visible, + cf.field_title, + cf.number_type, + cf.decimals, + cf.label, + cf.label_position, + cf.preview_value, + cf.expression, + cf.first_numeric_column_key, + cf.second_numeric_column_key, + ( + SELECT json_agg( + json_build_object( + 'selection_id', so.selection_id, + 'selection_name', so.selection_name, + 'selection_color', so.selection_color + ) + ) + FROM cc_selection_options so + WHERE so.column_id = cc.id + ) as selections_list, + ( + SELECT json_agg( + json_build_object( + 'label_id', lo.label_id, + 'label_name', lo.label_name, + 'label_color', lo.label_color + ) + ) + FROM cc_label_options lo + WHERE lo.column_id = cc.id + ) as labels_list + FROM cc_custom_columns cc + LEFT JOIN cc_column_configurations cf ON cf.column_id = cc.id + WHERE cc.project_id = $1 + ) + SELECT + json_agg( + json_build_object( + 'key', cd.key, + 'id', cd.id, + 'name', cd.name, + 'width', cd.width, + 'pinned', cd.is_visible, + 'custom_column', true, + 'custom_column_obj', json_build_object( + 'fieldType', cd.field_type, + 'fieldTitle', cd.field_title, + 'numberType', cd.number_type, + 'decimals', cd.decimals, + 'label', cd.label, + 'labelPosition', cd.label_position, + 'previewValue', cd.preview_value, + 'expression', cd.expression, + 'firstNumericColumnKey', cd.first_numeric_column_key, + 'secondNumericColumnKey', cd.second_numeric_column_key, + 'selectionsList', COALESCE(cd.selections_list, '[]'::json), + 'labelsList', COALESCE(cd.labels_list, '[]'::json) + ) + ) + ) as columns + FROM column_data cd; + `; + + const result = await db.query(q, [project_id]); + const columns = result.rows[0]?.columns || []; + + return res.status(200).send(new ServerResponse(true, columns)); + } +} diff --git a/worklenz-backend/src/controllers/home-page-controller.ts b/worklenz-backend/src/controllers/home-page-controller.ts index 34bfbe6f..be290eb9 100644 --- a/worklenz-backend/src/controllers/home-page-controller.ts +++ b/worklenz-backend/src/controllers/home-page-controller.ts @@ -114,7 +114,7 @@ export default class HomePageController extends WorklenzControllerBase { p.team_id, p.name AS project_name, p.color_code AS project_color, - (SELECT id FROM task_statuses WHERE id = t.status_id) AS status, + (SELECT name FROM task_statuses WHERE id = t.status_id) AS status, (SELECT color_code FROM sys_task_status_categories WHERE id = (SELECT category_id FROM task_statuses WHERE id = t.status_id)) AS status_color, diff --git a/worklenz-backend/src/controllers/index-controller.ts b/worklenz-backend/src/controllers/index-controller.ts index 938e09c9..bcd2ac92 100644 --- a/worklenz-backend/src/controllers/index-controller.ts +++ b/worklenz-backend/src/controllers/index-controller.ts @@ -59,7 +59,7 @@ export default class IndexController extends WorklenzControllerBase { if (req.user && !req.user.is_member) return res.redirect("/teams"); - return res.redirect("/auth"); + return res.redirect(301, "/auth"); } public static redirectToLogin(req: IWorkLenzRequest, res: IWorkLenzResponse) { diff --git a/worklenz-backend/src/controllers/project-comments-controller.ts b/worklenz-backend/src/controllers/project-comments-controller.ts index 26777221..2c65a725 100644 --- a/worklenz-backend/src/controllers/project-comments-controller.ts +++ b/worklenz-backend/src/controllers/project-comments-controller.ts @@ -195,7 +195,7 @@ export default class ProjectCommentsController extends WorklenzControllerBase { pc.created_at, pc.updated_at FROM project_comments pc - WHERE pc.project_id = $1 ORDER BY pc.updated_at DESC + WHERE pc.project_id = $1 ORDER BY pc.updated_at `; const result = await db.query(q, [req.params.id]); diff --git a/worklenz-backend/src/controllers/project-members-controller.ts b/worklenz-backend/src/controllers/project-members-controller.ts index b0fb6e17..4f20d124 100644 --- a/worklenz-backend/src/controllers/project-members-controller.ts +++ b/worklenz-backend/src/controllers/project-members-controller.ts @@ -7,6 +7,9 @@ import WorklenzControllerBase from "./worklenz-controller-base"; import HandleExceptions from "../decorators/handle-exceptions"; import {getColor} from "../shared/utils"; import TeamMembersController from "./team-members-controller"; +import {checkTeamSubscriptionStatus} from "../shared/paddle-utils"; +import {updateUsers} from "../shared/paddle-requests"; +import {statusExclude} from "../shared/constants"; import {NotificationsService} from "../services/notifications/notifications.service"; export default class ProjectMembersController extends WorklenzControllerBase { @@ -69,6 +72,70 @@ export default class ProjectMembersController extends WorklenzControllerBase { if (!req.user?.team_id) return res.status(200).send(new ServerResponse(false, "Required fields are missing.")); + // check the subscription status + const subscriptionData = await checkTeamSubscriptionStatus(req.user?.team_id); + + const userExists = await this.checkIfUserAlreadyExists(req.user?.owner_id as string, req.body.email); + + // Return error if user already exists + if (userExists) { + return res.status(200).send(new ServerResponse(false, null, "User already exists in the team.")); + } + + // Handle self-hosted subscriptions differently + if (subscriptionData.subscription_type === 'SELF_HOSTED') { + // Adding as a team member + const teamMemberReq: { team_id?: string; emails: string[], project_id?: string; } = { + team_id: req.user?.team_id, + emails: [req.body.email] + }; + + if (req.body.project_id) + teamMemberReq.project_id = req.body.project_id; + + const [member] = await TeamMembersController.createOrInviteMembers(teamMemberReq, req.user); + + if (!member) + return res.status(200).send(new ServerResponse(true, null, "Failed to add the member to the project. Please try again.")); + + // Adding to the project + const projectMemberReq = { + team_member_id: member.team_member_id, + team_id: req.user?.team_id, + project_id: req.body.project_id, + user_id: req.user?.id, + access_level: req.body.access_level ? req.body.access_level : "MEMBER" + }; + const data = await this.createOrInviteMembers(projectMemberReq); + return res.status(200).send(new ServerResponse(true, data.member)); + } + + if (statusExclude.includes(subscriptionData.subscription_status)) { + return res.status(200).send(new ServerResponse(false, null, "Unable to add user! Please check your subscription status.")); + } + + if (!userExists && subscriptionData.is_ltd && subscriptionData.current_count && (parseInt(subscriptionData.current_count) + 1 > parseInt(subscriptionData.ltd_users))) { + return res.status(200).send(new ServerResponse(false, null, "Maximum number of life time users reached.")); + } + + // if (subscriptionData.status === "trialing") break; + if (!userExists && !subscriptionData.is_credit && !subscriptionData.is_custom && subscriptionData.subscription_status !== "trialing") { + // if (subscriptionData.subscription_status === "active") { + // const response = await updateUsers(subscriptionData.subscription_id, (subscriptionData.quantity + 1)); + // if (!response.body.subscription_id) return res.status(200).send(new ServerResponse(false, null, response.message || "Unable to add user! Please check your subscription.")); + // } + const updatedCount = parseInt(subscriptionData.current_count) + 1; + const requiredSeats = updatedCount - subscriptionData.quantity; + if (updatedCount > subscriptionData.quantity) { + const obj = { + seats_enough: false, + required_count: requiredSeats, + current_seat_amount: subscriptionData.quantity + }; + return res.status(200).send(new ServerResponse(false, obj, null)); + } + } + // Adding as a team member const teamMemberReq: { team_id?: string; emails: string[], project_id?: string; } = { team_id: req.user?.team_id, diff --git a/worklenz-backend/src/controllers/project-templates/pt-templates-controller.ts b/worklenz-backend/src/controllers/project-templates/pt-templates-controller.ts index c9a74e6b..321df898 100644 --- a/worklenz-backend/src/controllers/project-templates/pt-templates-controller.ts +++ b/worklenz-backend/src/controllers/project-templates/pt-templates-controller.ts @@ -8,6 +8,7 @@ import { templateData } from "./project-templates"; import ProjectTemplatesControllerBase from "./project-templates-base"; import { LOG_DESCRIPTIONS, TASK_PRIORITY_COLOR_ALPHA, TASK_STATUS_COLOR_ALPHA } from "../../shared/constants"; import { IO } from "../../shared/io"; +import { getCurrentProjectsCount, getFreePlanSettings } from "../../shared/paddle-utils"; export default class ProjectTemplatesController extends ProjectTemplatesControllerBase { @@ -46,10 +47,10 @@ export default class ProjectTemplatesController extends ProjectTemplatesControll @HandleExceptions() public static async getDefaultProjectHealth() { - const q = `SELECT id FROM sys_project_healths WHERE is_default IS TRUE`; - const result = await db.query(q, []); - const [data] = result.rows; - return data.id; + const q = `SELECT id FROM sys_project_healths WHERE is_default IS TRUE`; + const result = await db.query(q, []); + const [data] = result.rows; + return data.id; } @HandleExceptions() @@ -92,6 +93,16 @@ export default class ProjectTemplatesController extends ProjectTemplatesControll @HandleExceptions() public static async importTemplates(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + if (req.user?.subscription_status === "free" && req.user?.owner_id) { + const limits = await getFreePlanSettings(); + const projectsCount = await getCurrentProjectsCount(req.user.owner_id); + const projectsLimit = parseInt(limits.projects_limit); + + if (parseInt(projectsCount) >= projectsLimit) { + return res.status(200).send(new ServerResponse(false, [], `Sorry, the free plan cannot have more than ${projectsLimit} projects.`)); + } + } + const { template_id } = req.body; let project_id: string | null = null; @@ -202,6 +213,16 @@ export default class ProjectTemplatesController extends ProjectTemplatesControll @HandleExceptions() public static async importCustomTemplate(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + if (req.user?.subscription_status === "free" && req.user?.owner_id) { + const limits = await getFreePlanSettings(); + const projectsCount = await getCurrentProjectsCount(req.user.owner_id); + const projectsLimit = parseInt(limits.projects_limit); + + if (parseInt(projectsCount) >= projectsLimit) { + return res.status(200).send(new ServerResponse(false, [], `Sorry, the free plan cannot have more than ${projectsLimit} projects.`)); + } + } + const { template_id } = req.body; let project_id: string | null = null; @@ -223,8 +244,8 @@ export default class ProjectTemplatesController extends ProjectTemplatesControll await this.deleteDefaultStatusForProject(project_id as string); await this.insertTeamLabels(data.labels, req.user?.team_id); await this.insertProjectPhases(data.phases, project_id as string); - await this.insertProjectStatuses(data.status, project_id as string, data.team_id ); - await this.insertProjectTasksFromCustom(data.tasks, data.team_id, project_id as string, data.user_id, IO.getSocketById(req.user?.socket_id as string)); + await this.insertProjectStatuses(data.status, project_id as string, data.team_id); + await this.insertProjectTasksFromCustom(data.tasks, data.team_id, project_id as string, data.user_id, IO.getSocketById(req.user?.socket_id as string)); return res.status(200).send(new ServerResponse(true, { project_id })); } diff --git a/worklenz-backend/src/controllers/projects-controller.ts b/worklenz-backend/src/controllers/projects-controller.ts index 676dce69..e739bfb1 100644 --- a/worklenz-backend/src/controllers/projects-controller.ts +++ b/worklenz-backend/src/controllers/projects-controller.ts @@ -12,6 +12,7 @@ import { NotificationsService } from "../services/notifications/notifications.se import { IPassportSession } from "../interfaces/passport-session"; import { SocketEvents } from "../socket.io/events"; import { IO } from "../shared/io"; +import { getCurrentProjectsCount, getFreePlanSettings } from "../shared/paddle-utils"; export default class ProjectsController extends WorklenzControllerBase { @@ -61,6 +62,16 @@ export default class ProjectsController extends WorklenzControllerBase { } }) public static async create(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + if (req.user?.subscription_status === "free" && req.user?.owner_id) { + const limits = await getFreePlanSettings(); + const projectsCount = await getCurrentProjectsCount(req.user.owner_id); + const projectsLimit = parseInt(limits.projects_limit); + + if (parseInt(projectsCount) >= projectsLimit) { + return res.status(200).send(new ServerResponse(false, [], `Sorry, the free plan cannot have more than ${projectsLimit} projects.`)); + } + } + const q = `SELECT create_project($1) AS project`; req.body.team_id = req.user?.team_id || null; @@ -689,7 +700,8 @@ export default class ProjectsController extends WorklenzControllerBase { public static async toggleArchiveAll(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { const q = `SELECT toggle_archive_all_projects($1);`; const result = await db.query(q, [req.params.id]); - return res.status(200).send(new ServerResponse(true, result.rows || [])); + const [data] = result.rows; + return res.status(200).send(new ServerResponse(true, data.toggle_archive_all_projects || [])); } public static async getProjectManager(projectId: string) { @@ -698,4 +710,47 @@ export default class ProjectsController extends WorklenzControllerBase { return result.rows || []; } + public static async updateExistPhaseColors() { + const q = `SELECT id, name FROM project_phases`; + const phases = await db.query(q); + + phases.rows.forEach((phase) => { + phase.color_code = getColor(phase.name); + }); + + const body = { + phases: phases.rows + }; + + const q2 = `SELECT update_existing_phase_colors($1)`; + await db.query(q2, [JSON.stringify(body)]); + + } + + public static async updateExistSortOrder() { + const q = `SELECT id, project_id FROM project_phases ORDER BY name`; + const phases = await db.query(q); + + const sortNumbers: any = {}; + + phases.rows.forEach(phase => { + const projectId = phase.project_id; + + if (!sortNumbers[projectId]) { + sortNumbers[projectId] = 0; + } + + phase.sort_number = sortNumbers[projectId]++; + }); + + const body = { + phases: phases.rows + }; + + const q2 = `SELECT update_existing_phase_sort_order($1)`; + await db.query(q2, [JSON.stringify(body)]); + // return phases; + + } + } diff --git a/worklenz-backend/src/controllers/reporting/interfaces.ts b/worklenz-backend/src/controllers/reporting/interfaces.ts index b9616c50..b85fedf4 100644 --- a/worklenz-backend/src/controllers/reporting/interfaces.ts +++ b/worklenz-backend/src/controllers/reporting/interfaces.ts @@ -1,4 +1,4 @@ -import { IChartObject } from "./overview/reporting-overview-base"; +import * as Highcharts from "highcharts"; export interface IDuration { label: string; @@ -34,7 +34,7 @@ export interface IOverviewStatistics { } export interface IChartData { - chart: IChartObject[]; + chart: Highcharts.PointOptionsObject[]; } export interface ITasksByStatus extends IChartData { diff --git a/worklenz-backend/src/controllers/reporting/overview/reporting-overview-base.ts b/worklenz-backend/src/controllers/reporting/overview/reporting-overview-base.ts index 68237fbf..da78aca8 100644 --- a/worklenz-backend/src/controllers/reporting/overview/reporting-overview-base.ts +++ b/worklenz-backend/src/controllers/reporting/overview/reporting-overview-base.ts @@ -1,4 +1,5 @@ import db from "../../../config/db"; +import * as Highcharts from "highcharts"; import { ITasksByDue, ITasksByPriority, ITasksByStatus } from "../interfaces"; import ReportingControllerBase from "../reporting-controller-base"; import { @@ -15,36 +16,33 @@ import { TASK_STATUS_TODO_COLOR } from "../../../shared/constants"; import { formatDuration, int } from "../../../shared/utils"; +import PointOptionsObject from "../point-options-object"; import moment from "moment"; -export interface IChartObject { - name: string, - color: string, - y: number -} - export default class ReportingOverviewBase extends ReportingControllerBase { - - private static createChartObject(name: string, color: string, y: number) { - return { - name, - color, - y - }; - } - protected static async getTeamsCounts(teamId: string | null, archivedQuery = "") { const q = ` - SELECT JSON_BUILD_OBJECT( - 'teams', (SELECT COUNT(*) FROM teams WHERE in_organization(id, $1)), - 'projects', - (SELECT COUNT(*) FROM projects WHERE in_organization(team_id, $1) ${archivedQuery}), - 'team_members', (SELECT COUNT(DISTINCT email) - FROM team_member_info_view - WHERE in_organization(team_id, $1)) - ) AS counts; - `; + WITH team_count AS ( + SELECT COUNT(*) AS count + FROM teams + WHERE in_organization(id, $1) + ), + project_count AS ( + SELECT COUNT(*) AS count + FROM projects + WHERE in_organization(team_id, $1) ${archivedQuery} + ), + team_member_count AS ( + SELECT COUNT(DISTINCT email) AS count + FROM team_member_info_view + WHERE in_organization(team_id, $1) + ) + SELECT JSON_BUILD_OBJECT( + 'teams', (SELECT count FROM team_count), + 'projects', (SELECT count FROM project_count), + 'team_members', (SELECT count FROM team_member_count) + ) AS counts;`; const res = await db.query(q, [teamId]); const [data] = res.rows; @@ -173,7 +171,7 @@ export default class ReportingOverviewBase extends ReportingControllerBase { const doing = int(data?.counts.doing); const done = int(data?.counts.done); - const chart: IChartObject[] = []; + const chart: Highcharts.PointOptionsObject[] = []; return { all, @@ -209,7 +207,7 @@ export default class ReportingOverviewBase extends ReportingControllerBase { const medium = int(data?.counts.medium); const high = int(data?.counts.high); - const chart: IChartObject[] = []; + const chart: Highcharts.PointOptionsObject[] = []; return { all: 0, @@ -237,7 +235,7 @@ export default class ReportingOverviewBase extends ReportingControllerBase { const res = await db.query(q, [projectId]); const [data] = res.rows; - const chart: IChartObject[] = []; + const chart: Highcharts.PointOptionsObject[] = []; return { all: 0, @@ -251,26 +249,26 @@ export default class ReportingOverviewBase extends ReportingControllerBase { protected static createByStatusChartData(body: ITasksByStatus) { body.chart = [ - this.createChartObject("Todo", TASK_STATUS_TODO_COLOR, body.todo), - this.createChartObject("Doing", TASK_STATUS_DOING_COLOR, body.doing), - this.createChartObject("Done", TASK_STATUS_DONE_COLOR, body.done), + new PointOptionsObject("Todo", TASK_STATUS_TODO_COLOR, body.todo), + new PointOptionsObject("Doing", TASK_STATUS_DOING_COLOR, body.doing), + new PointOptionsObject("Done", TASK_STATUS_DONE_COLOR, body.done), ]; } protected static createByPriorityChartData(body: ITasksByPriority) { body.chart = [ - this.createChartObject("Low", TASK_PRIORITY_LOW_COLOR, body.low), - this.createChartObject("Medium", TASK_PRIORITY_MEDIUM_COLOR, body.medium), - this.createChartObject("High", TASK_PRIORITY_HIGH_COLOR, body.high), + new PointOptionsObject("Low", TASK_PRIORITY_LOW_COLOR, body.low), + new PointOptionsObject("Medium", TASK_PRIORITY_MEDIUM_COLOR, body.medium), + new PointOptionsObject("High", TASK_PRIORITY_HIGH_COLOR, body.high), ]; } protected static createByDueDateChartData(body: ITasksByDue) { body.chart = [ - this.createChartObject("Completed", TASK_DUE_COMPLETED_COLOR, body.completed), - this.createChartObject("Upcoming", TASK_DUE_UPCOMING_COLOR, body.upcoming), - this.createChartObject("Overdue", TASK_DUE_OVERDUE_COLOR, body.overdue), - this.createChartObject("No due date", TASK_DUE_NO_DUE_COLOR, body.no_due), + new PointOptionsObject("Completed", TASK_DUE_COMPLETED_COLOR, body.completed), + new PointOptionsObject("Upcoming", TASK_DUE_UPCOMING_COLOR, body.upcoming), + new PointOptionsObject("Overdue", TASK_DUE_OVERDUE_COLOR, body.overdue), + new PointOptionsObject("No due date", TASK_DUE_NO_DUE_COLOR, body.no_due), ]; } @@ -581,7 +579,7 @@ export default class ReportingOverviewBase extends ReportingControllerBase { `; const result = await db.query(q, [teamMemberId]); - const chart: IChartObject[] = []; + const chart: Highcharts.PointOptionsObject[] = []; const total = result.rows.reduce((accumulator: number, current: { count: number @@ -589,7 +587,7 @@ export default class ReportingOverviewBase extends ReportingControllerBase { for (const project of result.rows) { project.count = int(project.count); - chart.push(this.createChartObject(project.label, project.color, project.count)); + chart.push(new PointOptionsObject(project.label, project.color, project.count)); } return { chart, total, data: result.rows }; @@ -635,7 +633,7 @@ export default class ReportingOverviewBase extends ReportingControllerBase { `; const result = await db.query(q, [teamMemberId]); - const chart: IChartObject[] = []; + const chart: Highcharts.PointOptionsObject[] = []; const total = result.rows.reduce((accumulator: number, current: { count: number @@ -643,7 +641,7 @@ export default class ReportingOverviewBase extends ReportingControllerBase { for (const project of result.rows) { project.count = int(project.count); - chart.push(this.createChartObject(project.label, project.color, project.count)); + chart.push(new PointOptionsObject(project.label, project.color, project.count)); } return { chart, total, data: result.rows }; @@ -673,10 +671,10 @@ export default class ReportingOverviewBase extends ReportingControllerBase { const total = int(d.low) + int(d.medium) + int(d.high); - const chart = [ - this.createChartObject("Low", TASK_PRIORITY_LOW_COLOR, d.low), - this.createChartObject("Medium", TASK_PRIORITY_MEDIUM_COLOR, d.medium), - this.createChartObject("High", TASK_PRIORITY_HIGH_COLOR, d.high), + const chart: Highcharts.PointOptionsObject[] = [ + new PointOptionsObject("Low", TASK_PRIORITY_LOW_COLOR, d.low), + new PointOptionsObject("Medium", TASK_PRIORITY_MEDIUM_COLOR, d.medium), + new PointOptionsObject("High", TASK_PRIORITY_HIGH_COLOR, d.high), ]; const data = [ @@ -730,10 +728,10 @@ export default class ReportingOverviewBase extends ReportingControllerBase { const total = int(d.low) + int(d.medium) + int(d.high); - const chart = [ - this.createChartObject("Low", TASK_PRIORITY_LOW_COLOR, d.low), - this.createChartObject("Medium", TASK_PRIORITY_MEDIUM_COLOR, d.medium), - this.createChartObject("High", TASK_PRIORITY_HIGH_COLOR, d.high), + const chart: Highcharts.PointOptionsObject[] = [ + new PointOptionsObject("Low", TASK_PRIORITY_LOW_COLOR, d.low), + new PointOptionsObject("Medium", TASK_PRIORITY_MEDIUM_COLOR, d.medium), + new PointOptionsObject("High", TASK_PRIORITY_HIGH_COLOR, d.high), ]; const data = [ @@ -784,10 +782,10 @@ export default class ReportingOverviewBase extends ReportingControllerBase { const total = int(d.total); - const chart = [ - this.createChartObject("Todo", TASK_STATUS_TODO_COLOR, d.todo), - this.createChartObject("Doing", TASK_STATUS_DOING_COLOR, d.doing), - this.createChartObject("Done", TASK_STATUS_DONE_COLOR, d.done), + const chart: Highcharts.PointOptionsObject[] = [ + new PointOptionsObject("Todo", TASK_STATUS_TODO_COLOR, d.todo), + new PointOptionsObject("Doing", TASK_STATUS_DOING_COLOR, d.doing), + new PointOptionsObject("Done", TASK_STATUS_DONE_COLOR, d.done), ]; const data = [ @@ -826,10 +824,10 @@ export default class ReportingOverviewBase extends ReportingControllerBase { const total = int(d.todo) + int(d.doing) + int(d.done); - const chart = [ - this.createChartObject("Todo", TASK_STATUS_TODO_COLOR, d.todo), - this.createChartObject("Doing", TASK_STATUS_DOING_COLOR, d.doing), - this.createChartObject("Done", TASK_STATUS_DONE_COLOR, d.done), + const chart: Highcharts.PointOptionsObject[] = [ + new PointOptionsObject("Todo", TASK_STATUS_TODO_COLOR, d.todo), + new PointOptionsObject("Doing", TASK_STATUS_DOING_COLOR, d.doing), + new PointOptionsObject("Done", TASK_STATUS_DONE_COLOR, d.done), ]; const data = [ @@ -878,7 +876,7 @@ export default class ReportingOverviewBase extends ReportingControllerBase { const in_progress = int(data?.counts.in_progress); const completed = int(data?.counts.completed); - const chart : IChartObject[] = []; + const chart: Highcharts.PointOptionsObject[] = []; return { all, @@ -908,7 +906,7 @@ export default class ReportingOverviewBase extends ReportingControllerBase { `; const result = await db.query(q, [teamId]); - const chart: IChartObject[] = []; + const chart: Highcharts.PointOptionsObject[] = []; const total = result.rows.reduce((accumulator: number, current: { count: number @@ -916,11 +914,7 @@ export default class ReportingOverviewBase extends ReportingControllerBase { for (const category of result.rows) { category.count = int(category.count); - chart.push({ - name: category.label, - color: category.color, - y: category.count - }); + chart.push(new PointOptionsObject(category.label, category.color, category.count)); } return { chart, total, data: result.rows }; @@ -956,7 +950,7 @@ export default class ReportingOverviewBase extends ReportingControllerBase { const at_risk = int(data?.counts.at_risk); const good = int(data?.counts.good); - const chart: IChartObject[] = []; + const chart: Highcharts.PointOptionsObject[] = []; return { not_set, @@ -971,22 +965,22 @@ export default class ReportingOverviewBase extends ReportingControllerBase { // Team Overview protected static createByProjectStatusChartData(body: any) { body.chart = [ - this.createChartObject("Cancelled", "#f37070", body.cancelled), - this.createChartObject("Blocked", "#cbc8a1", body.blocked), - this.createChartObject("On Hold", "#cbc8a1", body.on_hold), - this.createChartObject("Proposed", "#cbc8a1", body.proposed), - this.createChartObject("In Planning", "#cbc8a1", body.in_planning), - this.createChartObject("In Progress", "#80ca79", body.in_progress), - this.createChartObject("Completed", "#80ca79", body.completed) + new PointOptionsObject("Cancelled", "#f37070", body.cancelled), + new PointOptionsObject("Blocked", "#cbc8a1", body.blocked), + new PointOptionsObject("On Hold", "#cbc8a1", body.on_hold), + new PointOptionsObject("Proposed", "#cbc8a1", body.proposed), + new PointOptionsObject("In Planning", "#cbc8a1", body.in_planning), + new PointOptionsObject("In Progress", "#80ca79", body.in_progress), + new PointOptionsObject("Completed", "#80ca79", body.completed), ]; } protected static createByProjectHealthChartData(body: any) { body.chart = [ - this.createChartObject("Not Set", "#a9a9a9", body.not_set), - this.createChartObject("Needs Attention", "#f37070", body.needs_attention), - this.createChartObject("At Risk", "#fbc84c", body.at_risk), - this.createChartObject("Good", "#75c997", body.good) + new PointOptionsObject("Not Set", "#a9a9a9", body.not_set), + new PointOptionsObject("Needs Attention", "#f37070", body.needs_attention), + new PointOptionsObject("At Risk", "#fbc84c", body.at_risk), + new PointOptionsObject("Good", "#75c997", body.good) ]; } diff --git a/worklenz-backend/src/controllers/reporting/point-options-object.ts b/worklenz-backend/src/controllers/reporting/point-options-object.ts new file mode 100644 index 00000000..cc3cc5a2 --- /dev/null +++ b/worklenz-backend/src/controllers/reporting/point-options-object.ts @@ -0,0 +1,13 @@ +import * as Highcharts from "highcharts"; + +export default class PointOptionsObject implements Highcharts.PointOptionsObject { + name!: string; + color!: string; + y!: number; + + constructor(name: string, color: string, y: number) { + this.name = name; + this.color = color; + this.y = y; + } +} diff --git a/worklenz-backend/src/controllers/reporting/reporting-allocation-controller.ts b/worklenz-backend/src/controllers/reporting/reporting-allocation-controller.ts index fb301d35..be79c4b8 100644 --- a/worklenz-backend/src/controllers/reporting/reporting-allocation-controller.ts +++ b/worklenz-backend/src/controllers/reporting/reporting-allocation-controller.ts @@ -8,13 +8,14 @@ import { getColor, int, log_error } from "../../shared/utils"; import ReportingControllerBase from "./reporting-controller-base"; import { DATE_RANGES } from "../../shared/constants"; import Excel from "exceljs"; +import ChartJsImage from "chartjs-to-image"; enum IToggleOptions { 'WORKING_DAYS' = 'WORKING_DAYS', 'MAN_DAYS' = 'MAN_DAYS' } export default class ReportingAllocationController extends ReportingControllerBase { - private static async getTimeLoggedByProjects(projects: string[], users: string[], key: string, dateRange: string[], archived = false, user_id = ""): Promise { + private static async getTimeLoggedByProjects(projects: string[], users: string[], key: string, dateRange: string[], archived = false, user_id = "", billable: { billable: boolean; nonBillable: boolean }): Promise { try { const projectIds = projects.map(p => `'${p}'`).join(","); const userIds = users.map(u => `'${u}'`).join(","); @@ -24,8 +25,10 @@ export default class ReportingAllocationController extends ReportingControllerBa ? "" : `AND projects.id NOT IN (SELECT project_id FROM archived_projects WHERE project_id = projects.id AND user_id = '${user_id}') `; - const projectTimeLogs = await this.getTotalTimeLogsByProject(archived, duration, projectIds, userIds, archivedClause); - const userTimeLogs = await this.getTotalTimeLogsByUser(archived, duration, projectIds, userIds); + const billableQuery = this.buildBillableQuery(billable); + + const projectTimeLogs = await this.getTotalTimeLogsByProject(archived, duration, projectIds, userIds, archivedClause, billableQuery); + const userTimeLogs = await this.getTotalTimeLogsByUser(archived, duration, projectIds, userIds, billableQuery); const format = (seconds: number) => { if (seconds === 0) return "-"; @@ -65,7 +68,7 @@ export default class ReportingAllocationController extends ReportingControllerBa return []; } - private static async getTotalTimeLogsByProject(archived: boolean, duration: string, projectIds: string, userIds: string, archivedClause = "") { + private static async getTotalTimeLogsByProject(archived: boolean, duration: string, projectIds: string, userIds: string, archivedClause = "", billableQuery = '') { try { const q = `SELECT projects.name, projects.color_code, @@ -74,12 +77,12 @@ export default class ReportingAllocationController extends ReportingControllerBa sps.icon AS status_icon, (SELECT COUNT(*) FROM tasks - WHERE CASE WHEN ($1 IS TRUE) THEN project_id IS NOT NULL ELSE archived = FALSE END + WHERE CASE WHEN ($1 IS TRUE) THEN project_id IS NOT NULL ELSE archived = FALSE END ${billableQuery} AND project_id = projects.id) AS all_tasks_count, (SELECT COUNT(*) FROM tasks WHERE CASE WHEN ($1 IS TRUE) THEN project_id IS NOT NULL ELSE archived = FALSE END - AND project_id = projects.id + AND project_id = projects.id ${billableQuery} AND status_id IN (SELECT id FROM task_statuses WHERE project_id = projects.id @@ -91,10 +94,10 @@ export default class ReportingAllocationController extends ReportingControllerBa SELECT name, (SELECT COALESCE(SUM(time_spent), 0) FROM task_work_log - LEFT JOIN tasks t ON task_work_log.task_id = t.id - WHERE user_id = users.id - AND CASE WHEN ($1 IS TRUE) THEN t.project_id IS NOT NULL ELSE t.archived = FALSE END - AND t.project_id = projects.id + LEFT JOIN tasks ON task_work_log.task_id = tasks.id + WHERE user_id = users.id ${billableQuery} + AND CASE WHEN ($1 IS TRUE) THEN tasks.project_id IS NOT NULL ELSE tasks.archived = FALSE END + AND tasks.project_id = projects.id ${duration}) AS time_logged FROM users WHERE id IN (${userIds}) @@ -113,15 +116,15 @@ export default class ReportingAllocationController extends ReportingControllerBa } } - private static async getTotalTimeLogsByUser(archived: boolean, duration: string, projectIds: string, userIds: string) { + private static async getTotalTimeLogsByUser(archived: boolean, duration: string, projectIds: string, userIds: string, billableQuery = "") { try { const q = `(SELECT id, (SELECT COALESCE(SUM(time_spent), 0) FROM task_work_log - LEFT JOIN tasks t ON task_work_log.task_id = t.id + LEFT JOIN tasks ON task_work_log.task_id = tasks.id ${billableQuery} WHERE user_id = users.id - AND CASE WHEN ($1 IS TRUE) THEN t.project_id IS NOT NULL ELSE t.archived = FALSE END - AND t.project_id IN (${projectIds}) + AND CASE WHEN ($1 IS TRUE) THEN tasks.project_id IS NOT NULL ELSE tasks.archived = FALSE END + AND tasks.project_id IN (${projectIds}) ${duration}) AS time_logged FROM users WHERE id IN (${userIds}) @@ -154,6 +157,7 @@ export default class ReportingAllocationController extends ReportingControllerBa @HandleExceptions() public static async getAllocation(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { const teams = (req.body.teams || []) as string[]; // ids + const billable = req.body.billable; const teamIds = teams.map(id => `'${id}'`).join(","); const projectIds = (req.body.projects || []) as string[]; @@ -164,7 +168,7 @@ export default class ReportingAllocationController extends ReportingControllerBa const users = await this.getUserIds(teamIds); const userIds = users.map((u: any) => u.id); - const { projectTimeLogs, userTimeLogs } = await this.getTimeLoggedByProjects(projectIds, userIds, req.body.duration, req.body.date_range, (req.query.archived === "true"), req.user?.id); + const { projectTimeLogs, userTimeLogs } = await this.getTimeLoggedByProjects(projectIds, userIds, req.body.duration, req.body.date_range, (req.query.archived === "true"), req.user?.id, billable); for (const [i, user] of users.entries()) { user.total_time = userTimeLogs[i].time_logged; @@ -184,6 +188,7 @@ export default class ReportingAllocationController extends ReportingControllerBa public static async export(req: IWorkLenzRequest, res: IWorkLenzResponse) { const teams = (req.query.teams as string)?.split(","); const teamIds = teams.map(t => `'${t}'`).join(","); + const billable = req.body.billable ? req.body.billable : { billable: req.query.billable === "true", nonBillable: req.query.nonBillable === "true" }; const projectIds = (req.query.projects as string)?.split(","); @@ -218,7 +223,7 @@ export default class ReportingAllocationController extends ReportingControllerBa const users = await this.getUserIds(teamIds); const userIds = users.map((u: any) => u.id); - const { projectTimeLogs, userTimeLogs } = await this.getTimeLoggedByProjects(projectIds, userIds, duration as string, dateRange, (req.query.include_archived === "true"), req.user?.id); + const { projectTimeLogs, userTimeLogs } = await this.getTimeLoggedByProjects(projectIds, userIds, duration as string, dateRange, (req.query.include_archived === "true"), req.user?.id, billable); for (const [i, user] of users.entries()) { user.total_time = userTimeLogs[i].time_logged; @@ -341,6 +346,8 @@ export default class ReportingAllocationController extends ReportingControllerBa const projects = (req.body.projects || []) as string[]; const projectIds = projects.map(p => `'${p}'`).join(","); + const billable = req.body.billable; + if (!teamIds || !projectIds.length) return res.status(200).send(new ServerResponse(true, { users: [], projects: [] })); @@ -352,6 +359,8 @@ export default class ReportingAllocationController extends ReportingControllerBa ? "" : `AND p.id NOT IN (SELECT project_id FROM archived_projects WHERE project_id = p.id AND user_id = '${req.user?.id}') `; + const billableQuery = this.buildBillableQuery(billable); + const q = ` SELECT p.id, p.name, @@ -359,8 +368,8 @@ export default class ReportingAllocationController extends ReportingControllerBa SUM(total_minutes) AS estimated, color_code FROM projects p - LEFT JOIN tasks t ON t.project_id = p.id - LEFT JOIN task_work_log ON task_work_log.task_id = t.id + LEFT JOIN tasks ON tasks.project_id = p.id ${billableQuery} + LEFT JOIN task_work_log ON task_work_log.task_id = tasks.id WHERE p.id IN (${projectIds}) ${durationClause} ${archivedClause} GROUP BY p.id, p.name ORDER BY logged_time DESC;`; @@ -372,7 +381,7 @@ export default class ReportingAllocationController extends ReportingControllerBa project.value = project.logged_time ? parseFloat(moment.duration(project.logged_time, "seconds").asHours().toFixed(2)) : 0; project.estimated_value = project.estimated ? parseFloat(moment.duration(project.estimated, "minutes").asHours().toFixed(2)) : 0; - if (project.value > 0 ) { + if (project.value > 0) { data.push(project); } @@ -392,6 +401,8 @@ export default class ReportingAllocationController extends ReportingControllerBa const projects = (req.body.projects || []) as string[]; const projectIds = projects.map(p => `'${p}'`).join(","); + const billable = req.body.billable; + if (!teamIds || !projectIds.length) return res.status(200).send(new ServerResponse(true, { users: [], projects: [] })); @@ -402,12 +413,14 @@ export default class ReportingAllocationController extends ReportingControllerBa ? "" : `AND p.id NOT IN (SELECT project_id FROM archived_projects WHERE project_id = p.id AND user_id = '${req.user?.id}') `; + const billableQuery = this.buildBillableQuery(billable); + const q = ` SELECT tmiv.email, tmiv.name, SUM(time_spent) AS logged_time FROM team_member_info_view tmiv LEFT JOIN task_work_log ON task_work_log.user_id = tmiv.user_id - LEFT JOIN tasks t ON t.id = task_work_log.task_id - LEFT JOIN projects p ON p.id = t.project_id AND p.team_id = tmiv.team_id + LEFT JOIN tasks ON tasks.id = task_work_log.task_id ${billableQuery} + LEFT JOIN projects p ON p.id = tasks.project_id AND p.team_id = tmiv.team_id WHERE p.id IN (${projectIds}) ${durationClause} ${archivedClause} GROUP BY tmiv.email, tmiv.name @@ -422,7 +435,64 @@ export default class ReportingAllocationController extends ReportingControllerBa return res.status(200).send(new ServerResponse(true, result.rows)); } + @HandleExceptions() + public static async exportTest(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + + const archived = req.query.archived === "true"; + const teamId = this.getCurrentTeamId(req); + const { duration, date_range } = req.query; + + const durationClause = this.getDateRangeClause(duration as string || DATE_RANGES.LAST_WEEK, date_range as string[]); + + const archivedClause = archived + ? "" + : `AND p.id NOT IN (SELECT project_id FROM archived_projects WHERE project_id = p.id AND user_id = '${req.user?.id}') `; + + const q = ` + SELECT p.id, + p.name, + (SELECT SUM(time_spent)) AS logged_time, + SUM(total_minutes) AS estimated, + color_code + FROM projects p + LEFT JOIN tasks t ON t.project_id = p.id + LEFT JOIN task_work_log ON task_work_log.task_id = t.id + WHERE in_organization(p.team_id, $1) + ${durationClause} ${archivedClause} + GROUP BY p.id, p.name + ORDER BY p.name ASC;`; + const result = await db.query(q, [teamId]); + + const labelsX = []; + const dataX = []; + + for (const project of result.rows) { + project.value = project.logged_time ? parseFloat(moment.duration(project.logged_time, "seconds").asHours().toFixed(2)) : 0; + project.estimated_value = project.estimated ? parseFloat(moment.duration(project.estimated, "minutes").asHours().toFixed(2)) : 0; + labelsX.push(project.name); + dataX.push(project.value || 0); + } + + const chart = new ChartJsImage(); + chart.setConfig({ + type: "bar", + data: { + labels: labelsX, + datasets: [ + { label: "", data: dataX } + ] + }, + }); + chart.setWidth(1920).setHeight(1080).setBackgroundColor("transparent"); + const url = chart.getUrl(); + chart.toFile("test.png"); + return res.status(200).send(new ServerResponse(true, url)); + } + private static getEstimated(project: any, type: string) { + // if (project.estimated_man_days === 0 || project.estimated_working_days === 0) { + // return (parseFloat(moment.duration(project.estimated, "minutes").asHours().toFixed(2)) / int(project.hours_per_day)).toFixed(2) + // } switch (type) { case IToggleOptions.MAN_DAYS: @@ -445,7 +515,7 @@ export default class ReportingAllocationController extends ReportingControllerBa const projects = (req.body.projects || []) as string[]; const projectIds = projects.map(p => `'${p}'`).join(","); - const { type } = req.body; + const { type, billable } = req.body; if (!teamIds || !projectIds.length) return res.status(200).send(new ServerResponse(true, { users: [], projects: [] })); @@ -458,6 +528,8 @@ export default class ReportingAllocationController extends ReportingControllerBa ? "" : `AND p.id NOT IN (SELECT project_id FROM archived_projects WHERE project_id = p.id AND user_id = '${req.user?.id}') `; + const billableQuery = this.buildBillableQuery(billable); + const q = ` SELECT p.id, p.name, @@ -471,8 +543,8 @@ export default class ReportingAllocationController extends ReportingControllerBa WHERE project_id = p.id) AS estimated, color_code FROM projects p - LEFT JOIN tasks t ON t.project_id = p.id - LEFT JOIN task_work_log ON task_work_log.task_id = t.id + LEFT JOIN tasks ON tasks.project_id = p.id ${billableQuery} + LEFT JOIN task_work_log ON task_work_log.task_id = tasks.id WHERE p.id IN (${projectIds}) ${durationClause} ${archivedClause} GROUP BY p.id, p.name ORDER BY logged_time DESC;`; @@ -491,7 +563,7 @@ export default class ReportingAllocationController extends ReportingControllerBa project.estimated_working_days = project.estimated_working_days ?? 0; project.hours_per_day = project.hours_per_day ?? 0; - if (project.value > 0 || project.estimated_value > 0 ) { + if (project.value > 0 || project.estimated_value > 0) { data.push(project); } diff --git a/worklenz-backend/src/controllers/reporting/reporting-controller-base.ts b/worklenz-backend/src/controllers/reporting/reporting-controller-base.ts index a1adaccb..be6832eb 100644 --- a/worklenz-backend/src/controllers/reporting/reporting-controller-base.ts +++ b/worklenz-backend/src/controllers/reporting/reporting-controller-base.ts @@ -109,6 +109,23 @@ export default abstract class ReportingControllerBase extends WorklenzController return ""; } + protected static buildBillableQuery(selectedStatuses: { billable: boolean; nonBillable: boolean }): string { + const { billable, nonBillable } = selectedStatuses; + + if (billable && nonBillable) { + // Both are enabled, no need to filter + return ""; + } else if (billable) { + // Only billable is enabled + return " AND tasks.billable IS TRUE"; + } else if (nonBillable) { + // Only non-billable is enabled + return " AND tasks.billable IS FALSE"; + } + + return ""; + } + protected static formatEndDate(endDate: string) { const end = moment(endDate).format("YYYY-MM-DD"); const fEndDate = moment(end); @@ -173,6 +190,9 @@ export default abstract class ReportingControllerBase extends WorklenzController (SELECT color_code FROM sys_project_healths WHERE sys_project_healths.id = p.health_id) AS health_color, + (SELECT name + FROM sys_project_healths + WHERE sys_project_healths.id = p.health_id) AS health_name, pc.id AS category_id, pc.name AS category_name, diff --git a/worklenz-backend/src/controllers/reporting/reporting-members-controller.ts b/worklenz-backend/src/controllers/reporting/reporting-members-controller.ts index a4c6cd12..97500437 100644 --- a/worklenz-backend/src/controllers/reporting/reporting-members-controller.ts +++ b/worklenz-backend/src/controllers/reporting/reporting-members-controller.ts @@ -862,7 +862,7 @@ export default class ReportingMembersController extends ReportingControllerBase } - private static async memberTimeLogsData(durationClause: string, minMaxDateClause: string, team_id: string, team_member_id: string, includeArchived: boolean, userId: string) { + private static async memberTimeLogsData(durationClause: string, minMaxDateClause: string, team_id: string, team_member_id: string, includeArchived: boolean, userId: string, billableQuery = "") { const archivedClause = includeArchived ? "" @@ -884,7 +884,7 @@ export default class ReportingMembersController extends ReportingControllerBase FROM task_work_log twl WHERE twl.user_id = tmiv.user_id ${durationClause} - AND task_id IN (SELECT id FROM tasks WHERE project_id IN (SELECT id FROM projects WHERE team_id = $1) ${archivedClause} ) + AND task_id IN (SELECT id FROM tasks WHERE project_id IN (SELECT id FROM projects WHERE team_id = $1) ${archivedClause} ${billableQuery}) ORDER BY twl.updated_at DESC) tl) AS time_logs ${minMaxDateClause} FROM team_member_info_view tmiv @@ -1017,14 +1017,33 @@ export default class ReportingMembersController extends ReportingControllerBase } + protected static buildBillableQuery(selectedStatuses: { billable: boolean; nonBillable: boolean }): string { + const { billable, nonBillable } = selectedStatuses; + + if (billable && nonBillable) { + // Both are enabled, no need to filter + return ""; + } else if (billable) { + // Only billable is enabled + return " AND tasks.billable IS TRUE"; + } else if (nonBillable) { + // Only non-billable is enabled + return " AND tasks.billable IS FALSE"; + } + + return ""; + } + @HandleExceptions() public static async getMemberTimelogs(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { - const { team_member_id, team_id, duration, date_range, archived } = req.body; + const { team_member_id, team_id, duration, date_range, archived, billable } = req.body; const durationClause = ReportingMembersController.getDateRangeClauseMembers(duration || DATE_RANGES.LAST_WEEK, date_range, "twl"); const minMaxDateClause = this.getMinMaxDates(duration || DATE_RANGES.LAST_WEEK, date_range, "task_work_log"); - const logGroups = await this.memberTimeLogsData(durationClause, minMaxDateClause, team_id, team_member_id, archived, req.user?.id as string); + const billableQuery = this.buildBillableQuery(billable); + + const logGroups = await this.memberTimeLogsData(durationClause, minMaxDateClause, team_id, team_member_id, archived, req.user?.id as string, billableQuery); return res.status(200).send(new ServerResponse(true, logGroups)); } @@ -1049,6 +1068,7 @@ export default class ReportingMembersController extends ReportingControllerBase const completedDurationClasue = this.completedDurationFilter(duration as string, dateRange); const overdueClauseByDate = this.getActivityLogsOverdue(duration as string, dateRange); const taskSelectorClause = this.getTaskSelectorClause(); + const durationFilter = this.memberTasksDurationFilter(duration as string, dateRange); const q = ` SELECT name AS team_member_name, @@ -1059,6 +1079,12 @@ export default class ReportingMembersController extends ReportingControllerBase LEFT JOIN tasks_assignees ta ON t.id = ta.task_id WHERE ta.team_member_id = $1 ${assignClause} ${archivedClause}) assigned) AS assigned, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(assigned))), '[]') + FROM (${taskSelectorClause} + FROM tasks t + LEFT JOIN tasks_assignees ta ON t.id = ta.task_id + WHERE ta.team_member_id = $1 ${durationFilter} ${assignClause} ${archivedClause}) assigned) AS total, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(completed))), '[]') FROM (${taskSelectorClause} FROM tasks t @@ -1095,6 +1121,11 @@ export default class ReportingMembersController extends ReportingControllerBase const body = { team_member_name: data.team_member_name, groups: [ + { + name: "Total Tasks", + color_code: "#7590c9", + tasks: data.total ? data.total : 0 + }, { name: "Tasks Assigned", color_code: "#7590c9", @@ -1114,7 +1145,7 @@ export default class ReportingMembersController extends ReportingControllerBase name: "Tasks Ongoing", color_code: "#7cb5ec", tasks: data.ongoing ? data.ongoing : 0 - } + }, ] }; diff --git a/worklenz-backend/src/controllers/schedule-v2/schedule-controller.ts b/worklenz-backend/src/controllers/schedule-v2/schedule-controller.ts new file mode 100644 index 00000000..a008c06d --- /dev/null +++ b/worklenz-backend/src/controllers/schedule-v2/schedule-controller.ts @@ -0,0 +1,407 @@ +import db from "../../config/db"; +import { ParsedQs } from "qs"; +import HandleExceptions from "../../decorators/handle-exceptions"; +import { IWorkLenzRequest } from "../../interfaces/worklenz-request"; +import { IWorkLenzResponse } from "../../interfaces/worklenz-response"; +import { ServerResponse } from "../../models/server-response"; +import { TASK_PRIORITY_COLOR_ALPHA, TASK_STATUS_COLOR_ALPHA, UNMAPPED } from "../../shared/constants"; +import { getColor } from "../../shared/utils"; +import moment, { Moment } from "moment"; +import momentTime from "moment-timezone"; +import WorklenzControllerBase from "../worklenz-controller-base"; + +interface IDateUnions { + date_union: { + start_date: string | null; + end_date: string | null; + }, + logs_date_union: { + start_date: string | null; + end_date: string | null; + }, + allocated_date_union: { + start_date: string | null; + end_date: string | null; + } +} + +interface IDatesPair { + start_date: string | null, + end_date: string | null +} + +export default class ScheduleControllerV2 extends WorklenzControllerBase { + + @HandleExceptions() + public static async getSettings(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + // get organization working days + const getDataq = `SELECT organization_id, array_agg(initcap(day)) AS working_days + FROM ( + SELECT organization_id, + unnest(ARRAY['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']) AS day, + unnest(ARRAY[monday, tuesday, wednesday, thursday, friday, saturday, sunday]) AS is_working + FROM public.organization_working_days + WHERE organization_id IN ( + SELECT id FROM organizations + WHERE user_id = $1 + ) + ) t + WHERE t.is_working + GROUP BY organization_id LIMIT 1;`; + + const workingDaysResults = await db.query(getDataq, [req.user?.owner_id]); + const [workingDays] = workingDaysResults.rows; + + // get organization working hours + const getDataHoursq = `SELECT working_hours FROM organizations WHERE user_id = $1 GROUP BY id LIMIT 1;`; + + const workingHoursResults = await db.query(getDataHoursq, [req.user?.owner_id]); + + const [workingHours] = workingHoursResults.rows; + + return res.status(200).send(new ServerResponse(true, { workingDays: workingDays?.working_days, workingHours: workingHours?.working_hours })); + } + + @HandleExceptions() + public static async updateSettings(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const { workingDays, workingHours } = req.body; + + // Days of the week + const days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]; + + // Generate the SET clause dynamically + const setClause = days + .map(day => `${day.toLowerCase()} = ${workingDays.includes(day)}`) + .join(", "); + + const updateQuery = ` + UPDATE public.organization_working_days + SET ${setClause}, updated_at = CURRENT_TIMESTAMP + WHERE organization_id IN ( + SELECT organization_id FROM organizations + WHERE user_id = $1 + ); + `; + + await db.query(updateQuery, [req.user?.owner_id]); + + const getDataHoursq = `UPDATE organizations SET working_hours = $1 WHERE user_id = $2;`; + + await db.query(getDataHoursq, [workingHours, req.user?.owner_id]); + + return res.status(200).send(new ServerResponse(true, {})); + } + + @HandleExceptions() + public static async getDates(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + + const { date, type } = req.params; + + if (type === "week") { + const getDataq = `WITH input_date AS ( + SELECT + $1::DATE AS given_date, + (SELECT id FROM organizations WHERE user_id=$2 LIMIT 1) AS organization_id + ), + week_range AS ( + SELECT + (given_date - (EXTRACT(DOW FROM given_date)::INT + 6) % 7)::DATE AS start_date, -- Current week start date + (given_date - (EXTRACT(DOW FROM given_date)::INT + 6) % 7 + 6)::DATE AS end_date, -- Current week end date + (given_date - (EXTRACT(DOW FROM given_date)::INT + 6) % 7 + 7)::DATE AS next_week_start, -- Next week start date + (given_date - (EXTRACT(DOW FROM given_date)::INT + 6) % 7 + 13)::DATE AS next_week_end, -- Next week end date + TO_CHAR(given_date, 'Mon YYYY') AS month_year, -- Format the month as 'Jan 2025' + EXTRACT(DAY FROM given_date) AS day_number, -- Extract the day from the date + (given_date - (EXTRACT(DOW FROM given_date)::INT + 6) % 7)::DATE AS chart_start, -- First week start date + (given_date - (EXTRACT(DOW FROM given_date)::INT + 6) % 7 + 13)::DATE AS chart_end, -- Second week end date + CURRENT_DATE::DATE AS today, + organization_id + FROM input_date + ), + org_working_days AS ( + SELECT + organization_id, + monday, tuesday, wednesday, thursday, friday, saturday, sunday + FROM organization_working_days + WHERE organization_id = (SELECT organization_id FROM week_range) + ), + days AS ( + SELECT + generate_series((SELECT start_date FROM week_range), (SELECT next_week_end FROM week_range), '1 day'::INTERVAL)::DATE AS date + ), + formatted_days AS ( + SELECT + d.date, + TO_CHAR(d.date, 'Dy') AS day_name, + EXTRACT(DAY FROM d.date) AS day, + TO_CHAR(d.date, 'Mon YYYY') AS month, -- Format the month as 'Jan 2025' + CASE + WHEN EXTRACT(DOW FROM d.date) = 0 THEN (SELECT sunday FROM org_working_days) + WHEN EXTRACT(DOW FROM d.date) = 1 THEN (SELECT monday FROM org_working_days) + WHEN EXTRACT(DOW FROM d.date) = 2 THEN (SELECT tuesday FROM org_working_days) + WHEN EXTRACT(DOW FROM d.date) = 3 THEN (SELECT wednesday FROM org_working_days) + WHEN EXTRACT(DOW FROM d.date) = 4 THEN (SELECT thursday FROM org_working_days) + WHEN EXTRACT(DOW FROM d.date) = 5 THEN (SELECT friday FROM org_working_days) + WHEN EXTRACT(DOW FROM d.date) = 6 THEN (SELECT saturday FROM org_working_days) + END AS is_weekend, + CASE WHEN d.date = (SELECT today FROM week_range) THEN TRUE ELSE FALSE END AS is_today + FROM days d + ), + aggregated_days AS ( + SELECT + jsonb_agg( + jsonb_build_object( + 'day', day, + 'month', month, -- Include formatted month + 'name', day_name, + 'isWeekend', NOT is_weekend, + 'isToday', is_today + ) ORDER BY date + ) AS days_json + FROM formatted_days + ) + SELECT jsonb_build_object( + 'date_data', jsonb_agg( + jsonb_build_object( + 'month', (SELECT month_year FROM week_range), -- Formatted month-year (e.g., Jan 2025) + 'day', (SELECT day_number FROM week_range), -- Dynamic day number + 'weeks', '[]', -- Empty weeks array for now + 'days', (SELECT days_json FROM aggregated_days) -- Aggregated days data + ) + ), + 'chart_start', (SELECT chart_start FROM week_range), -- First week start date + 'chart_end', (SELECT chart_end FROM week_range) -- Second week end date + ) AS result_json;`; + + const results = await db.query(getDataq, [date, req.user?.owner_id]); + const [data] = results.rows; + return res.status(200).send(new ServerResponse(true, data.result_json)); + } else if (type === "month") { + + const getDataq = `WITH params AS ( + SELECT + DATE_TRUNC('month', $1::DATE)::DATE AS start_date, -- First day of the month + (DATE_TRUNC('month', $1::DATE) + INTERVAL '1 month' - INTERVAL '1 day')::DATE AS end_date, -- Last day of the month + CURRENT_DATE::DATE AS today, + (SELECT id FROM organizations WHERE user_id = $2 LIMIT 1) AS org_id + ), + days AS ( + SELECT + generate_series( + (SELECT start_date FROM params), + (SELECT end_date FROM params), + '1 day'::INTERVAL + )::DATE AS date + ), + org_working_days AS ( + SELECT + monday, tuesday, wednesday, thursday, friday, saturday, sunday + FROM organization_working_days + WHERE organization_id = (SELECT org_id FROM params) + LIMIT 1 + ), + formatted_days AS ( + SELECT + d.date, + TO_CHAR(d.date, 'Dy') AS day_name, + EXTRACT(DAY FROM d.date) AS day, + -- Dynamically check if the day is a weekend based on the organization's settings + CASE + WHEN EXTRACT(DOW FROM d.date) = 0 THEN NOT (SELECT sunday FROM org_working_days) + WHEN EXTRACT(DOW FROM d.date) = 1 THEN NOT (SELECT monday FROM org_working_days) + WHEN EXTRACT(DOW FROM d.date) = 2 THEN NOT (SELECT tuesday FROM org_working_days) + WHEN EXTRACT(DOW FROM d.date) = 3 THEN NOT (SELECT wednesday FROM org_working_days) + WHEN EXTRACT(DOW FROM d.date) = 4 THEN NOT (SELECT thursday FROM org_working_days) + WHEN EXTRACT(DOW FROM d.date) = 5 THEN NOT (SELECT friday FROM org_working_days) + WHEN EXTRACT(DOW FROM d.date) = 6 THEN NOT (SELECT saturday FROM org_working_days) + END AS is_weekend, + CASE WHEN d.date = (SELECT today FROM params) THEN TRUE ELSE FALSE END AS is_today + FROM days d + ), + grouped_by_month AS ( + SELECT + TO_CHAR(date, 'Mon YYYY') AS month_name, + jsonb_agg( + jsonb_build_object( + 'day', day, + 'name', day_name, + 'isWeekend', is_weekend, + 'isToday', is_today + ) ORDER BY date + ) AS days + FROM formatted_days + GROUP BY month_name + ) + SELECT jsonb_build_object( + 'date_data', jsonb_agg( + jsonb_build_object( + 'month', month_name, + 'weeks', '[]'::JSONB, -- Placeholder for weeks data + 'days', days + ) ORDER BY month_name + ), + 'chart_start', (SELECT start_date FROM params), + 'chart_end', (SELECT end_date FROM params) + ) AS result_json + FROM grouped_by_month;`; + + const results = await db.query(getDataq, [date, req.user?.owner_id]); + const [data] = results.rows; + return res.status(200).send(new ServerResponse(true, data.result_json)); + + } + + return res.status(200).send(new ServerResponse(true, [])); + } + + @HandleExceptions() + public static async getOrganizationMembers(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + + const getDataq = `SELECT DISTINCT ON (users.email) + team_members.id AS team_member_id, + users.id AS id, + users.name AS name, + users.email AS email, + '[]'::JSONB AS projects + FROM team_members + INNER JOIN users ON users.id = team_members.user_id + WHERE team_members.team_id IN ( + SELECT id FROM teams + WHERE organization_id IN ( + SELECT id FROM organizations + WHERE user_id = $1 + LIMIT 1 + ) + ) + ORDER BY users.email ASC, users.name ASC;`; + + const results = await db.query(getDataq, [req.user?.owner_id]); + return res.status(200).send(new ServerResponse(true, results.rows)); + + } + + @HandleExceptions() + public static async getOrganizationMemberProjects(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + + const { id } = req.params; + + const getDataq = `WITH project_dates AS ( + SELECT + pm.project_id, + MIN(pm.allocated_from) AS start_date, + MAX(pm.allocated_to) AS end_date, + MAX(pm.seconds_per_day) / 3600 AS hours_per_day, -- Convert max seconds per day to hours per day + ( + -- Calculate total working days between start and end dates + SELECT COUNT(*) + FROM generate_series(MIN(pm.allocated_from), MAX(pm.allocated_to), '1 day'::interval) AS day + JOIN public.organization_working_days owd ON owd.organization_id = t.organization_id + WHERE + (EXTRACT(ISODOW FROM day) = 1 AND owd.monday = true) OR + (EXTRACT(ISODOW FROM day) = 2 AND owd.tuesday = true) OR + (EXTRACT(ISODOW FROM day) = 3 AND owd.wednesday = true) OR + (EXTRACT(ISODOW FROM day) = 4 AND owd.thursday = true) OR + (EXTRACT(ISODOW FROM day) = 5 AND owd.friday = true) OR + (EXTRACT(ISODOW FROM day) = 6 AND owd.saturday = true) OR + (EXTRACT(ISODOW FROM day) = 7 AND owd.sunday = true) + ) * (MAX(pm.seconds_per_day) / 3600) AS total_hours -- Multiply by hours per day + FROM public.project_member_allocations pm + JOIN public.projects p ON pm.project_id = p.id + JOIN public.teams t ON p.team_id = t.id + GROUP BY pm.project_id, t.organization_id + ), + projects_with_offsets AS ( + SELECT + p.name AS project_name, + p.id AS project_id, + COALESCE(pd.hours_per_day, 0) AS hours_per_day, -- Default to 8 if not available in project_member_allocations + COALESCE(pd.total_hours, 0) AS total_hours, -- Calculated total hours based on working days + pd.start_date, + pd.end_date, + p.team_id, + tm.user_id, + -- Calculate indicator_offset dynamically: days difference from earliest project start date * 75px + COALESCE( + (DATE_PART('day', pd.start_date - MIN(pd.start_date) OVER ())) * 75, + 0 + ) AS indicator_offset, + -- Calculate indicator_width as the number of days * 75 pixels per day + COALESCE((DATE_PART('day', pd.end_date - pd.start_date) + 1) * 75, 75) AS indicator_width, -- Fallback to 75 if no dates exist + 75 AS min_width -- 75px minimum width for a 1-day project + FROM public.projects p + LEFT JOIN project_dates pd ON p.id = pd.project_id + JOIN public.team_members tm ON tm.team_id = p.team_id + JOIN public.teams t ON p.team_id = t.id + WHERE tm.user_id = $2 + AND tm.team_id = $1 + ORDER BY pd.start_date, pd.end_date -- Order by start and end date + ) + SELECT jsonb_agg(jsonb_build_object( + 'name', project_name, + 'id', project_id, + 'hours_per_day', hours_per_day, + 'total_hours', total_hours, + 'date_union', jsonb_build_object( + 'start', start_date::DATE, + 'end', end_date::DATE + ), + 'indicator_offset', indicator_offset, + 'indicator_width', indicator_width, + 'tasks', '[]'::jsonb, -- Empty tasks array for now, + 'default_values', jsonb_build_object( + 'allocated_from', start_date::DATE, + 'allocated_to', end_date::DATE, + 'seconds_per_day', hours_per_day, + 'total_seconds', total_hours + ) + )) AS projects + FROM projects_with_offsets;`; + + const results = await db.query(getDataq, [req.user?.team_id, id]); + const [data] = results.rows; + return res.status(200).send(new ServerResponse(true, { projects: data.projects, id })); + + } + + @HandleExceptions() + public static async createSchedule(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + + const { allocated_from, allocated_to, project_id, team_member_id, seconds_per_day } = req.body; + + const fromFormat = moment(allocated_from).format("YYYY-MM-DD"); + const toFormat = moment(allocated_to).format("YYYY-MM-DD"); + + const getDataq1 = ` + SELECT id + FROM project_member_allocations + WHERE project_id = $1 + AND team_member_id = $2 + AND ( + -- Case 1: The given range starts inside an existing range + ($3 BETWEEN allocated_from AND allocated_to) + OR + -- Case 2: The given range ends inside an existing range + ($4 BETWEEN allocated_from AND allocated_to) + OR + -- Case 3: The given range fully covers an existing range + (allocated_from BETWEEN $3 AND $4 AND allocated_to BETWEEN $3 AND $4) + OR + -- Case 4: The existing range fully covers the given range + (allocated_from <= $3 AND allocated_to >= $4) + );`; + + const results1 = await db.query(getDataq1, [project_id, team_member_id, fromFormat, toFormat]); + + const [data] = results1.rows; + if (data) { + return res.status(200).send(new ServerResponse(false, null, "Allocation already exists!")); + } + + const getDataq = `INSERT INTO public.project_member_allocations( + project_id, team_member_id, allocated_from, allocated_to, seconds_per_day) + VALUES ($1, $2, $3, $4, $5);`; + + const results = await db.query(getDataq, [project_id, team_member_id, allocated_from, allocated_to, Number(seconds_per_day) * 60 * 60]); + return res.status(200).send(new ServerResponse(true, null, "Allocated successfully!")); + + } +} diff --git a/worklenz-backend/src/controllers/schedule/schedule-controller.ts b/worklenz-backend/src/controllers/schedule/schedule-controller.ts index 459c44b8..212145b5 100644 --- a/worklenz-backend/src/controllers/schedule/schedule-controller.ts +++ b/worklenz-backend/src/controllers/schedule/schedule-controller.ts @@ -52,6 +52,83 @@ export default class ScheduleControllerV2 extends ScheduleTasksControllerBase { private static GLOBAL_START_DATE = moment().format("YYYY-MM-DD"); private static GLOBAL_END_DATE = moment().format("YYYY-MM-DD"); + // Migrate data + @HandleExceptions() + public static async migrate(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const getDataq = `SELECT p.id, + (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) + FROM (SELECT tmiv.team_member_id, + tmiv.user_id, + + LEAST( + (SELECT MIN(LEAST(start_date, end_date)) AS start_date + FROM tasks + INNER JOIN tasks_assignees ta ON tasks.id = ta.task_id + WHERE archived IS FALSE + AND project_id = p.id + AND ta.team_member_id = tmiv.team_member_id), + (SELECT MIN(twl.created_at - INTERVAL '1 second' * twl.time_spent) AS ll_start_date + FROM task_work_log twl + INNER JOIN tasks t ON twl.task_id = t.id AND t.archived IS FALSE + WHERE t.project_id = p.id + AND twl.user_id = tmiv.user_id) + ) AS lowest_date, + + GREATEST( + (SELECT MAX(GREATEST(start_date, end_date)) AS end_date + FROM tasks + INNER JOIN tasks_assignees ta ON tasks.id = ta.task_id + WHERE archived IS FALSE + AND project_id = p.id + AND ta.team_member_id = tmiv.team_member_id), + (SELECT MAX(twl.created_at - INTERVAL '1 second' * twl.time_spent) AS ll_end_date + FROM task_work_log twl + INNER JOIN tasks t ON twl.task_id = t.id AND t.archived IS FALSE + WHERE t.project_id = p.id + AND twl.user_id = tmiv.user_id) + ) AS greatest_date + + FROM project_members pm + INNER JOIN team_member_info_view tmiv + ON pm.team_member_id = tmiv.team_member_id + WHERE project_id = p.id) rec) AS members + +FROM projects p +WHERE team_id IS NOT NULL +AND p.id NOT IN (SELECT project_id FROM archived_projects)`; + + const projectMembersResults = await db.query(getDataq); + + const projectMemberData = projectMembersResults.rows; + + const arrayToInsert = []; + + for (const data of projectMemberData) { + if (data.members.length) { + for (const member of data.members) { + + const body = { + project_id: data.id, + team_member_id: member.team_member_id, + allocated_from: member.lowest_date ? member.lowest_date : null, + allocated_to: member.greatest_date ? member.greatest_date : null + }; + + if (body.allocated_from && body.allocated_to) arrayToInsert.push(body); + + } + } + } + + const insertArray = JSON.stringify(arrayToInsert); + + const insertFunctionCall = `SELECT migrate_member_allocations($1)`; + await db.query(insertFunctionCall, [insertArray]); + + return res.status(200).send(new ServerResponse(true, "")); + } + + private static async getFirstLastDates(teamId: string, userId: string) { const q = `SELECT MIN(LEAST(allocated_from, allocated_to)) AS start_date, MAX(GREATEST(allocated_from, allocated_to)) AS end_date, diff --git a/worklenz-backend/src/controllers/sub-tasks-controller.ts b/worklenz-backend/src/controllers/sub-tasks-controller.ts index 279119e6..1875cce5 100644 --- a/worklenz-backend/src/controllers/sub-tasks-controller.ts +++ b/worklenz-backend/src/controllers/sub-tasks-controller.ts @@ -5,7 +5,7 @@ import {IWorkLenzResponse} from "../interfaces/worklenz-response"; import db from "../config/db"; import {ServerResponse} from "../models/server-response"; -import {PriorityColorCodes, TASK_PRIORITY_COLOR_ALPHA, TASK_STATUS_COLOR_ALPHA} from "../shared/constants"; +import {PriorityColorCodes, PriorityColorCodesDark, TASK_PRIORITY_COLOR_ALPHA, TASK_STATUS_COLOR_ALPHA} from "../shared/constants"; import {getColor} from "../shared/utils"; import WorklenzControllerBase from "./worklenz-controller-base"; import HandleExceptions from "../decorators/handle-exceptions"; @@ -33,6 +33,7 @@ export default class SubTasksController extends WorklenzControllerBase { (ts.name) AS status_name, TRUE AS is_sub_task, (tsc.color_code) AS status_color, + (tsc.color_code_dark) AS status_color_dark, (SELECT name FROM projects WHERE id = t.project_id) AS project_name, (SELECT value FROM task_priorities WHERE id = t.priority_id) AS priority_value, total_minutes, @@ -46,11 +47,12 @@ export default class SubTasksController extends WorklenzControllerBase { WHERE task_id = t.id ORDER BY name) r) AS labels, (SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON) - FROM (SELECT task_statuses.id, task_statuses.name, stsc.color_code + FROM (SELECT task_statuses.id, task_statuses.name, stsc.color_code, stsc.color_code_dark FROM task_statuses INNER JOIN sys_task_status_categories stsc ON task_statuses.category_id = stsc.id WHERE project_id = t.project_id - ORDER BY task_statuses.name) rec) AS statuses + ORDER BY task_statuses.name) rec) AS statuses, + t.completed_at FROM tasks t INNER JOIN task_statuses ts ON ts.id = t.status_id INNER JOIN task_priorities tp ON tp.id = t.priority_id @@ -62,6 +64,7 @@ export default class SubTasksController extends WorklenzControllerBase { for (const task of result.rows) { task.priority_color = PriorityColorCodes[task.priority_value] || null; + task.priority_color_dark = PriorityColorCodesDark[task.priority_value] || null; task.time_spent = {hours: Math.floor(task.total_minutes_spent / 60), minutes: task.total_minutes_spent % 60}; task.time_spent_string = `${task.time_spent.hours}h ${task.time_spent.minutes}m`; @@ -72,6 +75,7 @@ export default class SubTasksController extends WorklenzControllerBase { task.labels = this.createTagList(task.labels, 2); task.status_color = task.status_color + TASK_STATUS_COLOR_ALPHA; + task.status_color_dark = task.status_color_dark + TASK_STATUS_COLOR_ALPHA; task.priority_color = task.priority_color + TASK_PRIORITY_COLOR_ALPHA; } diff --git a/worklenz-backend/src/controllers/task-comments-controller.ts b/worklenz-backend/src/controllers/task-comments-controller.ts index a4bc17b0..2eb210a7 100644 --- a/worklenz-backend/src/controllers/task-comments-controller.ts +++ b/worklenz-backend/src/controllers/task-comments-controller.ts @@ -6,11 +6,13 @@ import { ServerResponse } from "../models/server-response"; import WorklenzControllerBase from "./worklenz-controller-base"; import HandleExceptions from "../decorators/handle-exceptions"; import { NotificationsService } from "../services/notifications/notifications.service"; -import { log_error } from "../shared/utils"; -import { HTML_TAG_REGEXP } from "../shared/constants"; +import { humanFileSize, log_error, megabytesToBytes } from "../shared/utils"; +import { HTML_TAG_REGEXP, S3_URL } from "../shared/constants"; import { getBaseUrl } from "../cron_jobs/helpers"; import { ICommentEmailNotification } from "../interfaces/comment-email-notification"; import { sendTaskComment } from "../shared/email-notifications"; +import { getRootDir, uploadBase64, getKey, getTaskAttachmentKey, createPresignedUrlWithClient } from "../shared/s3"; +import { getFreePlanSettings, getUsedStorage } from "../shared/paddle-utils"; interface ITaskAssignee { team_member_id: string; @@ -99,11 +101,134 @@ export default class TaskCommentsController extends WorklenzControllerBase { public static async create(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { req.body.user_id = req.user?.id; req.body.team_id = req.user?.team_id; - const {mentions} = req.body; + const { mentions, attachments, task_id } = req.body; + const url = `${S3_URL}/${getRootDir()}`; let commentContent = req.body.content; if (mentions.length > 0) { - commentContent = await this.replaceContent(commentContent, mentions); + commentContent = this.replaceContent(commentContent, mentions); + } + + req.body.content = commentContent; + + const q = `SELECT create_task_comment($1) AS comment;`; + const result = await db.query(q, [JSON.stringify(req.body)]); + const [data] = result.rows; + + const response = data.comment; + + const commentId = response.id; + + if (attachments.length !== 0) { + for (const attachment of attachments) { + const q = ` + INSERT INTO task_comment_attachments (name, type, size, task_id, comment_id, team_id, project_id) + VALUES ($1, $2, $3, $4, $5, $6, $7) + RETURNING id, name, type, task_id, comment_id, created_at, + CONCAT($8::TEXT, '/', team_id, '/', project_id, '/', task_id, '/', comment_id, '/', id, '.', type) AS url; + `; + + const result = await db.query(q, [ + attachment.file_name, + attachment.file_name.split(".").pop(), + attachment.size, + task_id, + commentId, + req.user?.team_id, + attachment.project_id, + url + ]); + + const [data] = result.rows; + + const s3Url = await uploadBase64(attachment.file, getTaskAttachmentKey(req.user?.team_id as string, attachment.project_id, task_id, commentId, data.id, data.type)); + + if (!data?.id || !s3Url) + return res.status(200).send(new ServerResponse(false, null, "Attachment upload failed")); + } + } + + const mentionMessage = `${req.user?.name} has mentioned you in a comment on ${response.task_name} (${response.team_name})`; + // const mentions = [...new Set(req.body.mentions || [])] as string[]; // remove duplicates + + const assignees = await getAssignees(req.body.task_id); + + const commentMessage = `${req.user?.name} added a comment on ${response.task_name} (${response.team_name})`; + for (const member of assignees || []) { + if (member.user_id && member.user_id === req.user?.id) continue; + + void NotificationsService.createNotification({ + userId: member.user_id, + teamId: req.user?.team_id as string, + socketId: member.socket_id, + message: commentMessage, + taskId: req.body.task_id, + projectId: response.project_id + }); + + if (member.email_notifications_enabled) + await this.sendMail({ + message: commentMessage, + receiverEmail: member.email, + receiverName: member.name, + content: req.body.content, + commentId: response.id, + projectId: response.project_id, + taskId: req.body.task_id, + teamName: response.team_name, + projectName: response.project_name, + taskName: response.task_name + }); + } + + const senderUserId = req.user?.id as string; + + for (const mention of mentions) { + if (mention) { + const member = await this.getUserDataByTeamMemberId(senderUserId, mention.team_member_id, response.project_id); + if (member) { + + NotificationsService.sendNotification({ + team: member.team, + receiver_socket_id: member.socket_id, + message: mentionMessage, + task_id: req.body.task_id, + project_id: response.project_id, + project: member.project, + project_color: member.project_color, + team_id: req.user?.team_id as string + }); + + if (member.email_notifications_enabled) + await this.sendMail({ + message: mentionMessage, + receiverEmail: member.email, + receiverName: member.user_name, + content: req.body.content, + commentId: response.id, + projectId: response.project_id, + taskId: req.body.task_id, + teamName: response.team_name, + projectName: response.project_name, + taskName: response.task_name + }); + } + + } + } + + return res.status(200).send(new ServerResponse(true, data.comment)); + } + + @HandleExceptions() + public static async update(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + req.body.user_id = req.user?.id; + req.body.team_id = req.user?.team_id; + const { mentions, comment_id } = req.body; + + let commentContent = req.body.content; + if (mentions.length > 0) { + commentContent = await this.replaceContent(commentContent, mentions); } req.body.content = commentContent; @@ -210,46 +335,90 @@ export default class TaskCommentsController extends WorklenzControllerBase { @HandleExceptions() public static async getByTaskId(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { - const q = ` - SELECT task_comments.id, - tc.text_content AS content, - task_comments.user_id, - task_comments.team_member_id, - (SELECT name FROM team_member_info_view WHERE team_member_info_view.team_member_id = tm.id) AS member_name, - u.avatar_url, - task_comments.created_at, - (SELECT COALESCE(JSON_AGG(rec), '[]'::JSON) - FROM (SELECT tmiv.name AS user_name, - tmiv.email AS user_email - FROM task_comment_mentions tcm - LEFT JOIN team_member_info_view tmiv ON tcm.informed_by = tmiv.team_member_id - WHERE tcm.comment_id = task_comments.id) rec) AS mentions - FROM task_comments - INNER JOIN task_comment_contents tc ON task_comments.id = tc.comment_id - INNER JOIN team_members tm ON task_comments.team_member_id = tm.id - LEFT JOIN users u ON tm.user_id = u.id - WHERE task_comments.task_id = $1 - ORDER BY task_comments.created_at DESC; - `; - const result = await db.query(q, [req.params.id]); // task id + const result = await TaskCommentsController.getTaskComments(req.params.id); // task id + return res.status(200).send(new ServerResponse(true, result.rows)); + } + + private static async getTaskComments(taskId: string) { + const url = `${S3_URL}/${getRootDir()}`; + + const q = `SELECT task_comments.id, + tc.text_content AS content, + task_comments.user_id, + task_comments.team_member_id, + (SELECT name FROM team_member_info_view WHERE team_member_info_view.team_member_id = tm.id) AS member_name, + u.avatar_url, + task_comments.created_at, + (SELECT COALESCE(JSON_AGG(rec), '[]'::JSON) + FROM (SELECT tmiv.name AS user_name, + tmiv.email AS user_email + FROM task_comment_mentions tcm + LEFT JOIN team_member_info_view tmiv ON tcm.informed_by = tmiv.team_member_id + WHERE tcm.comment_id = task_comments.id) rec) AS mentions, + (SELECT JSON_BUILD_OBJECT( + 'likes', + JSON_BUILD_OBJECT( + 'count', (SELECT COUNT(*) + FROM task_comment_reactions tcr + WHERE tcr.comment_id = task_comments.id + AND reaction_type = 'like'), + 'liked_members', COALESCE( + (SELECT JSON_AGG(tmiv.name) + FROM task_comment_reactions tcr + JOIN team_member_info_view tmiv ON tcr.team_member_id = tmiv.team_member_id + WHERE tcr.comment_id = task_comments.id + AND tcr.reaction_type = 'like'), + '[]'::JSON + ), + 'liked_member_ids', COALESCE( + (SELECT JSON_AGG(tmiv.team_member_id) + FROM task_comment_reactions tcr + JOIN team_member_info_view tmiv ON tcr.team_member_id = tmiv.team_member_id + WHERE tcr.comment_id = task_comments.id + AND tcr.reaction_type = 'like'), + '[]'::JSON + ) + ) + )) AS reactions, + (SELECT COALESCE(JSON_AGG(rec), '[]'::JSON) + FROM (SELECT id, created_at, name, size, type, (CONCAT('/', team_id, '/', project_id, '/', task_id, '/', comment_id, '/', id, '.', type)) AS url + FROM task_comment_attachments tca + WHERE tca.comment_id = task_comments.id) rec) AS attachments + FROM task_comments + LEFT JOIN task_comment_contents tc ON task_comments.id = tc.comment_id + INNER JOIN team_members tm ON task_comments.team_member_id = tm.id + LEFT JOIN users u ON tm.user_id = u.id + WHERE task_comments.task_id = $1 + ORDER BY task_comments.created_at;`; + const result = await db.query(q, [taskId]); // task id for (const comment of result.rows) { + if (!comment.content) comment.content = ""; + comment.rawContent = await comment.content; comment.content = await comment.content.replace(/\n/g, "
"); - const {mentions} = comment; + comment.edit = false; + const { mentions } = comment; if (mentions.length > 0) { const placeHolders = comment.content.match(/{\d+}/g); if (placeHolders) { placeHolders.forEach((placeHolder: { match: (arg0: RegExp) => string[]; }) => { - const index = parseInt(placeHolder.match(/\d+/)[0]); - if (index >= 0 && index < comment.mentions.length) { - comment.content = comment.content.replace(placeHolder, ` @${comment.mentions[index].user_name} `); - } + const index = parseInt(placeHolder.match(/\d+/)[0]); + if (index >= 0 && index < comment.mentions.length) { + comment.rawContent = comment.rawContent.replace(placeHolder, `@${comment.mentions[index].user_name}`); + comment.content = comment.content.replace(placeHolder, ` @${comment.mentions[index].user_name} `); + } }); } } + + for (const attachment of comment.attachments) { + attachment.size = humanFileSize(attachment.size); + attachment.url = url + attachment.url; + } + } - return res.status(200).send(new ServerResponse(true, result.rows)); + return result; } @HandleExceptions() @@ -262,4 +431,186 @@ export default class TaskCommentsController extends WorklenzControllerBase { const result = await db.query(q, [req.params.id, req.params.taskId, req.user?.id || null]); return res.status(200).send(new ServerResponse(true, result.rows)); } + + @HandleExceptions() + public static async deleteAttachmentById(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const q = `DELETE + FROM task_comment_attachments + WHERE id = $1;`; + const result = await db.query(q, [req.params.id]); + return res.status(200).send(new ServerResponse(true, result.rows)); + } + + private static async checkIfAlreadyExists(commentId: string, teamMemberId: string | undefined, reaction_type: string) { + if (!teamMemberId) return; + try { + const q = `SELECT EXISTS(SELECT 1 FROM task_comment_reactions WHERE comment_id = $1 AND team_member_id = $2 AND reaction_type = $3)`; + const result = await db.query(q, [commentId, teamMemberId, reaction_type]); + const [data] = result.rows; + return data.exists; + } catch (error) { + log_error(error); + } + } + + private static async getTaskCommentData(commentId: string) { + if (!commentId) return; + try { + const q = `SELECT tc.user_id, + t.project_id, + t.name AS task_name, + (SELECT team_id FROM projects p WHERE p.id = t.project_id) AS team_id, + (SELECT name FROM teams te WHERE id = (SELECT team_id FROM projects p WHERE p.id = t.project_id)) AS team_name, + (SELECT u.socket_id FROM users u WHERE u.id = tc.user_id) AS socket_id, + (SELECT name FROM team_member_info_view tmiv WHERE tmiv.team_member_id = tcr.team_member_id) AS reactor_name + FROM task_comments tc + LEFT JOIN tasks t ON t.id = tc.task_id + LEFT JOIN task_comment_reactions tcr ON tc.id = tcr.comment_id + WHERE tc.id = $1;`; + const result = await db.query(q, [commentId]); + const [data] = result.rows; + return data; + } catch (error) { + log_error(error); + } + } + + @HandleExceptions() + public static async updateReaction(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const { id } = req.params; + const { reaction_type, task_id } = req.query; + + const exists = await this.checkIfAlreadyExists(id, req.user?.team_member_id, reaction_type as string); + + if (exists) { + const deleteQ = `DELETE FROM task_comment_reactions WHERE comment_id = $1 AND team_member_id = $2;`; + await db.query(deleteQ, [id, req.user?.team_member_id]); + } else { + const q = `INSERT INTO task_comment_reactions (comment_id, user_id, team_member_id) VALUES ($1, $2, $3);`; + await db.query(q, [id, req.user?.id, req.user?.team_member_id]); + + const getTaskCommentData = await TaskCommentsController.getTaskCommentData(id); + const commentMessage = `${getTaskCommentData.reactor_name} liked your comment on ${getTaskCommentData.task_name} (${getTaskCommentData.team_name})`; + + if (getTaskCommentData && getTaskCommentData.user_id !== req.user?.id) { + void NotificationsService.createNotification({ + userId: getTaskCommentData.user_id, + teamId: req.user?.team_id as string, + socketId: getTaskCommentData.socket_id, + message: commentMessage, + taskId: req.body.task_id, + projectId: getTaskCommentData.project_id + }); + } + } + + const result = await TaskCommentsController.getTaskComments(task_id as string); + return res.status(200).send(new ServerResponse(true, result.rows)); + } + + @HandleExceptions() + public static async createAttachment(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + req.body.user_id = req.user?.id; + req.body.team_id = req.user?.team_id; + const { attachments, task_id } = req.body; + + const q = `INSERT INTO task_comments (user_id, team_member_id, task_id) + VALUES ($1, (SELECT id + FROM team_members + WHERE user_id = $1 + AND team_id = $2::UUID), $3) + RETURNING id;`; + const result = await db.query(q, [req.user?.id, req.user?.team_id, task_id]); + const [data] = result.rows; + + const commentId = data.id; + + const url = `${S3_URL}/${getRootDir()}`; + + for (const attachment of attachments) { + if (req.user?.subscription_status === "free" && req.user?.owner_id) { + const limits = await getFreePlanSettings(); + + const usedStorage = await getUsedStorage(req.user?.owner_id); + if ((parseInt(usedStorage) + attachment.size) > megabytesToBytes(parseInt(limits.free_tier_storage))) { + return res.status(200).send(new ServerResponse(false, [], `Sorry, the free plan cannot exceed ${limits.free_tier_storage}MB of storage.`)); + } + } + + const q = ` + INSERT INTO task_comment_attachments (name, type, size, task_id, comment_id, team_id, project_id) + VALUES ($1, $2, $3, $4, $5, $6, $7) + RETURNING id, name, type, task_id, comment_id, created_at, + CONCAT($8::TEXT, '/', team_id, '/', project_id, '/', task_id, '/', comment_id, '/', id, '.', type) AS url; + `; + + const result = await db.query(q, [ + attachment.file_name, + attachment.size, + attachment.file_name.split(".").pop(), + task_id, + commentId, + req.user?.team_id, + attachment.project_id, + url + ]); + + const [data] = result.rows; + + const s3Url = await uploadBase64(attachment.file, getTaskAttachmentKey(req.user?.team_id as string, attachment.project_id, task_id, commentId, data.id, data.type)); + + if (!data?.id || !s3Url) + return res.status(200).send(new ServerResponse(false, null, "Attachment upload failed")); + } + + const assignees = await getAssignees(task_id); + + const commentMessage = `${req.user?.name} added a new attachment as a comment on ${commentId.task_name} (${commentId.team_name})`; + + for (const member of assignees || []) { + if (member.user_id && member.user_id === req.user?.id) continue; + + void NotificationsService.createNotification({ + userId: member.user_id, + teamId: req.user?.team_id as string, + socketId: member.socket_id, + message: commentMessage, + taskId: task_id, + projectId: commentId.project_id + }); + + if (member.email_notifications_enabled) + await this.sendMail({ + message: commentMessage, + receiverEmail: member.email, + receiverName: member.name, + content: req.body.content, + commentId: commentId.id, + projectId: commentId.project_id, + taskId: task_id, + teamName: commentId.team_name, + projectName: commentId.project_name, + taskName: commentId.task_name + }); + } + + return res.status(200).send(new ServerResponse(true, [])); + } + + + @HandleExceptions() + public static async download(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const q = `SELECT CONCAT($2::TEXT, '/', team_id, '/', project_id, '/', task_id, '/', comment_id, '/', id, '.', type) AS key + FROM task_comment_attachments + WHERE id = $1;`; + const result = await db.query(q, [req.query.id, getRootDir()]); + const [data] = result.rows; + + if (data?.key) { + const url = await createPresignedUrlWithClient(data.key, req.query.file as string); + return res.status(200).send(new ServerResponse(true, url)); + } + + return res.status(200).send(new ServerResponse(true, null)); + } } diff --git a/worklenz-backend/src/controllers/task-dependencies-controller.ts b/worklenz-backend/src/controllers/task-dependencies-controller.ts new file mode 100644 index 00000000..d3459c7e --- /dev/null +++ b/worklenz-backend/src/controllers/task-dependencies-controller.ts @@ -0,0 +1,52 @@ +import { IWorkLenzRequest } from "../interfaces/worklenz-request"; +import { IWorkLenzResponse } from "../interfaces/worklenz-response"; + +import db from "../config/db"; +import { ServerResponse } from "../models/server-response"; +import WorklenzControllerBase from "./worklenz-controller-base"; +import HandleExceptions from "../decorators/handle-exceptions"; + +export default class TaskdependenciesController extends WorklenzControllerBase { + @HandleExceptions({ + raisedExceptions: { + "DEPENDENCY_EXISTS": `Task dependency already exists.` + } + }) + public static async saveTaskDependency(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const {task_id, related_task_id, dependency_type } = req.body; + const q = `SELECT insert_task_dependency($1, $2, $3);`; + const result = await db.query(q, [task_id, related_task_id, dependency_type]); + return res.status(200).send(new ServerResponse(true, result.rows)); + } + + @HandleExceptions() + public static async getTaskDependencies(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const { id } = req.params; + + const q = `SELECT + td.id, + t2.name AS task_name, + td.dependency_type, + CONCAT(p.key, '-', t2.task_no) AS task_key + FROM + task_dependencies td + LEFT JOIN + tasks t ON td.task_id = t.id + LEFT JOIN + tasks t2 ON td.related_task_id = t2.id + LEFT JOIN + projects p ON t.project_id = p.id + WHERE + td.task_id = $1;`; + const result = await db.query(q, [id]); + + return res.status(200).send(new ServerResponse(true, result.rows)); + } + + public static async deleteById(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const {id} = req.params; + const q = `DELETE FROM task_dependencies WHERE id = $1;`; + const result = await db.query(q, [id]); + return res.status(200).send(new ServerResponse(true, result.rows)); + } +} \ No newline at end of file diff --git a/worklenz-backend/src/controllers/task-list-columns-controller.ts b/worklenz-backend/src/controllers/task-list-columns-controller.ts index 0903abf7..c8340b5b 100644 --- a/worklenz-backend/src/controllers/task-list-columns-controller.ts +++ b/worklenz-backend/src/controllers/task-list-columns-controller.ts @@ -32,8 +32,9 @@ export default class TaskListColumnsController extends WorklenzControllerBase { const q = `UPDATE project_task_list_cols SET pinned = $3 WHERE project_id = $1 - AND key = $2;`; + AND key = $2 RETURNING *;`; const result = await db.query(q, [req.params.id, req.body.key, !!req.body.pinned]); - return res.status(200).send(new ServerResponse(true, result.rows)); + const [data] = result.rows; + return res.status(200).send(new ServerResponse(true, data)); } } diff --git a/worklenz-backend/src/controllers/task-priorities-controller.ts b/worklenz-backend/src/controllers/task-priorities-controller.ts index 78d4b666..7e32821f 100644 --- a/worklenz-backend/src/controllers/task-priorities-controller.ts +++ b/worklenz-backend/src/controllers/task-priorities-controller.ts @@ -5,15 +5,17 @@ import db from "../config/db"; import {ServerResponse} from "../models/server-response"; import WorklenzControllerBase from "./worklenz-controller-base"; import HandleExceptions from "../decorators/handle-exceptions"; -import {PriorityColorCodes} from "../shared/constants"; +import {PriorityColorCodes, PriorityColorCodesDark} from "../shared/constants"; export default class TaskPrioritiesController extends WorklenzControllerBase { @HandleExceptions() public static async get(_req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { const q = `SELECT id, name, value From task_priorities ORDER BY value;`; const result = await db.query(q, []); - for (const item of result.rows) + for (const item of result.rows) { item.color_code = PriorityColorCodes[item.value] || PriorityColorCodes["0"]; + item.color_code_dark = PriorityColorCodesDark[item.value] || PriorityColorCodesDark["0"]; + } return res.status(200).send(new ServerResponse(true, result.rows)); } diff --git a/worklenz-backend/src/controllers/task-recurring-controller.ts b/worklenz-backend/src/controllers/task-recurring-controller.ts new file mode 100644 index 00000000..6dd2dfc1 --- /dev/null +++ b/worklenz-backend/src/controllers/task-recurring-controller.ts @@ -0,0 +1,108 @@ +import db from "../config/db"; + +import WorklenzControllerBase from "./worklenz-controller-base"; +import HandleExceptions from "../decorators/handle-exceptions"; +import { IWorkLenzRequest } from "../interfaces/worklenz-request"; +import { IWorkLenzResponse } from "../interfaces/worklenz-response"; +import { ServerResponse } from "../models/server-response"; +import { calculateNextEndDate, log_error } from "../shared/utils"; + +export default class TaskRecurringController extends WorklenzControllerBase { + @HandleExceptions() + public static async getById(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const { id } = req.params; + const q = `SELECT id, + schedule_type, + days_of_week, + date_of_month, + day_of_month, + week_of_month, + interval_days, + interval_weeks, + interval_months, + created_at + FROM task_recurring_schedules WHERE id = $1;`; + const result = await db.query(q, [id]); + const [data] = result.rows; + + return res.status(200).send(new ServerResponse(true, data)); + } + + private static async insertTaskRecurringTemplate(taskId: string, scheduleId: string) { + const q = `SELECT create_recurring_task_template($1, $2);`; + await db.query(q, [taskId, scheduleId]); + } + + @HandleExceptions() + public static async createTaskSchedule(taskId: string) { + const q = `INSERT INTO task_recurring_schedules (schedule_type) VALUES ('daily') RETURNING id, schedule_type;`; + const result = await db.query(q, []); + const [data] = result.rows; + + const updateQ = `UPDATE tasks SET schedule_id = $1 WHERE id = $2;`; + await db.query(updateQ, [data.id, taskId]); + + await TaskRecurringController.insertTaskRecurringTemplate(taskId, data.id); + + return data; + } + + @HandleExceptions() + public static async removeTaskSchedule(scheduleId: string) { + const deleteQ = `DELETE FROM task_recurring_schedules WHERE id = $1;`; + await db.query(deleteQ, [scheduleId]); + } + + @HandleExceptions() + public static async updateSchedule(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const { id } = req.params; + const { schedule_type, days_of_week, day_of_month, week_of_month, interval_days, interval_weeks, interval_months, date_of_month } = req.body; + + const deleteQ = `UPDATE task_recurring_schedules + SET schedule_type = $1, + days_of_week = $2, + date_of_month = $3, + day_of_month = $4, + week_of_month = $5, + interval_days = $6, + interval_weeks = $7, + interval_months = $8 + WHERE id = $9;`; + await db.query(deleteQ, [schedule_type, days_of_week, date_of_month, day_of_month, week_of_month, interval_days, interval_weeks, interval_months, id]); + return res.status(200).send(new ServerResponse(true, null)); + } + + // Function to create the next task in the recurring schedule + private static async createNextRecurringTask(scheduleId: string, lastTask: any, taskTemplate: any) { + try { + const q = "SELECT * FROM task_recurring_schedules WHERE id = $1"; + const { rows: schedules } = await db.query(q, [scheduleId]); + + if (schedules.length === 0) { + log_error("No schedule found"); + return; + } + + const [schedule] = schedules; + + // Define the next start date based on the schedule + const nextStartDate = calculateNextEndDate(schedule, lastTask.start_date); + + const result = await db.query( + `INSERT INTO tasks (name, start_date, end_date, priority_id, project_id, reporter_id, description, total_minutes, status_id, schedule_id) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) RETURNING id;`, + [ + taskTemplate.name, nextStartDate, null, taskTemplate.priority_id, + lastTask.project_id, lastTask.reporter_id, taskTemplate.description, + 0, taskTemplate.status_id, scheduleId + ] + ); + const [data] = result.rows; + + log_error(`Next task created with id: ${data.id}`); + + } catch (error) { + log_error("Error creating next recurring task:", error); + } + } +} \ No newline at end of file diff --git a/worklenz-backend/src/controllers/task-statuses-controller.ts b/worklenz-backend/src/controllers/task-statuses-controller.ts index 8ef725ff..dbefe0dd 100644 --- a/worklenz-backend/src/controllers/task-statuses-controller.ts +++ b/worklenz-backend/src/controllers/task-statuses-controller.ts @@ -54,7 +54,7 @@ export default class TaskStatusesController extends WorklenzControllerBase { @HandleExceptions() public static async getCategories(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { - const q = `SELECT id, name, color_code, description + const q = `SELECT id, name, color_code, color_code_dark, description FROM sys_task_status_categories ORDER BY index;`; const result = await db.query(q, []); @@ -73,7 +73,7 @@ export default class TaskStatusesController extends WorklenzControllerBase { @HandleExceptions() public static async getById(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { const q = ` - SELECT task_statuses.id, task_statuses.name, stsc.color_code + SELECT task_statuses.id, task_statuses.name, stsc.color_code, stsc.color_code_dark FROM task_statuses INNER JOIN sys_task_status_categories stsc ON task_statuses.category_id = stsc.id WHERE task_statuses.id = $1 @@ -113,7 +113,7 @@ export default class TaskStatusesController extends WorklenzControllerBase { category_id = COALESCE($4, (SELECT id FROM sys_task_status_categories WHERE is_todo IS TRUE)) WHERE id = $1 AND project_id = $3 - RETURNING (SELECT color_code FROM sys_task_status_categories WHERE id = task_statuses.category_id); + RETURNING (SELECT color_code FROM sys_task_status_categories WHERE id = task_statuses.category_id), (SELECT color_code_dark FROM sys_task_status_categories WHERE id = task_statuses.category_id); `; const result = await db.query(q, [req.params.id, req.body.name, req.body.project_id, req.body.category_id]); const [data] = result.rows; diff --git a/worklenz-backend/src/controllers/task-work-log-controller.ts b/worklenz-backend/src/controllers/task-work-log-controller.ts index ed47b4b4..13f69737 100644 --- a/worklenz-backend/src/controllers/task-work-log-controller.ts +++ b/worklenz-backend/src/controllers/task-work-log-controller.ts @@ -234,4 +234,25 @@ export default class TaskWorklogController extends WorklenzControllerBase { res.end(); }); } + + @HandleExceptions() + public static async getAllRunningTimers(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const q = `SELECT + tt.task_id, + tt.start_time, + t1.name AS task_name, + pr.id AS project_id, + pr.name AS project_name, + t1.parent_task_id, + t2.name AS parent_task_name + FROM task_timers tt + LEFT JOIN public.tasks t1 ON tt.task_id = t1.id + LEFT JOIN public.tasks t2 ON t1.parent_task_id = t2.id -- Optimized join for parent task name + INNER JOIN projects pr ON t1.project_id = pr.id -- INNER JOIN ensures project-team match + WHERE tt.user_id = $1 + AND pr.team_id = $2;`; + const params = [req.user?.id, req.user?.team_id]; + const result = await db.query(q, params); + return res.status(200).send(new ServerResponse(true, result.rows)); + } } diff --git a/worklenz-backend/src/controllers/tasks-controller-base.ts b/worklenz-backend/src/controllers/tasks-controller-base.ts index ae336b63..293ae3d8 100644 --- a/worklenz-backend/src/controllers/tasks-controller-base.ts +++ b/worklenz-backend/src/controllers/tasks-controller-base.ts @@ -73,8 +73,8 @@ export default class TasksControllerBase extends WorklenzControllerBase { if (task.timer_start_time) task.timer_start_time = moment(task.timer_start_time).valueOf(); - const totalCompleted = +task.completed_sub_tasks + +task.parent_task_completed; - const totalTasks = +task.sub_tasks_count + 1; // +1 for parent + const totalCompleted = (+task.completed_sub_tasks + +task.parent_task_completed) || 0; + const totalTasks = +task.sub_tasks_count || 0; // if needed add +1 for parent task.complete_ratio = TasksControllerBase.calculateTaskCompleteRatio(totalCompleted, totalTasks); task.completed_count = totalCompleted; task.total_tasks_count = totalTasks; diff --git a/worklenz-backend/src/controllers/tasks-controller-v2.ts b/worklenz-backend/src/controllers/tasks-controller-v2.ts index 2b4ad020..3e1290f1 100644 --- a/worklenz-backend/src/controllers/tasks-controller-v2.ts +++ b/worklenz-backend/src/controllers/tasks-controller-v2.ts @@ -1,18 +1,19 @@ -import {ParsedQs} from "qs"; +import { ParsedQs } from "qs"; import db from "../config/db"; import HandleExceptions from "../decorators/handle-exceptions"; -import {IWorkLenzRequest} from "../interfaces/worklenz-request"; -import {IWorkLenzResponse} from "../interfaces/worklenz-response"; -import {ServerResponse} from "../models/server-response"; -import {TASK_PRIORITY_COLOR_ALPHA, TASK_STATUS_COLOR_ALPHA, UNMAPPED} from "../shared/constants"; -import {getColor} from "../shared/utils"; -import TasksControllerBase, {GroupBy, ITaskGroup} from "./tasks-controller-base"; +import { IWorkLenzRequest } from "../interfaces/worklenz-request"; +import { IWorkLenzResponse } from "../interfaces/worklenz-response"; +import { ServerResponse } from "../models/server-response"; +import { TASK_PRIORITY_COLOR_ALPHA, TASK_STATUS_COLOR_ALPHA, UNMAPPED } from "../shared/constants"; +import { getColor, log_error } from "../shared/utils"; +import TasksControllerBase, { GroupBy, ITaskGroup } from "./tasks-controller-base"; export class TaskListGroup implements ITaskGroup { name: string; category_id: string | null; color_code: string; + color_code_dark: string; start_date?: string; end_date?: string; todo_progress: number; @@ -26,6 +27,7 @@ export class TaskListGroup implements ITaskGroup { this.start_date = group.start_date || null; this.end_date = group.end_date || null; this.color_code = group.color_code + TASK_STATUS_COLOR_ALPHA; + this.color_code_dark = group.color_code_dark; this.todo_progress = 0; this.doing_progress = 0; this.done_progress = 0; @@ -104,7 +106,7 @@ export default class TasksControllerV2 extends TasksControllerBase { private static getQuery(userId: string, options: ParsedQs) { const searchField = options.search ? "t.name" : "sort_order"; - const {searchQuery, sortField} = TasksControllerV2.toPaginationOptions(options, searchField); + const { searchQuery, sortField } = TasksControllerV2.toPaginationOptions(options, searchField); const isSubTasks = !!options.parent_task; @@ -124,6 +126,33 @@ export default class TasksControllerV2 extends TasksControllerBase { const filterByAssignee = TasksControllerV2.getFilterByAssignee(options.filterBy as string); // Returns statuses of each task as a json array if filterBy === "member" const statusesQuery = TasksControllerV2.getStatusesQuery(options.filterBy as string); + + // Custom columns data query + const customColumnsQuery = options.customColumns + ? `, (SELECT COALESCE( + jsonb_object_agg( + custom_cols.key, + custom_cols.value + ), + '{}'::JSONB + ) + FROM ( + SELECT + cc.key, + CASE + WHEN ccv.text_value IS NOT NULL THEN to_jsonb(ccv.text_value) + WHEN ccv.number_value IS NOT NULL THEN to_jsonb(ccv.number_value) + WHEN ccv.boolean_value IS NOT NULL THEN to_jsonb(ccv.boolean_value) + WHEN ccv.date_value IS NOT NULL THEN to_jsonb(ccv.date_value) + WHEN ccv.json_value IS NOT NULL THEN ccv.json_value + ELSE NULL::JSONB + END AS value + FROM cc_column_values ccv + JOIN cc_custom_columns cc ON ccv.column_id = cc.id + WHERE ccv.task_id = t.id + ) AS custom_cols + WHERE custom_cols.value IS NOT NULL) AS custom_column_values` + : ""; const archivedFilter = options.archived === "true" ? "archived IS TRUE" : "archived IS FALSE"; @@ -173,7 +202,7 @@ export default class TasksControllerV2 extends TasksControllerBase { WHERE id = (SELECT phase_id FROM task_phase WHERE task_id = t.id)) AS phase_color_code, (EXISTS(SELECT 1 FROM task_subscribers WHERE task_id = t.id)) AS has_subscribers, - + (EXISTS(SELECT 1 FROM task_dependencies td WHERE td.task_id = t.id)) AS has_dependencies, (SELECT start_time FROM task_timers WHERE task_id = t.id @@ -183,6 +212,10 @@ export default class TasksControllerV2 extends TasksControllerBase { FROM sys_task_status_categories WHERE id = (SELECT category_id FROM task_statuses WHERE id = t.status_id)) AS status_color, + (SELECT color_code_dark + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = t.status_id)) AS status_color_dark, + (SELECT COALESCE(ROW_TO_JSON(r), '{}'::JSON) FROM (SELECT is_done, is_doing, is_todo FROM sys_task_status_categories @@ -209,7 +242,7 @@ export default class TasksControllerV2 extends TasksControllerBase { (SELECT color_code FROM team_labels WHERE id = task_labels.label_id) FROM task_labels WHERE task_id = t.id) r) AS labels, - + (SELECT is_completed(status_id, project_id)) AS is_complete, (SELECT name FROM users WHERE id = t.reporter_id) AS reporter, (SELECT id FROM task_priorities WHERE id = t.priority_id) AS priority, (SELECT value FROM task_priorities WHERE id = t.priority_id) AS priority_value, @@ -219,7 +252,9 @@ export default class TasksControllerV2 extends TasksControllerBase { updated_at, completed_at, start_date, - END_DATE ${statusesQuery} + billable, + schedule_id, + END_DATE ${customColumnsQuery} ${statusesQuery} FROM tasks t WHERE ${filters} ${searchQuery} ORDER BY ${sortFields} @@ -235,6 +270,7 @@ export default class TasksControllerV2 extends TasksControllerBase { SELECT id, name, (SELECT color_code FROM sys_task_status_categories WHERE id = task_statuses.category_id), + (SELECT color_code_dark FROM sys_task_status_categories WHERE id = task_statuses.category_id), category_id FROM task_statuses WHERE project_id = $1 @@ -243,7 +279,7 @@ export default class TasksControllerV2 extends TasksControllerBase { params = [projectId]; break; case GroupBy.PRIORITY: - q = `SELECT id, name, color_code + q = `SELECT id, name, color_code, color_code_dark FROM task_priorities ORDER BY value DESC;`; break; @@ -261,7 +297,7 @@ export default class TasksControllerV2 extends TasksControllerBase { break; case GroupBy.PHASE: q = ` - SELECT id, name, color_code, start_date, end_date, sort_index + SELECT id, name, color_code, color_code AS color_code_dark, start_date, end_date, sort_index FROM project_phases WHERE project_id = $1 ORDER BY sort_index DESC; @@ -281,6 +317,9 @@ export default class TasksControllerV2 extends TasksControllerBase { public static async getList(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { const isSubTasks = !!req.query.parent_task; const groupBy = (req.query.group || GroupBy.STATUS) as string; + + // Add customColumns flag to query params + req.query.customColumns = "true"; const q = TasksControllerV2.getQuery(req.user?.id as string, req.query); const params = isSubTasks ? [req.params.id || null, req.query.parent_task] : [req.params.id || null]; @@ -356,6 +395,10 @@ export default class TasksControllerV2 extends TasksControllerBase { @HandleExceptions() public static async getTasksOnly(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { const isSubTasks = !!req.query.parent_task; + + // Add customColumns flag to query params + req.query.customColumns = "true"; + const q = TasksControllerV2.getQuery(req.user?.id as string, req.query); const params = isSubTasks ? [req.params.id || null, req.query.parent_task] : [req.params.id || null]; const result = await db.query(q, params); @@ -393,7 +436,7 @@ export default class TasksControllerV2 extends TasksControllerBase { @HandleExceptions() public static async getNewKanbanTask(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { - const {id} = req.params; + const { id } = req.params; const result = await db.query("SELECT get_single_task($1) AS task;", [id]); const [data] = result.rows; const task = TasksControllerV2.updateTaskViewModel(data.task); @@ -474,9 +517,211 @@ export default class TasksControllerV2 extends TasksControllerBase { } + public static async getTasksByName(searchString: string, projectId: string, taskId: string) { + const q = `SELECT id AS value , + name AS label, + CONCAT((SELECT key FROM projects WHERE id = t.project_id), '-', task_no) AS task_key + FROM tasks t + WHERE t.name ILIKE '%${searchString}%' + AND t.project_id = $1 AND t.id != $2 + LIMIT 15;`; + const result = await db.query(q, [projectId, taskId]); + + return result.rows; + } + @HandleExceptions() public static async getSubscribers(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { const subscribers = await this.getTaskSubscribers(req.params.id); return res.status(200).send(new ServerResponse(true, subscribers)); } + + @HandleExceptions() + public static async searchTasks(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const { projectId, taskId, searchQuery } = req.query; + const tasks = await this.getTasksByName(searchQuery as string, projectId as string, taskId as string); + return res.status(200).send(new ServerResponse(true, tasks)); + } + + @HandleExceptions() + public static async getTaskDependencyStatus(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const { statusId, taskId } = req.query; + const canContinue = await TasksControllerV2.checkForCompletedDependencies(taskId as string, statusId as string); + return res.status(200).send(new ServerResponse(true, { can_continue: canContinue })); + } + + @HandleExceptions() + public static async checkForCompletedDependencies(taskId: string, nextStatusId: string): Promise { + const q = `SELECT + CASE + WHEN EXISTS ( + -- Check if the status id is not in the "done" category + SELECT 1 + FROM task_statuses ts + WHERE ts.id = $2 + AND ts.project_id = (SELECT project_id FROM tasks WHERE id = $1) + AND ts.category_id IN ( + SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE + ) + ) THEN TRUE -- If status is not in the "done" category, continue immediately (TRUE) + + WHEN EXISTS ( + -- Check if any dependent tasks are not completed + SELECT 1 + FROM task_dependencies td + LEFT JOIN public.tasks t ON t.id = td.related_task_id + WHERE td.task_id = $1 + AND t.status_id NOT IN ( + SELECT id + FROM task_statuses ts + WHERE t.project_id = ts.project_id + AND ts.category_id IN ( + SELECT id FROM sys_task_status_categories WHERE is_done IS TRUE + ) + ) + ) THEN FALSE -- If there are incomplete dependent tasks, do not continue (FALSE) + + ELSE TRUE -- Continue if no other conditions block the process + END AS can_continue;`; + const result = await db.query(q, [taskId, nextStatusId]); + const [data] = result.rows; + + return data.can_continue; + } + + public static async getTaskStatusColor(status_id: string) { + try { + const q = `SELECT color_code, color_code_dark + FROM sys_task_status_categories + WHERE id = (SELECT category_id FROM task_statuses WHERE id = $1)`; + const result = await db.query(q, [status_id]); + const [data] = result.rows; + return data; + } catch (e) { + log_error(e); + } + } + + @HandleExceptions() + public static async assignLabelsToTask(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const { id } = req.params; + const { labels }: { labels: string[] } = req.body; + + labels.forEach(async (label: string) => { + const q = `SELECT add_or_remove_task_label($1, $2) AS labels;`; + await db.query(q, [id, label]); + }); + return res.status(200).send(new ServerResponse(true, null, "Labels assigned successfully")); + } + + /** + * Updates a custom column value for a task + * @param req The request object + * @param res The response object + */ + @HandleExceptions() + public static async updateCustomColumnValue( + req: IWorkLenzRequest, + res: IWorkLenzResponse + ): Promise { + const { taskId } = req.params; + const { column_key, value, project_id } = req.body; + + if (!taskId || !column_key || value === undefined || !project_id) { + return res.status(400).send(new ServerResponse(false, "Missing required parameters")); + } + + // Get column information + const columnQuery = ` + SELECT id, field_type + FROM cc_custom_columns + WHERE project_id = $1 AND key = $2 + `; + const columnResult = await db.query(columnQuery, [project_id, column_key]); + + if (columnResult.rowCount === 0) { + return res.status(404).send(new ServerResponse(false, "Custom column not found")); + } + + const column = columnResult.rows[0]; + const columnId = column.id; + const fieldType = column.field_type; + + // Determine which value field to use based on the field_type + let textValue = null; + let numberValue = null; + let dateValue = null; + let booleanValue = null; + let jsonValue = null; + + switch (fieldType) { + case "number": + numberValue = parseFloat(String(value)); + break; + case "date": + dateValue = new Date(String(value)); + break; + case "checkbox": + booleanValue = Boolean(value); + break; + case "people": + jsonValue = JSON.stringify(Array.isArray(value) ? value : [value]); + break; + default: + textValue = String(value); + } + + // Check if a value already exists + const existingValueQuery = ` + SELECT id + FROM cc_column_values + WHERE task_id = $1 AND column_id = $2 + `; + const existingValueResult = await db.query(existingValueQuery, [taskId, columnId]); + + if (existingValueResult.rowCount && existingValueResult.rowCount > 0) { + // Update existing value + const updateQuery = ` + UPDATE cc_column_values + SET text_value = $1, + number_value = $2, + date_value = $3, + boolean_value = $4, + json_value = $5, + updated_at = NOW() + WHERE task_id = $6 AND column_id = $7 + `; + await db.query(updateQuery, [ + textValue, + numberValue, + dateValue, + booleanValue, + jsonValue, + taskId, + columnId + ]); + } else { + // Insert new value + const insertQuery = ` + INSERT INTO cc_column_values + (task_id, column_id, text_value, number_value, date_value, boolean_value, json_value, created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW()) + `; + await db.query(insertQuery, [ + taskId, + columnId, + textValue, + numberValue, + dateValue, + booleanValue, + jsonValue + ]); + } + + return res.status(200).send(new ServerResponse(true, { + task_id: taskId, + column_key, + value + })); + } } diff --git a/worklenz-backend/src/controllers/tasks-controller.ts b/worklenz-backend/src/controllers/tasks-controller.ts index fd8c6930..37ff8f84 100644 --- a/worklenz-backend/src/controllers/tasks-controller.ts +++ b/worklenz-backend/src/controllers/tasks-controller.ts @@ -6,9 +6,9 @@ import { IWorkLenzResponse } from "../interfaces/worklenz-response"; import db from "../config/db"; import { ServerResponse } from "../models/server-response"; -import { TASK_STATUS_COLOR_ALPHA } from "../shared/constants"; +import { S3_URL, TASK_STATUS_COLOR_ALPHA } from "../shared/constants"; import { getDates, getMinMaxOfTaskDates, getMonthRange, getWeekRange } from "../shared/tasks-controller-utils"; -import { getColor, getRandomColorCode, log_error, toMinutes } from "../shared/utils"; +import { getColor, getRandomColorCode, humanFileSize, log_error, toMinutes } from "../shared/utils"; import WorklenzControllerBase from "./worklenz-controller-base"; import HandleExceptions from "../decorators/handle-exceptions"; import { NotificationsService } from "../services/notifications/notifications.service"; @@ -18,9 +18,9 @@ import TasksControllerV2 from "./tasks-controller-v2"; import { IO } from "../shared/io"; import { SocketEvents } from "../socket.io/events"; import TasksControllerBase from "./tasks-controller-base"; -import { insertToActivityLogs, logStatusChange } from "../services/activity-logs/activity-logs.service"; -import { forEach } from "lodash"; +import { insertToActivityLogs } from "../services/activity-logs/activity-logs.service"; import { IActivityLog } from "../services/activity-logs/interfaces"; +import { getKey, getRootDir, uploadBase64 } from "../shared/s3"; export default class TasksController extends TasksControllerBase { private static notifyProjectUpdates(socketId: string, projectId: string) { @@ -29,14 +29,54 @@ export default class TasksController extends TasksControllerBase { .emit(SocketEvents.PROJECT_UPDATES_AVAILABLE.toString()); } + public static async uploadAttachment(attachments: any, teamId: string, userId: string) { + try { + const promises = attachments.map(async (attachment: any) => { + const { file, file_name, project_id, size } = attachment; + const type = file_name.split(".").pop(); + + const q = ` + INSERT INTO task_attachments (name, task_id, team_id, project_id, uploaded_by, size, type) + VALUES ($1, $2, $3, $4, $5, $6, $7) + RETURNING id, name, size, type, created_at, CONCAT($8::TEXT, '/', team_id, '/', project_id, '/', id, '.', type) AS url; + `; + + const result = await db.query(q, [ + file_name, + null, + teamId, + project_id, + userId, + size, + type, + `${S3_URL}/${getRootDir()}` + ]); + + const [data] = result.rows; + await uploadBase64(file, getKey(teamId, project_id, data.id, data.type)); + return data.id; + }); + + const attachmentIds = await Promise.all(promises); + return attachmentIds; + } catch (error) { + log_error(error); + } + } + @HandleExceptions() public static async create(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + const userId = req.user?.id as string; + const teamId = req.user?.team_id as string; + + if (req.body.attachments_raw) { + req.body.attachments = await this.uploadAttachment(req.body.attachments_raw, teamId, userId); + } + const q = `SELECT create_task($1) AS task;`; const result = await db.query(q, [JSON.stringify(req.body)]); const [data] = result.rows; - const userId = req.user?.id as string; - for (const member of data?.task.assignees || []) { NotificationsService.createTaskUpdate( "ASSIGN", @@ -468,7 +508,7 @@ export default class TasksController extends TasksControllerBase { TasksController.notifyProjectUpdates(req.user?.socket_id as string, req.query.project as string); - return res.status(200).send(new ServerResponse(true, data)); + return res.status(200).send(new ServerResponse(true, { failed_tasks: data.task })); } @HandleExceptions() diff --git a/worklenz-backend/src/controllers/team-members-controller.ts b/worklenz-backend/src/controllers/team-members-controller.ts index 12c35bfd..33bde00d 100644 --- a/worklenz-backend/src/controllers/team-members-controller.ts +++ b/worklenz-backend/src/controllers/team-members-controller.ts @@ -13,7 +13,9 @@ import { SocketEvents } from "../socket.io/events"; import WorklenzControllerBase from "./worklenz-controller-base"; import HandleExceptions from "../decorators/handle-exceptions"; import { formatDuration, getColor } from "../shared/utils"; -import { TEAM_MEMBER_TREE_MAP_COLOR_ALPHA } from "../shared/constants"; +import { statusExclude, TEAM_MEMBER_TREE_MAP_COLOR_ALPHA } from "../shared/constants"; +import { checkTeamSubscriptionStatus } from "../shared/paddle-utils"; +import { updateUsers } from "../shared/paddle-requests"; import { NotificationsService } from "../services/notifications/notifications.service"; export default class TeamMembersController extends WorklenzControllerBase { @@ -80,6 +82,98 @@ export default class TeamMembersController extends WorklenzControllerBase { return res.status(200).send(new ServerResponse(false, "Required fields are missing.")); } + /** + * Checks the subscription status of the team. + * @type {Object} subscriptionData - Object containing subscription information + */ + const subscriptionData = await checkTeamSubscriptionStatus(req.user?.team_id); + + let incrementBy = 0; + + // Handle self-hosted subscriptions differently + if (subscriptionData.subscription_type === 'SELF_HOSTED') { + // Check if users exist and add them if they don't + await Promise.all(req.body.emails.map(async (email: string) => { + const trimmedEmail = email.trim(); + const userExists = await this.checkIfUserAlreadyExists(req.user?.owner_id as string, trimmedEmail); + if (!userExists) { + incrementBy = incrementBy + 1; + } + })); + + // Create or invite new members + const newMembers = await this.createOrInviteMembers(req.body, req.user); + return res.status(200).send(new ServerResponse(true, newMembers, `Your teammates will get an email that gives them access to your team.`).withTitle("Invitations sent")); + } + + /** + * Iterates through each email in the request body and checks if the user already exists. + * If the user doesn't exist, increments the counter. + * @param {string} email - Email address to check + */ + await Promise.all(req.body.emails.map(async (email: string) => { + const trimmedEmail = email.trim(); + + const userExists = await this.checkIfUserAlreadyExists(req.user?.owner_id as string, trimmedEmail); + const isUserActive = await this.checkIfUserActiveInOtherTeams(req.user?.owner_id as string, trimmedEmail); + + if (!userExists || !isUserActive) { + incrementBy = incrementBy + 1; + } + })); + + /** + * Checks various conditions to determine if the maximum number of lifetime users is exceeded. + * Sends a response if the limit is reached. + */ + if ( + incrementBy > 0 + && subscriptionData.is_ltd + && subscriptionData.current_count + && ((parseInt(subscriptionData.current_count) + req.body.emails.length) > parseInt(subscriptionData.ltd_users))) { + return res.status(200).send(new ServerResponse(false, null, "Cannot exceed the maximum number of life time users.")); + } + + if ( + subscriptionData.is_ltd + && subscriptionData.current_count + && ((parseInt(subscriptionData.current_count) + incrementBy) > parseInt(subscriptionData.ltd_users))) { + return res.status(200).send(new ServerResponse(false, null, "Cannot exceed the maximum number of life time users.")); + } + + /** + * Checks subscription details and updates the user count if applicable. + * Sends a response if there is an issue with the subscription. + */ + // if (!subscriptionData.is_credit && !subscriptionData.is_custom && subscriptionData.subscription_status === "active") { + // const response = await updateUsers(subscriptionData.subscription_id, (subscriptionData.quantity + incrementBy)); + + // if (!response.body.subscription_id) { + // return res.status(200).send(new ServerResponse(false, null, response.message || "Please check your subscription.")); + // } + // } + + if (!subscriptionData.is_credit && !subscriptionData.is_custom && subscriptionData.subscription_status === "active") { + const updatedCount = parseInt(subscriptionData.current_count) + incrementBy; + const requiredSeats = updatedCount - subscriptionData.quantity; + if (updatedCount > subscriptionData.quantity) { + const obj = { + seats_enough: false, + required_count: requiredSeats, + current_seat_amount: subscriptionData.quantity + }; + return res.status(200).send(new ServerResponse(false, obj, null)); + } + } + + /** + * Checks if the subscription status is in the exclusion list. + * Sends a response if the status is excluded. + */ + if (statusExclude.includes(subscriptionData.subscription_status)) { + return res.status(200).send(new ServerResponse(false, null, "Unable to add user! Please check your subscription status.")); + } + /** * Creates or invites new members based on the request body and user information. * Sends a response with the result. @@ -93,12 +187,24 @@ export default class TeamMembersController extends WorklenzControllerBase { req.query.field = ["is_owner", "active", "u.name", "u.email"]; req.query.order = "descend"; + // Helper function to check for encoded components + function containsEncodedComponents(x: string) { + return decodeURI(x) !== decodeURIComponent(x); + } + + // Decode search parameter if it contains encoded components + if (req.query.search && typeof req.query.search === 'string') { + if (containsEncodedComponents(req.query.search)) { + req.query.search = decodeURIComponent(req.query.search); + } + } + const { - searchQuery, - sortField, - sortOrder, - size, - offset + searchQuery, + sortField, + sortOrder, + size, + offset } = this.toPaginationOptions(req.query, ["u.name", "u.email"], true); const paginate = req.query.all === "false" ? `LIMIT ${size} OFFSET ${offset}` : ""; @@ -126,7 +232,7 @@ export default class TeamMembersController extends WorklenzControllerBase { ELSE FALSE END) AS is_owner, (SELECT email FROM team_member_info_view - WHERE team_member_info_view.team_member_id = team_members.id), + WHERE team_member_info_view.team_member_id = team_members.id) AS email, EXISTS(SELECT email FROM email_invitations WHERE team_member_id = team_members.id @@ -277,12 +383,33 @@ export default class TeamMembersController extends WorklenzControllerBase { if (!id || !req.user?.team_id) return res.status(200).send(new ServerResponse(false, "Required fields are missing.")); + // check subscription status + const subscriptionData = await checkTeamSubscriptionStatus(req.user?.team_id); + if (statusExclude.includes(subscriptionData.subscription_status)) { + return res.status(200).send(new ServerResponse(false, "Please check your subscription status.")); + } + const q = `SELECT remove_team_member($1, $2, $3) AS member;`; const result = await db.query(q, [id, req.user?.id, req.user?.team_id]); const [data] = result.rows; const message = `You have been removed from ${req.user?.team_name} by ${req.user?.name}`; + // if (subscriptionData.status === "trialing") break; + // if (!subscriptionData.is_credit && !subscriptionData.is_custom) { + // if (subscriptionData.subscription_status === "active" && subscriptionData.quantity > 0) { + // const obj = await getActiveTeamMemberCount(req.user?.owner_id ?? ""); + // // const activeObj = await getActiveTeamMemberCount(req.user?.owner_id ?? ""); + + // const userActiveInOtherTeams = await this.checkIfUserActiveInOtherTeams(req.user?.owner_id as string, req.query?.email as string); + + // if (!userActiveInOtherTeams) { + // const response = await updateUsers(subscriptionData.subscription_id, obj.user_count); + // if (!response.body.subscription_id) return res.status(200).send(new ServerResponse(false, response.message || "Please check your subscription.")); + // } + // } + // } + NotificationsService.sendNotification({ receiver_socket_id: data.socket_id, message, @@ -871,20 +998,68 @@ export default class TeamMembersController extends WorklenzControllerBase { public static async toggleMemberActiveStatus(req: IWorkLenzRequest, res: IWorkLenzResponse) { if (!req.user?.team_id) return res.status(200).send(new ServerResponse(false, "Required fields are missing.")); - const q1 = `SELECT active FROM team_members WHERE id = $1;`; - const result1 = await db.query(q1, [req.params?.id]); - const [status] = result1.rows; - - if (status.active) { - const updateQ1 = `UPDATE users - SET active_team = (SELECT id FROM teams WHERE user_id = users.id ORDER BY created_at DESC LIMIT 1) - WHERE id = (SELECT user_id FROM team_members WHERE id = $1 AND active IS TRUE LIMIT 1);`; - await db.query(updateQ1, [req.params?.id]); + // check subscription status + const subscriptionData = await checkTeamSubscriptionStatus(req.user?.team_id); + if (statusExclude.includes(subscriptionData.subscription_status)) { + return res.status(200).send(new ServerResponse(false, "Please check your subscription status.")); } - const q = `UPDATE team_members SET active = NOT active WHERE id = $1 RETURNING active;`; - const result = await db.query(q, [req.params?.id]); - const [data] = result.rows; + let data: any; + + if (req.query.active === "true") { + const q1 = `SELECT active FROM team_members WHERE id = $1;`; + const result1 = await db.query(q1, [req.params?.id]); + const [status] = result1.rows; + + if (status.active) { + const updateQ1 = `UPDATE users + SET active_team = (SELECT id FROM teams WHERE user_id = users.id ORDER BY created_at DESC LIMIT 1) + WHERE id = (SELECT user_id FROM team_members WHERE id = $1 AND active IS TRUE LIMIT 1);`; + await db.query(updateQ1, [req.params?.id]); + } + + const q = `UPDATE team_members SET active = NOT active WHERE id = $1 RETURNING active;`; + const result = await db.query(q, [req.params?.id]); + data = result.rows[0]; + + // const userExists = await this.checkIfUserActiveInOtherTeams(req.user?.owner_id as string, req.query?.email as string); + + // if (subscriptionData.status === "trialing") break; + // if (!userExists && !subscriptionData.is_credit && !subscriptionData.is_custom) { + // if (subscriptionData.subscription_status === "active" && subscriptionData.quantity > 0) { + // const operator = req.query.active === "true" ? - 1 : + 1; + // const response = await updateUsers(subscriptionData.subscription_id, subscriptionData.quantity + operator); + // if (!response.body.subscription_id) return res.status(200).send(new ServerResponse(false, response.message || "Please check your subscription.")); + // } + // } + } else { + + const userExists = await this.checkIfUserActiveInOtherTeams(req.user?.owner_id as string, req.query?.email as string); + + // if (subscriptionData.status === "trialing") break; + // if (!userExists && !subscriptionData.is_credit && !subscriptionData.is_custom) { + // if (subscriptionData.subscription_status === "active" && subscriptionData.quantity > 0) { + // const operator = req.query.active === "true" ? - 1 : + 1; + // const response = await updateUsers(subscriptionData.subscription_id, subscriptionData.quantity + operator); + // if (!response.body.subscription_id) return res.status(200).send(new ServerResponse(false, response.message || "Please check your subscription.")); + // } + // } + + const q1 = `SELECT active FROM team_members WHERE id = $1;`; + const result1 = await db.query(q1, [req.params?.id]); + const [status] = result1.rows; + + if (status.active) { + const updateQ1 = `UPDATE users + SET active_team = (SELECT id FROM teams WHERE user_id = users.id ORDER BY created_at DESC LIMIT 1) + WHERE id = (SELECT user_id FROM team_members WHERE id = $1 AND active IS TRUE LIMIT 1);`; + await db.query(updateQ1, [req.params?.id]); + } + + const q = `UPDATE team_members SET active = NOT active WHERE id = $1 RETURNING active;`; + const result = await db.query(q, [req.params?.id]); + data = result.rows[0]; + } return res.status(200).send(new ServerResponse(true, [], `Team member ${data.active ? " activated" : " deactivated"} successfully.`)); } @@ -899,6 +1074,21 @@ export default class TeamMembersController extends WorklenzControllerBase { if (!req.body.team_id || !req.user?.id) return res.status(200).send(new ServerResponse(false, "Required fields are missing.")); + // check the subscription status + const subscriptionData = await checkTeamSubscriptionStatus(req.body.team_id); + + if (statusExclude.includes(subscriptionData.subscription_status)) { + return res.status(200).send(new ServerResponse(false, "Please check your subscription status.")); + } + + // if (subscriptionData.status === "trialing") break; + if (!subscriptionData.is_credit && !subscriptionData.is_custom) { + if (subscriptionData.subscription_status === "active") { + const response = await updateUsers(subscriptionData.subscription_id, subscriptionData.quantity + (req.body.emails.length || 1)); + if (!response.body.subscription_id) return res.status(200).send(new ServerResponse(false, response.message || "Please check your subscription.")); + } + } + const newMembers = await this.createOrInviteMembers(req.body, req.user); return res.status(200).send(new ServerResponse(true, newMembers, `Your teammates will get an email that gives them access to your team.`).withTitle("Invitations sent")); } diff --git a/worklenz-backend/src/controllers/timezones-controller.ts b/worklenz-backend/src/controllers/timezones-controller.ts index ede78257..6d9d1182 100644 --- a/worklenz-backend/src/controllers/timezones-controller.ts +++ b/worklenz-backend/src/controllers/timezones-controller.ts @@ -16,8 +16,8 @@ export default class TimezonesController extends WorklenzControllerBase { @HandleExceptions() public static async update(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { - const q = `UPDATE users SET timezone_id = $2 WHERE id = $1;`; - const result = await db.query(q, [req.user?.id, req.body.timezone]); - return res.status(200).send(new ServerResponse(true, result.rows, "Timezone updated")); + const q = `UPDATE users SET timezone_id = $2, language = $3 WHERE id = $1;`; + const result = await db.query(q, [req.user?.id, req.body.timezone, req.body.language]); + return res.status(200).send(new ServerResponse(true, result.rows, "Updated successfully")); } } diff --git a/worklenz-backend/src/cron_jobs/helpers.ts b/worklenz-backend/src/cron_jobs/helpers.ts index 86c69e60..b9add7fd 100644 --- a/worklenz-backend/src/cron_jobs/helpers.ts +++ b/worklenz-backend/src/cron_jobs/helpers.ts @@ -12,8 +12,8 @@ export function mapMembersWithAnd(members: string) { } export function getBaseUrl() { - if (isLocalServer()) return `http://${process.env.HOSTNAME}`; - return `https://${process.env.HOSTNAME}`; + if (isLocalServer()) return `http://${process.env.FRONTEND_URL}`; + return `https://${process.env.FRONTEND_URL}`; } function mapMembers(project: ITaskAssignmentModelProject) { diff --git a/worklenz-backend/src/cron_jobs/index.ts b/worklenz-backend/src/cron_jobs/index.ts index 08a46a0c..f13ec2e8 100644 --- a/worklenz-backend/src/cron_jobs/index.ts +++ b/worklenz-backend/src/cron_jobs/index.ts @@ -1,9 +1,11 @@ import {startDailyDigestJob} from "./daily-digest-job"; import {startNotificationsJob} from "./notifications-job"; import {startProjectDigestJob} from "./project-digest-job"; +import { startRecurringTasksJob } from "./recurring-tasks"; export function startCronJobs() { startNotificationsJob(); startDailyDigestJob(); startProjectDigestJob(); + // startRecurringTasksJob(); } diff --git a/worklenz-backend/src/cron_jobs/project-digest-job.ts b/worklenz-backend/src/cron_jobs/project-digest-job.ts index dac17a52..8841754d 100644 --- a/worklenz-backend/src/cron_jobs/project-digest-job.ts +++ b/worklenz-backend/src/cron_jobs/project-digest-job.ts @@ -7,6 +7,8 @@ import {sendProjectDailyDigest} from "../shared/email-notifications"; // At 11:00+00 (4.30pm+530) on every day-of-month if it's on every day-of-week from Monday through Friday. const TIME = "0 11 */1 * 1-5"; +// const TIME = "0/10 * * * *"; + // const TIME = "* * * * *"; const log = (value: any) => console.log("project-digest-cron-job:", value); diff --git a/worklenz-backend/src/cron_jobs/recurring-tasks.ts b/worklenz-backend/src/cron_jobs/recurring-tasks.ts new file mode 100644 index 00000000..a9ae7847 --- /dev/null +++ b/worklenz-backend/src/cron_jobs/recurring-tasks.ts @@ -0,0 +1,113 @@ +import { CronJob } from "cron"; +import { calculateNextEndDate, log_error } from "../shared/utils"; +import db from "../config/db"; +import { IRecurringSchedule, ITaskTemplate } from "../interfaces/recurring-tasks"; +import moment from "moment"; +import TasksController from "../controllers/tasks-controller"; + +// At 11:00+00 (4.30pm+530) on every day-of-month if it's on every day-of-week from Monday through Friday. +// const TIME = "0 11 */1 * 1-5"; +const TIME = "*/2 * * * *"; +const TIME_FORMAT = "YYYY-MM-DD"; +// const TIME = "0 0 * * *"; // Runs at midnight every day + +const log = (value: any) => console.log("recurring-task-cron-job:", value); + +async function onRecurringTaskJobTick() { + try { + log("(cron) Recurring tasks job started."); + + const templatesQuery = ` + SELECT t.*, s.*, (SELECT MAX(end_date) FROM tasks WHERE schedule_id = s.id) as last_task_end_date + FROM task_recurring_templates t + JOIN task_recurring_schedules s ON t.schedule_id = s.id; + `; + const templatesResult = await db.query(templatesQuery); + const templates = templatesResult.rows as (ITaskTemplate & IRecurringSchedule)[]; + + const now = moment(); + let createdTaskCount = 0; + + for (const template of templates) { + const lastTaskEndDate = template.last_task_end_date + ? moment(template.last_task_end_date) + : moment(template.created_at); + + const futureLimit = moment(template.last_checked_at || template.created_at).add(1, "week"); + + let nextEndDate = calculateNextEndDate(template, lastTaskEndDate); + + // Find the next future occurrence + while (nextEndDate.isSameOrBefore(now)) { + nextEndDate = calculateNextEndDate(template, nextEndDate); + } + + // Only create a task if it's within the future limit + if (nextEndDate.isSameOrBefore(futureLimit)) { + const existingTaskQuery = ` + SELECT id FROM tasks + WHERE schedule_id = $1 AND end_date::DATE = $2::DATE; + `; + const existingTaskResult = await db.query(existingTaskQuery, [template.schedule_id, nextEndDate.format(TIME_FORMAT)]); + + if (existingTaskResult.rows.length === 0) { + const createTaskQuery = `SELECT create_quick_task($1::json) as task;`; + const taskData = { + name: template.name, + priority_id: template.priority_id, + project_id: template.project_id, + reporter_id: template.reporter_id, + status_id: template.status_id || null, + end_date: nextEndDate.format(TIME_FORMAT), + schedule_id: template.schedule_id + }; + const createTaskResult = await db.query(createTaskQuery, [JSON.stringify(taskData)]); + const createdTask = createTaskResult.rows[0].task; + + if (createdTask) { + createdTaskCount++; + + for (const assignee of template.assignees) { + await TasksController.createTaskBulkAssignees(assignee.team_member_id, template.project_id, createdTask.id, assignee.assigned_by); + } + + for (const label of template.labels) { + const q = `SELECT add_or_remove_task_label($1, $2) AS labels;`; + await db.query(q, [createdTask.id, label.label_id]); + } + + console.log(`Created task for template ${template.name} with end date ${nextEndDate.format(TIME_FORMAT)}`); + } + } else { + console.log(`Skipped creating task for template ${template.name} with end date ${nextEndDate.format(TIME_FORMAT)} - task already exists`); + } + } else { + console.log(`No task created for template ${template.name} - next occurrence is beyond the future limit`); + } + + // Update the last_checked_at in the schedule + const updateScheduleQuery = ` + UPDATE task_recurring_schedules + SET last_checked_at = $1::DATE, last_created_task_end_date = $2 + WHERE id = $3; + `; + await db.query(updateScheduleQuery, [moment(template.last_checked_at || template.created_at).add(1, "day").format(TIME_FORMAT), nextEndDate.format(TIME_FORMAT), template.schedule_id]); + } + + log(`(cron) Recurring tasks job ended with ${createdTaskCount} new tasks created.`); + } catch (error) { + log_error(error); + log("(cron) Recurring task job ended with errors."); + } +} + +export function startRecurringTasksJob() { + log("(cron) Recurring task job ready."); + const job = new CronJob( + TIME, + () => void onRecurringTaskJobTick(), + () => log("(cron) Recurring task job successfully executed."), + true + ); + job.start(); +} \ No newline at end of file diff --git a/worklenz-backend/src/interfaces/passport-session.ts b/worklenz-backend/src/interfaces/passport-session.ts index dc9f408f..48117111 100644 --- a/worklenz-backend/src/interfaces/passport-session.ts +++ b/worklenz-backend/src/interfaces/passport-session.ts @@ -17,4 +17,5 @@ export interface IPassportSession extends IUser { socket_id?: string; is_expired?: boolean; owner_id?: string; + subscription_status?: string; } diff --git a/worklenz-backend/src/interfaces/recurring-tasks.ts b/worklenz-backend/src/interfaces/recurring-tasks.ts new file mode 100644 index 00000000..a16204e0 --- /dev/null +++ b/worklenz-backend/src/interfaces/recurring-tasks.ts @@ -0,0 +1,38 @@ +export interface IRecurringSchedule { + id: string; + schedule_type: "daily" | "weekly" | "monthly" | "yearly" | "every_x_days" | "every_x_weeks" | "every_x_months"; + days_of_week: number[] | null; + day_of_month: number | null; + date_of_month: number | null; + week_of_month: number | null; + interval_days: number | null; + interval_weeks: number | null; + interval_months: number | null; + last_created_task_end_date: Date | null; + last_checked_at: Date | null; + last_task_end_date: Date | null; + created_at: Date; +} + +interface ITaskTemplateAssignee { + team_member_id: string; + assigned_by: string +} + +interface ITaskTemplateLabel { + label_id: string; +} + + +export interface ITaskTemplate { + task_id: string; + schedule_id: string; + created_at: Date; + name: string; + priority_id: string; + project_id: string; + reporter_id: string; + status_id: string; + assignees: ITaskTemplateAssignee[]; + labels: ITaskTemplateLabel[] +} \ No newline at end of file diff --git a/worklenz-backend/src/interfaces/serialize-callback.ts b/worklenz-backend/src/interfaces/serialize-callback.ts index 1bea9e09..9ccd99df 100644 --- a/worklenz-backend/src/interfaces/serialize-callback.ts +++ b/worklenz-backend/src/interfaces/serialize-callback.ts @@ -1,3 +1,3 @@ export interface ISerializeCallback { - (error: string | null, id: string | null): void; + (error: string | null, user: { id: string | null } | null): void; } diff --git a/worklenz-backend/src/keys/PRIVATE_KEY_DEV.pem b/worklenz-backend/src/keys/PRIVATE_KEY_DEV.pem new file mode 100644 index 00000000..71af23fa --- /dev/null +++ b/worklenz-backend/src/keys/PRIVATE_KEY_DEV.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBAMe4wKg0OazdVWEyLCnTxubXHqpp6U7S7MiIE96Iufe+T4fe1EJl +2+7UJ0Vh0iO9vy/dr03Y9Mjm/IxgiaLEqFECAwEAAQJBAIV17jf4fjoHxZAnyN9C +h32mbvWNxLxJsrTmSfDBCRSFRv+ME7WAb7wGhfeDPZcxC+sDZv5EhTnDwQoVl0+3 +tOECIQDzAbIUX6IS401UKISr8rk9dmPa+i89z5JAyiuhX8sQdQIhANJmkUYjHJtp +do/4dmDC6Dgv6SPr9zrNFg2A9Hgu3zztAiBpSHDJFu33VPep4Kwqe0z6bhKxSvew +xf/NhkoE7qXiCQIgEltslWf+2PhspccR3QNka3KSrtWprnGyWN9FdS7xv0kCIDje +m2QMP/tkiyGlX4cxpDvoB3syPEsbnH+3iaGMlD1T +-----END RSA PRIVATE KEY----- diff --git a/worklenz-backend/src/keys/PRIVATE_KEY_PROD.pem b/worklenz-backend/src/keys/PRIVATE_KEY_PROD.pem new file mode 100644 index 00000000..61b47456 --- /dev/null +++ b/worklenz-backend/src/keys/PRIVATE_KEY_PROD.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOwIBAAJBALdfkpZY9GkPSezqNtNP70SDc5ovnB8NttBxheDecIXRiKkGQaTc +QuDq19IlDPr+jPvJ6VyMZXtK1UQ09ewUZQ0CAwEAAQJBAKIKkaXMW8bPHNt/qQ0Y +kO4xXyF8OvDyFH+kIdMxnauRm8Z28EC4S8F9sfaqL/haj8lMDDDUEhJJB5P3l4XW +3WECIQDbZBsfv5+++ie08FzW4K0IrTeFrkanbuV9fhx9sqpgNQIhANX43uuGl7qE +RfGEesIfK3FurZhNUXBzYwpZoGC4Drx5AiANK18tcrVGI4IKrHsGMwpwAOXaUnHP +Tyrbc5yGNxlfGQIgGgFGLn/MHvoGeiTsun0JTZ7y8Citdio/5jkgWcDk4ZkCIQCk +TLAHaLJHiN63o3F/lTwyMib/3xQrsjcxs6k/Y9VEHw== +-----END RSA PRIVATE KEY----- diff --git a/worklenz-backend/src/middlewares/session-middleware.ts b/worklenz-backend/src/middlewares/session-middleware.ts index 4efb4e04..cb6cd624 100644 --- a/worklenz-backend/src/middlewares/session-middleware.ts +++ b/worklenz-backend/src/middlewares/session-middleware.ts @@ -1,12 +1,13 @@ import session from "express-session"; import db from "../config/db"; +import { isProduction } from "../shared/utils"; // eslint-disable-next-line @typescript-eslint/no-var-requires const pgSession = require("connect-pg-simple")(session); export default session({ name: process.env.SESSION_NAME, - secret: process.env.SESSION_SECRET || [], // session secret + secret: process.env.SESSION_SECRET || "development-secret-key", proxy: false, resave: false, saveUninitialized: true, @@ -17,10 +18,10 @@ export default session({ }), cookie: { path: "/", - // secure: true, - // httpOnly: true, - // sameSite: true, - // domain: process.env.HOSTNAME, + // secure: isProduction(), + // httpOnly: isProduction(), + // sameSite: "none", + // domain: isProduction() ? ".worklenz.com" : undefined, maxAge: 30 * 24 * 60 * 60 * 1000 // 30 days } -}); +}); \ No newline at end of file diff --git a/worklenz-backend/src/middlewares/validators/sign-up-validator.ts b/worklenz-backend/src/middlewares/validators/sign-up-validator.ts index 5c00df6c..7618a6ef 100644 --- a/worklenz-backend/src/middlewares/validators/sign-up-validator.ts +++ b/worklenz-backend/src/middlewares/validators/sign-up-validator.ts @@ -5,9 +5,11 @@ import {isValidateEmail} from "../../shared/utils"; export default function (req: Request, res: Response, next: NextFunction) { const {name, email} = req.body; + if (!name) return res.status(200).send(new ServerResponse(false, null, "Name is required")); if (!email) return res.status(200).send(new ServerResponse(false, null, "Email is required")); if (!isValidateEmail(email)) return res.status(200).send(new ServerResponse(false, null, "Invalid email address")); + req.body.team_name = name.trim(); return next(); } diff --git a/worklenz-backend/src/middlewares/validators/task-attachments-validator.ts b/worklenz-backend/src/middlewares/validators/task-attachments-validator.ts index b8f67ad9..508bce64 100644 --- a/worklenz-backend/src/middlewares/validators/task-attachments-validator.ts +++ b/worklenz-backend/src/middlewares/validators/task-attachments-validator.ts @@ -1,11 +1,13 @@ -import {NextFunction} from "express"; +import { NextFunction } from "express"; -import {IWorkLenzRequest} from "../../interfaces/worklenz-request"; -import {IWorkLenzResponse} from "../../interfaces/worklenz-response"; -import {ServerResponse} from "../../models/server-response"; +import { IWorkLenzRequest } from "../../interfaces/worklenz-request"; +import { IWorkLenzResponse } from "../../interfaces/worklenz-response"; +import { ServerResponse } from "../../models/server-response"; +import { getFreePlanSettings, getUsedStorage } from "../../shared/paddle-utils"; +import { megabytesToBytes } from "../../shared/utils"; -export default function (req: IWorkLenzRequest, res: IWorkLenzResponse, next: NextFunction): IWorkLenzResponse | void { - const {file, file_name, project_id, size} = req.body; +export default async function (req: IWorkLenzRequest, res: IWorkLenzResponse, next: NextFunction): Promise { + const { file, file_name, project_id, size } = req.body; if (!file || !file_name || !project_id || !size) return res.status(200).send(new ServerResponse(false, null, "Upload failed")); @@ -13,6 +15,15 @@ export default function (req: IWorkLenzRequest, res: IWorkLenzResponse, next: Ne if (size > 5.243e+7) return res.status(200).send(new ServerResponse(false, null, "Max file size for attachments is 50 MB.").withTitle("Upload failed!")); + if (req.user?.subscription_status === "free" && req.user?.owner_id) { + const limits = await getFreePlanSettings(); + + const usedStorage = await getUsedStorage(req.user?.owner_id); + if ((parseInt(usedStorage) + size) > megabytesToBytes(parseInt(limits.free_tier_storage))) { + return res.status(200).send(new ServerResponse(false, [], `Sorry, the free plan cannot exceed ${limits.free_tier_storage}MB of storage.`)); + } + } + req.body.type = file_name.split(".").pop(); req.body.task_id = req.body.task_id || null; diff --git a/worklenz-backend/src/middlewares/validators/task-comment-attachment-validator.ts b/worklenz-backend/src/middlewares/validators/task-comment-attachment-validator.ts new file mode 100644 index 00000000..4de2c40d --- /dev/null +++ b/worklenz-backend/src/middlewares/validators/task-comment-attachment-validator.ts @@ -0,0 +1,17 @@ +import { NextFunction } from "express"; + +import { IWorkLenzRequest } from "../../interfaces/worklenz-request"; +import { IWorkLenzResponse } from "../../interfaces/worklenz-response"; +import { ServerResponse } from "../../models/server-response"; + +export default function (req: IWorkLenzRequest, res: IWorkLenzResponse, next: NextFunction): IWorkLenzResponse | void { + const { attachments, task_id } = req.body; + + if (attachments.length === 0) + return res.status(200).send(new ServerResponse(false, null, "Attachments are required!")); + + if (!task_id) + return res.status(200).send(new ServerResponse(false, null, "Task ID is required!")); + + return next(); +} \ No newline at end of file diff --git a/worklenz-backend/src/middlewares/validators/task-comment-body-validator.ts b/worklenz-backend/src/middlewares/validators/task-comment-body-validator.ts index 7742cfe3..6681c5b9 100644 --- a/worklenz-backend/src/middlewares/validators/task-comment-body-validator.ts +++ b/worklenz-backend/src/middlewares/validators/task-comment-body-validator.ts @@ -6,11 +6,11 @@ import {ServerResponse} from "../../models/server-response"; export default function (req: IWorkLenzRequest, res: IWorkLenzResponse, next: NextFunction): IWorkLenzResponse | void { const {content, task_id} = req.body; - if (!content) - return res.status(200).send(new ServerResponse(false, null, "Comment message is required")); + // if (!content) + // return res.status(200).send(new ServerResponse(false, null, "Comment message is required")); if (!task_id) return res.status(200).send(new ServerResponse(false, null, "Unable to create comment")); - if (content.length > 2000) + if (content.length > 5000) return res.status(200).send(new ServerResponse(false, null, "Message length exceeded")); req.body.mentions = Array.isArray(req.body.mentions) diff --git a/worklenz-backend/src/middlewares/validators/task-create-body--validator.ts b/worklenz-backend/src/middlewares/validators/task-create-body--validator.ts new file mode 100644 index 00000000..593a5e8b --- /dev/null +++ b/worklenz-backend/src/middlewares/validators/task-create-body--validator.ts @@ -0,0 +1,48 @@ +import {NextFunction} from "express"; + +import {IWorkLenzRequest} from "../../interfaces/worklenz-request"; +import {IWorkLenzResponse} from "../../interfaces/worklenz-response"; +import {ServerResponse} from "../../models/server-response"; +import {getRandomColorCode, sanitize, toMinutes, toRound} from "../../shared/utils"; + +export default function (req: IWorkLenzRequest, res: IWorkLenzResponse, next: NextFunction): IWorkLenzResponse | void { + const {name, assignees, project_id, labels} = req.body; + if (!name?.trim()?.length) + return res.status(200).send(new ServerResponse(false, null, "Name is required")); + + if (!project_id) + return res.status(200).send(new ServerResponse(false, null, "Project is required")); + + req.body.total_hours = isNaN(+req.body.total_hours) || req.body.total_hours > 1000 ? 0 : toRound(req.body.total_hours); + req.body.total_minutes = isNaN(+req.body.total_minutes) || req.body.total_minutes > 1000 ? 0 : toRound(req.body.total_minutes); + + req.body.assignees = Array.isArray(assignees) ? assignees : []; + req.body.labels = Array.isArray(labels) ? labels : []; + + req.body.reporter_id = req.user?.id || null; + req.body.total_minutes = toMinutes(req.body.total_hours, req.body.total_minutes); + req.body.team_id = req.user?.team_id || null; + + req.body.inline = req.query.inline || false; + + const labelsJson = []; + for (const label of req.body.labels) { + labelsJson.push({ + name: label, + color: getRandomColorCode() + }); + } + + req.body.labels = labelsJson; + + if (req.body.description) { + if (req.body.description.length > 4000) + return res.status(200).send(new ServerResponse(false, null, "Task description length exceeded!")); + req.body.description = sanitize(req.body.description); + } + + if (req.body.name.length > 100) + return res.status(200).send(new ServerResponse(false, null, "Task name length exceeded!")); + + return next(); +} diff --git a/worklenz-backend/src/middlewares/validators/task-dependencies-validator.ts b/worklenz-backend/src/middlewares/validators/task-dependencies-validator.ts new file mode 100644 index 00000000..744c68f4 --- /dev/null +++ b/worklenz-backend/src/middlewares/validators/task-dependencies-validator.ts @@ -0,0 +1,12 @@ +import { NextFunction } from "express"; + +import { IWorkLenzRequest } from "../../interfaces/worklenz-request"; +import { IWorkLenzResponse } from "../../interfaces/worklenz-response"; +import { ServerResponse } from "../../models/server-response"; + +export default function (req: IWorkLenzRequest, res: IWorkLenzResponse, next: NextFunction): IWorkLenzResponse | void { + const { example_name } = req.body; + if (!example_name) + return res.status(200).send(new ServerResponse(false, null, "Name is required")); + return next(); +} \ No newline at end of file diff --git a/worklenz-backend/src/middlewares/validators/team-members-body-validator.ts b/worklenz-backend/src/middlewares/validators/team-members-body-validator.ts index 7ed6f9d0..2c1cf895 100644 --- a/worklenz-backend/src/middlewares/validators/team-members-body-validator.ts +++ b/worklenz-backend/src/middlewares/validators/team-members-body-validator.ts @@ -12,7 +12,7 @@ export default function (req: IWorkLenzRequest, res: IWorkLenzResponse, next: Ne return res.status(200).send(new ServerResponse(false, null, "Email addresses cannot be empty")); for (const email of emails) { - if (!isValidateEmail(email)) + if (!isValidateEmail(email.trim())) return res.status(200).send(new ServerResponse(false, null, "Invalid email address")); } diff --git a/worklenz-backend/src/passport/deserialize.ts b/worklenz-backend/src/passport/deserialize.ts index ccb6509c..bbbd5352 100644 --- a/worklenz-backend/src/passport/deserialize.ts +++ b/worklenz-backend/src/passport/deserialize.ts @@ -20,14 +20,24 @@ async function clearEmailInvitations(email: string, teamId: string) { } // Check whether the user still exists on the database -export async function deserialize(id: string, done: IDeserializeCallback) { +export async function deserialize(user: { id: string | null }, done: IDeserializeCallback) { try { + if (!user || !user.id) { + return done(null, null); + } + + const {id} = user; + const excludedSubscriptionTypes = ["TRIAL", "PADDLE"]; const q = `SELECT deserialize_user($1) AS user;`; const result = await db.query(q, [id]); if (result.rows.length) { const [data] = result.rows; if (data?.user) { + const realExpiredDate = moment(data.user.valid_till_date).add(7, "days"); + data.user.is_expired = false; + data.user.is_member = !!data.user.team_member_id; + if (excludedSubscriptionTypes.includes(data.user.subscription_type)) data.user.is_expired = realExpiredDate.isBefore(moment(), "days"); void setLastActive(data.user.id); void clearEmailInvitations(data.user.email, data.user.team_id); diff --git a/worklenz-backend/src/passport/passport-strategies/passport-google.ts b/worklenz-backend/src/passport/passport-strategies/passport-google.ts index 04cbccd4..2cf153f3 100644 --- a/worklenz-backend/src/passport/passport-strategies/passport-google.ts +++ b/worklenz-backend/src/passport/passport-strategies/passport-google.ts @@ -12,11 +12,11 @@ async function handleGoogleLogin(req: Request, _accessToken: string, _refreshTok if (Array.isArray(profile.photos) && profile.photos.length) body.picture = profile.photos[0].value; // Check for existing accounts signed up using OAuth - const localAccountResult = await db.query("SELECT 1 FROM users WHERE email = $1 AND password IS NOT NULL;", [body.email]); + const localAccountResult = await db.query("SELECT 1 FROM users WHERE email = $1 AND password IS NOT NULL AND is_deleted IS FALSE;", [body.email]); if (localAccountResult.rowCount) { const message = `No Google account exists for email ${body.email}.`; (req.session as any).error = message; - return done(null, undefined, req.flash(ERROR_KEY, message)); + return done(null, undefined, { message: req.flash(ERROR_KEY, message) }); } // If the user came from an invitation, this exists diff --git a/worklenz-backend/src/passport/passport-strategies/passport-local-login.ts b/worklenz-backend/src/passport/passport-strategies/passport-local-login.ts index 67f74052..7d29fae8 100644 --- a/worklenz-backend/src/passport/passport-strategies/passport-local-login.ts +++ b/worklenz-backend/src/passport/passport-strategies/passport-local-login.ts @@ -1,46 +1,50 @@ import bcrypt from "bcrypt"; -import {Strategy as LocalStrategy} from "passport-local"; - -import {log_error} from "../../shared/utils"; +import { Strategy as LocalStrategy } from "passport-local"; +import { log_error } from "../../shared/utils"; import db from "../../config/db"; -import {Request} from "express"; +import { Request } from "express"; async function handleLogin(req: Request, email: string, password: string, done: any) { - (req.session as any).flash = {}; + console.log("Login attempt for:", email); - if (!email || !password) - return done(null, false, {message: "Invalid credentials."}); + if (!email || !password) { + console.log("Missing credentials"); + return done(null, false, { message: "Please enter both email and password" }); + } try { - // select the user from the database based on the username const q = `SELECT id, email, google_id, password FROM users WHERE email = $1 - AND google_id IS NULL;`; + AND google_id IS NULL + AND is_deleted IS FALSE;`; const result = await db.query(q, [email]); + console.log("User query result count:", result.rowCount); + const [data] = result.rows; - // Check user existence - if (!data?.password) - return done(null, false, {message: "Invalid credentials."}); - - // Compare the password & email - if (bcrypt.compareSync(password, data.password) && email === data.email) { - delete data.password; - - req.logout(() => true); - return done(false, data, {message: "User successfully logged in"}); + if (!data?.password) { + console.log("No account found"); + return done(null, false, { message: "No account found with this email" }); } - return done(null, false, {message: "Invalid credentials."}); + const passwordMatch = bcrypt.compareSync(password, data.password); + console.log("Password match:", passwordMatch); + + if (passwordMatch && email === data.email) { + delete data.password; + return done(null, data, {message: "User successfully logged in"}); + } + return done(null, false, { message: "Incorrect email or password" }); } catch (error) { + console.error("Login error:", error); log_error(error, req.body); return done(error); } } export default new LocalStrategy({ - usernameField: "email", // = email + usernameField: "email", passwordField: "password", passReqToCallback: true -}, (req, email, password, done) => void handleLogin(req, email, password, done)); +}, (req, email, password, done) => void handleLogin(req, email, password, done)); \ No newline at end of file diff --git a/worklenz-backend/src/passport/passport-strategies/passport-local-signup.ts b/worklenz-backend/src/passport/passport-strategies/passport-local-signup.ts index cfc66562..56395066 100644 --- a/worklenz-backend/src/passport/passport-strategies/passport-local-signup.ts +++ b/worklenz-backend/src/passport/passport-strategies/passport-local-signup.ts @@ -56,11 +56,7 @@ async function handleSignUp(req: Request, email: string, password: string, done: try { const user = await registerUser(password, team_id, name, team_name, email, timezone, team_member_id); sendWelcomeEmail(email, name); - - setTimeout(() => { - return done(null, user, req.flash(SUCCESS_KEY, "Registration successful. Please check your email for verification.")); - }, 500); - + return done(null, user, req.flash(SUCCESS_KEY, "Registration successful. Please check your email for verification.")); } catch (error: any) { const message = (error?.message) || ""; diff --git a/worklenz-backend/src/passport/serialize.ts b/worklenz-backend/src/passport/serialize.ts index ae7ed7d5..b3c603f6 100644 --- a/worklenz-backend/src/passport/serialize.ts +++ b/worklenz-backend/src/passport/serialize.ts @@ -3,5 +3,5 @@ import {IPassportSession} from "../interfaces/passport-session"; // Parse the user id to deserialize function export function serialize($user: IPassportSession, done: ISerializeCallback) { - done(null, $user?.id ?? null); + done(null, { id: $user?.id ?? null }); } diff --git a/worklenz-backend/src/public/148.3562c20f7c79dfbe.js b/worklenz-backend/src/public/148.3562c20f7c79dfbe.js deleted file mode 100644 index 513898df..00000000 --- a/worklenz-backend/src/public/148.3562c20f7c79dfbe.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkworklenz=self.webpackChunkworklenz||[]).push([[148],{17816:(zo,wi,ct)=>{ct.d(wi,{ZL:()=>Qt,kL:()=>zn,od:()=>Ki,qi:()=>we,zX:()=>rr});var R=ct(4942),u=ct(98137);class E{constructor(){this._request=null,this._charts=new Map,this._running=!1,this._lastDate=void 0}_notify(i,t,e,n){const r=t.duration;t.listeners[n].forEach(l=>l({chart:i,initial:t.initial,numSteps:r,currentStep:Math.min(e-t.start,r)}))}_refresh(){this._request||(this._running=!0,this._request=u.r.call(window,()=>{this._update(),this._request=null,this._running&&this._refresh()}))}_update(i=Date.now()){let t=0;this._charts.forEach((e,n)=>{if(!e.running||!e.items.length)return;const o=e.items;let c,r=o.length-1,l=!1;for(;r>=0;--r)c=o[r],c._active?(c._total>e.duration&&(e.duration=c._total),c.tick(i),l=!0):(o[r]=o[o.length-1],o.pop());l&&(n.draw(),this._notify(n,e,i,"progress")),o.length||(e.running=!1,this._notify(n,e,i,"complete"),e.initial=!1),t+=o.length}),this._lastDate=i,0===t&&(this._running=!1)}_getAnims(i){const t=this._charts;let e=t.get(i);return e||(e={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},t.set(i,e)),e}listen(i,t,e){this._getAnims(i).listeners[t].push(e)}add(i,t){!t||!t.length||this._getAnims(i).items.push(...t)}has(i){return this._getAnims(i).items.length>0}start(i){const t=this._charts.get(i);t&&(t.running=!0,t.start=Date.now(),t.duration=t.items.reduce((e,n)=>Math.max(e,n._duration),0),this._refresh())}running(i){if(!this._running)return!1;const t=this._charts.get(i);return!(!t||!t.running||!t.items.length)}stop(i){const t=this._charts.get(i);if(!t||!t.items.length)return;const e=t.items;let n=e.length-1;for(;n>=0;--n)e[n].cancel();t.items=[],this._notify(i,t,Date.now(),"complete")}remove(i){return this._charts.delete(i)}}var Ot=new E;const St="transparent",Dt={boolean:(s,i,t)=>t>.5?i:s,color(s,i,t){const e=(0,u.c)(s||St),n=e.valid&&(0,u.c)(i||St);return n&&n.valid?n.mix(e,t).hexString():i},number:(s,i,t)=>s+(i-s)*t};class Ve{constructor(i,t,e,n){const o=t[e];n=(0,u.a)([i.to,n,o,i.from]);const r=(0,u.a)([i.from,o,n]);this._active=!0,this._fn=i.fn||Dt[i.type||typeof r],this._easing=u.e[i.easing]||u.e.linear,this._start=Math.floor(Date.now()+(i.delay||0)),this._duration=this._total=Math.floor(i.duration),this._loop=!!i.loop,this._target=t,this._prop=e,this._from=r,this._to=n,this._promises=void 0}active(){return this._active}update(i,t,e){if(this._active){this._notify(!1);const n=this._target[this._prop],o=e-this._start,r=this._duration-o;this._start=e,this._duration=Math.floor(Math.max(r,i.duration)),this._total+=o,this._loop=!!i.loop,this._to=(0,u.a)([i.to,t,n,i.from]),this._from=(0,u.a)([i.from,n,t])}}cancel(){this._active&&(this.tick(Date.now()),this._active=!1,this._notify(!1))}tick(i){const t=i-this._start,e=this._duration,n=this._prop,o=this._from,r=this._loop,l=this._to;let c;if(this._active=o!==l&&(r||t1?2-c:c,c=this._easing(Math.min(1,Math.max(0,c))),this._target[n]=this._fn(o,l,c))}wait(){const i=this._promises||(this._promises=[]);return new Promise((t,e)=>{i.push({res:t,rej:e})})}_notify(i){const t=i?"res":"rej",e=this._promises||[];for(let n=0;n{const o=i[n];if(!(0,u.i)(o))return;const r={};for(const l of t)r[l]=o[l];((0,u.b)(o.properties)&&o.properties||[n]).forEach(l=>{(l===n||!e.has(l))&&e.set(l,r)})})}_animateOptions(i,t){const e=t.options,n=function We(s,i){if(!i)return;let t=s.options;if(t)return t.$shared&&(s.options=t=Object.assign({},t,{$shared:!1,$animations:{}})),t;s.options=i}(i,e);if(!n)return[];const o=this._createAnimations(n,e);return e.$shared&&function Ut(s,i){const t=[],e=Object.keys(i);for(let n=0;n{i.options=e},()=>{}),o}_createAnimations(i,t){const e=this._properties,n=[],o=i.$animations||(i.$animations={}),r=Object.keys(t),l=Date.now();let c;for(c=r.length-1;c>=0;--c){const d=r[c];if("$"===d.charAt(0))continue;if("options"===d){n.push(...this._animateOptions(i,t));continue}const g=t[d];let m=o[d];const x=e.get(d);if(m){if(x&&m.active()){m.update(x,g,l);continue}m.cancel()}x&&x.duration?(o[d]=m=new Ve(x,i,d,g),n.push(m)):i[d]=g}return n}update(i,t){if(0===this._properties.size)return void Object.assign(i,t);const e=this._createAnimations(i,t);return e.length?(Ot.add(this._chart,e),!0):void 0}}function ie(s,i){const t=s&&s.options||{},e=t.reverse,n=void 0===t.min?i:0,o=void 0===t.max?i:0;return{start:e?o:n,end:e?n:o}}function Ye(s,i){const t=[],e=s._getSortedDatasetMetas(i);let n,o;for(n=0,o=e.length;n0||!t&&o<0)return n.index}return null}function At(s,i){const{chart:t,_cachedMeta:e}=s,n=t._stacks||(t._stacks={}),{iScale:o,vScale:r,index:l}=e,c=o.axis,d=r.axis,g=function Ze(s,i,t){return`${s.id}.${i.id}.${t.stack||t.type}`}(o,r,e),m=i.length;let x;for(let v=0;vt[e].axis===i).shift()}function ne(s,i){const t=s.controller.index,e=s.vScale&&s.vScale.axis;if(e){i=i||s._parsed;for(const n of i){const o=n._stacks;if(!o||void 0===o[e]||void 0===o[e][t])return;delete o[e][t],void 0!==o[e]._visualValues&&void 0!==o[e]._visualValues[t]&&delete o[e]._visualValues[t]}}}const se=s=>"reset"===s||"none"===s,fe=(s,i)=>i?s:Object.assign({},s);let It=(()=>{class s{constructor(t,e){this.chart=t,this._ctx=t.ctx,this.index=e,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.supportsDecimation=!1,this.$context=void 0,this._syncList=[],this.datasetElementType=new.target.datasetElementType,this.dataElementType=new.target.dataElementType,this.initialize()}initialize(){const t=this._cachedMeta;this.configure(),this.linkScales(),t._stacked=Ue(t.vScale,t),this.addElements(),this.options.fill&&!this.chart.isPluginEnabled("filler")&&console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options")}updateIndex(t){this.index!==t&&ne(this._cachedMeta),this.index=t}linkScales(){const t=this.chart,e=this._cachedMeta,n=this.getDataset(),o=(x,v,S,P)=>"x"===x?v:"r"===x?P:S,r=e.xAxisID=(0,u.v)(n.xAxisID,Tt(t,"x")),l=e.yAxisID=(0,u.v)(n.yAxisID,Tt(t,"y")),c=e.rAxisID=(0,u.v)(n.rAxisID,Tt(t,"r")),d=e.indexAxis,g=e.iAxisID=o(d,r,l,c),m=e.vAxisID=o(d,l,r,c);e.xScale=this.getScaleForId(r),e.yScale=this.getScaleForId(l),e.rScale=this.getScaleForId(c),e.iScale=this.getScaleForId(g),e.vScale=this.getScaleForId(m)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(t){return this.chart.scales[t]}_getOtherScale(t){const e=this._cachedMeta;return t===e.iScale?e.vScale:e.iScale}reset(){this._update("reset")}_destroy(){const t=this._cachedMeta;this._data&&(0,u.u)(this._data,this),t._stacked&&ne(t)}_dataCheck(){const t=this.getDataset(),e=t.data||(t.data=[]),n=this._data;if((0,u.i)(e))this._data=function Pi(s){const i=Object.keys(s),t=new Array(i.length);let e,n,o;for(e=0,n=i.length;e0&&n._parsed[t-1];if(!1===this._parsing)n._parsed=o,n._sorted=!0,v=o;else{v=(0,u.b)(o[t])?this.parseArrayData(n,o,t,e):(0,u.i)(o[t])?this.parseObjectData(n,o,t,e):this.parsePrimitiveData(n,o,t,e);const S=()=>null===x[c]||g&&x[c]s&&!i.hidden&&i._stacked&&{keys:Ye(this.chart,!0),values:null})(e,n),g={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY},{min:m,max:x}=function Ci(s){const{min:i,max:t,minDefined:e,maxDefined:n}=s.getUserBounds();return{min:e?i:Number.NEGATIVE_INFINITY,max:n?t:Number.POSITIVE_INFINITY}}(c);let v,S;function P(){S=o[v];const O=S[c.axis];return!(0,u.g)(S[t.axis])||m>O||x=0;--v)if(!P()){this.updateRangeFromParsed(g,t,S,d);break}return g}getAllParsedValues(t){const e=this._cachedMeta._parsed,n=[];let o,r,l;for(o=0,r=e.length;o=0&&tthis.getContext(n,o,e),x);return O.$shared&&(O.$shared=d,r[l]=Object.freeze(fe(O,d))),O}_resolveAnimations(t,e,n){const o=this.chart,r=this._cachedDataOpts,l=`animation-${e}`,c=r[l];if(c)return c;let d;if(!1!==o.options.animation){const m=this.chart.config,x=m.datasetAnimationScopeKeys(this._type,e),v=m.getOptionScopes(this.getDataset(),x);d=m.createResolver(v,this.getContext(t,n,e))}const g=new mt(o,d&&d.animations);return d&&d._cacheable&&(r[l]=Object.freeze(g)),g}getSharedOptions(t){if(t.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},t))}includeOptions(t,e){return!e||se(t)||this.chart._animationsDisabled}_getSharedOptions(t,e){const n=this.resolveDataElementOptions(t,e),o=this._sharedOptions,r=this.getSharedOptions(n),l=this.includeOptions(e,r)||r!==o;return this.updateSharedOptions(r,e,n),{sharedOptions:r,includeOptions:l}}updateElement(t,e,n,o){se(o)?Object.assign(t,n):this._resolveAnimations(e,o).update(t,n)}updateSharedOptions(t,e,n){t&&!se(e)&&this._resolveAnimations(void 0,e).update(t,n)}_setStyle(t,e,n,o){t.active=o;const r=this.getStyle(e,o);this._resolveAnimations(e,n,o).update(t,{options:!o&&this.getSharedOptions(r)||r})}removeHoverStyle(t,e,n){this._setStyle(t,n,"active",!1)}setHoverStyle(t,e,n){this._setStyle(t,n,"active",!0)}_removeDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!1)}_setDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!0)}_resyncElements(t){const e=this._data,n=this._cachedMeta.data;for(const[c,d,g]of this._syncList)this[c](d,g);this._syncList=[];const o=n.length,r=e.length,l=Math.min(r,o);l&&this.parse(0,l),r>o?this._insertElements(o,r-o,t):r{for(g.length+=e,c=g.length-1;c>=l;c--)g[c]=g[c-e]};for(d(r),c=t;cn-o))}return s._cache.$bar}(i,s.type);let n,o,r,l,e=i._length;const c=()=>{32767===r||-32768===r||((0,u.h)(l)&&(e=Math.min(e,Math.abs(r-l)||e)),l=r)};for(n=0,o=t.length;nMath.abs(l)&&(c=l,d=r),i[t.axis]=d,i._custom={barStart:c,barEnd:d,start:n,end:o,min:r,max:l}}(s,i,t,e):i[t.axis]=t.parse(s,e),i}function q(s,i,t,e){const n=s.iScale,o=s.vScale,r=n.getLabels(),l=n===o,c=[];let d,g,m,x;for(d=t,g=t+e;ds.x,t="left",e="right"):(i=s.base{class s extends It{parsePrimitiveData(t,e,n,o){return q(t,e,n,o)}parseArrayData(t,e,n,o){return q(t,e,n,o)}parseObjectData(t,e,n,o){const{iScale:r,vScale:l}=t,{xAxisKey:c="x",yAxisKey:d="y"}=this._parsing,g="x"===r.axis?c:d,m="x"===l.axis?c:d,x=[];let v,S,P,O;for(v=n,S=n+o;vd.controller.options.grouped),r=n.options.stacked,l=[],c=d=>{const g=d.controller.getParsed(e),m=g&&g[d.vScale.axis];if((0,u.k)(m)||isNaN(m))return!0};for(const d of o)if((void 0===e||!c(d))&&((!1===r||-1===l.indexOf(d.stack)||void 0===r&&void 0===d.stack)&&l.push(d.stack),d.index===t))break;return l.length||l.push(void 0),l}_getStackCount(t){return this._getStacks(void 0,t).length}_getStackIndex(t,e,n){const o=this._getStacks(t,n),r=void 0!==e?o.indexOf(e):-1;return-1===r?o.length-1:r}_getRuler(){const t=this.options,e=this._cachedMeta,n=e.iScale,o=[];let r,l;for(r=0,l=e.data.length;r=t?1:-1)}(O,e,c)*l,x===c&&(A-=O/2);const B=e.getPixelForDecimal(0),j=e.getPixelForDecimal(1),L=Math.min(B,j),H=Math.max(B,j);A=Math.max(Math.min(A,H),L),P=A+O,n&&!m&&(d._stacks[e.axis]._visualValues[o]=e.getValueForPixel(P)-e.getValueForPixel(A))}if(A===e.getPixelForValue(c)){const B=(0,u.s)(O)*e.getLineWidthForValue(c)/2;A+=B,O-=B}return{size:O,base:A,head:P,center:P+O/2}}_calculateBarIndexPixels(t,e){const n=e.scale,o=this.options,r=o.skipNull,l=(0,u.v)(o.maxBarThickness,1/0);let c,d;if(e.grouped){const g=r?this._getStackCount(t):e.stackCount,m="flex"===o.barThickness?function ge(s,i,t,e){const n=i.pixels,o=n[s];let r=s>0?n[s-1]:null,l=s{class s extends It{initialize(){this.enableOptionSharing=!0,super.initialize()}parsePrimitiveData(t,e,n,o){const r=super.parsePrimitiveData(t,e,n,o);for(let l=0;l=0;--n)e=Math.max(e,t[n].size(this.resolveDataElementOptions(n))/2);return e>0&&e}getLabelAndValue(t){const e=this._cachedMeta,n=this.chart.data.labels||[],{xScale:o,yScale:r}=e,l=this.getParsed(t),c=o.getLabelForValue(l.x),d=r.getLabelForValue(l.y),g=l._custom;return{label:n[t]||"",value:"("+c+", "+d+(g?", "+g:"")+")"}}update(t){const e=this._cachedMeta.data;this.updateElements(e,0,e.length,t)}updateElements(t,e,n,o){const r="reset"===o,{iScale:l,vScale:c}=this._cachedMeta,{sharedOptions:d,includeOptions:g}=this._getSharedOptions(e,o),m=l.axis,x=c.axis;for(let v=e;v{class s extends It{constructor(t,e){super(t,e),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(t,e){const n=this.getDataset().data,o=this._cachedMeta;if(!1===this._parsing)o._parsed=n;else{let l,c,r=d=>+n[d];if((0,u.i)(n[t])){const{key:d="value"}=this._parsing;r=g=>+(0,u.f)(n[g],d)}for(l=t,c=t+e;l(0,u.p)(B,l,c,!0)?1:Math.max(j,j*t,L,L*t),S=(B,j,L)=>(0,u.p)(B,l,c,!0)?-1:Math.min(j,j*t,L,L*t),P=v(0,d,m),O=v(u.H,g,x),z=S(u.P,d,m),A=S(u.P+u.H,g,x);e=(P-z)/2,n=(O-A)/2,o=-(P+z)/2,r=-(O+A)/2}return{ratioX:e,ratioY:n,offsetX:o,offsetY:r}}(x,m,d),B=Math.max(Math.min((n.width-l)/v,(n.height-l)/S)/2,0),j=(0,u.n)(this.options.radius,B),H=(j-Math.max(j*d,0))/this._getVisibleDatasetWeightTotal();this.offsetX=P*j,this.offsetY=O*j,o.total=this.calculateTotal(),this.outerRadius=j-H*this._getRingWeightOffset(this.index),this.innerRadius=Math.max(this.outerRadius-H*g,0),this.updateElements(r,0,r.length,t)}_circumference(t,e){const n=this.options,o=this._cachedMeta,r=this._getCircumference();return e&&n.animation.animateRotate||!this.chart.getDataVisibility(t)||null===o._parsed[t]||o.data[t].hidden?0:this.calculateCircumference(o._parsed[t]*r/u.T)}updateElements(t,e,n,o){const r="reset"===o,l=this.chart,c=l.chartArea,m=(c.left+c.right)/2,x=(c.top+c.bottom)/2,v=r&&l.options.animation.animateScale,S=v?0:this.innerRadius,P=v?0:this.outerRadius,{sharedOptions:O,includeOptions:z}=this._getSharedOptions(e,o);let B,A=this._getRotation();for(B=0;B0&&!isNaN(t)?u.T*(Math.abs(t)/e):0}getLabelAndValue(t){const n=this.chart,o=n.data.labels||[],r=(0,u.o)(this._cachedMeta._parsed[t],n.options.locale);return{label:o[t]||"",value:r}}getMaxBorderWidth(t){let e=0;const n=this.chart;let o,r,l,c,d;if(!t)for(o=0,r=n.data.datasets.length;o"spacing"!==i,_indexable:i=>"spacing"!==i&&!i.startsWith("borderDash")&&!i.startsWith("hoverBorderDash")}),(0,R.Z)(s,"overrides",{aspectRatio:1,plugins:{legend:{labels:{generateLabels(i){const t=i.data;if(t.labels.length&&t.datasets.length){const{labels:{pointStyle:e,color:n}}=i.legend.options;return t.labels.map((o,r)=>{const c=i.getDatasetMeta(0).controller.getStyle(r);return{text:o,fillStyle:c.backgroundColor,strokeStyle:c.borderColor,fontColor:n,lineWidth:c.borderWidth,pointStyle:e,hidden:!i.getDataVisibility(r),index:r}})}return[]}},onClick(i,t,e){e.chart.toggleDataVisibility(t.index),e.chart.update()}}}}),s})(),Oe=(()=>{class s extends It{initialize(){this.enableOptionSharing=!0,this.supportsDecimation=!0,super.initialize()}update(t){const e=this._cachedMeta,{dataset:n,data:o=[],_dataset:r}=e,l=this.chart._animationsDisabled;let{start:c,count:d}=(0,u.q)(e,o,l);this._drawStart=c,this._drawCount=d,(0,u.w)(e)&&(c=0,d=o.length),n._chart=this.chart,n._datasetIndex=this.index,n._decimated=!!r._decimated,n.points=o;const g=this.resolveDatasetElementOptions(t);this.options.showLine||(g.borderWidth=0),g.segment=this.options.segment,this.updateElement(n,void 0,{animated:!l,options:g},t),this.updateElements(o,c,d,t)}updateElements(t,e,n,o){const r="reset"===o,{iScale:l,vScale:c,_stacked:d,_dataset:g}=this._cachedMeta,{sharedOptions:m,includeOptions:x}=this._getSharedOptions(e,o),v=l.axis,S=c.axis,{spanGaps:P,segment:O}=this.options,z=(0,u.x)(P)?P:Number.POSITIVE_INFINITY,A=this.chart._animationsDisabled||r||"none"===o,B=e+n,j=t.length;let L=e>0&&this.getParsed(e-1);for(let H=0;H=B){V.skip=!0;continue}const U=this.getParsed(H),tt=(0,u.k)(U[S]),J=V[v]=l.getPixelForValue(U[v],H),nt=V[S]=r||tt?c.getBasePixel():c.getPixelForValue(d?this.applyStack(c,U,d):U[S],H);V.skip=isNaN(J)||isNaN(nt)||tt,V.stop=H>0&&Math.abs(U[v]-L[v])>z,O&&(V.parsed=U,V.raw=g.data[H]),x&&(V.options=m||this.resolveDataElementOptions(H,W.active?"active":o)),A||this.updateElement(W,H,V,o),L=U}}getMaxOverflow(){const t=this._cachedMeta,e=t.dataset,n=e.options&&e.options.borderWidth||0,o=t.data||[];if(!o.length)return n;const r=o[0].size(this.resolveDataElementOptions(0)),l=o[o.length-1].size(this.resolveDataElementOptions(o.length-1));return Math.max(n,r,l)/2}draw(){const t=this._cachedMeta;t.dataset.updateControlPoints(this.chart.chartArea,t.iScale.axis),super.draw()}}return(0,R.Z)(s,"id","line"),(0,R.Z)(s,"defaults",{datasetElementType:"line",dataElementType:"point",showLine:!0,spanGaps:!1}),(0,R.Z)(s,"overrides",{scales:{_index_:{type:"category"},_value_:{type:"linear"}}}),s})(),_t=(()=>{class s extends It{constructor(t,e){super(t,e),this.innerRadius=void 0,this.outerRadius=void 0}getLabelAndValue(t){const n=this.chart,o=n.data.labels||[],r=(0,u.o)(this._cachedMeta._parsed[t].r,n.options.locale);return{label:o[t]||"",value:r}}parseObjectData(t,e,n,o){return u.y.bind(this)(t,e,n,o)}update(t){const e=this._cachedMeta.data;this._updateRadius(),this.updateElements(e,0,e.length,t)}getMinMax(){const e={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY};return this._cachedMeta.data.forEach((n,o)=>{const r=this.getParsed(o).r;!isNaN(r)&&this.chart.getDataVisibility(o)&&(re.max&&(e.max=r))}),e}_updateRadius(){const t=this.chart,e=t.chartArea,n=t.options,o=Math.min(e.right-e.left,e.bottom-e.top),r=Math.max(o/2,0),c=(r-Math.max(n.cutoutPercentage?r/100*n.cutoutPercentage:1,0))/t.getVisibleDatasetCount();this.outerRadius=r-c*this.index,this.innerRadius=this.outerRadius-c}updateElements(t,e,n,o){const r="reset"===o,l=this.chart,d=l.options.animation,g=this._cachedMeta.rScale,m=g.xCenter,x=g.yCenter,v=g.getIndexAngle(0)-.5*u.P;let P,S=v;const O=360/this.countVisibleElements();for(P=0;P{!isNaN(this.getParsed(o).r)&&this.chart.getDataVisibility(o)&&e++}),e}_computeAngle(t,e,n){return this.chart.getDataVisibility(t)?(0,u.t)(this.resolveDataElementOptions(t,e).angle||n):0}}return(0,R.Z)(s,"id","polarArea"),(0,R.Z)(s,"defaults",{dataElementType:"arc",animation:{animateRotate:!0,animateScale:!0},animations:{numbers:{type:"number",properties:["x","y","startAngle","endAngle","innerRadius","outerRadius"]}},indexAxis:"r",startAngle:0}),(0,R.Z)(s,"overrides",{aspectRatio:1,plugins:{legend:{labels:{generateLabels(i){const t=i.data;if(t.labels.length&&t.datasets.length){const{labels:{pointStyle:e,color:n}}=i.legend.options;return t.labels.map((o,r)=>{const c=i.getDatasetMeta(0).controller.getStyle(r);return{text:o,fillStyle:c.backgroundColor,strokeStyle:c.borderColor,fontColor:n,lineWidth:c.borderWidth,pointStyle:e,hidden:!i.getDataVisibility(r),index:r}})}return[]}},onClick(i,t,e){e.chart.toggleDataVisibility(t.index),e.chart.update()}}},scales:{r:{type:"radialLinear",angleLines:{display:!1},beginAtZero:!0,grid:{circular:!0},pointLabels:{display:!1},startAngle:0}}}),s})();var yt=Object.freeze({__proto__:null,BarController:pe,BubbleController:ho,DoughnutController:zi,LineController:Oe,PieController:(()=>{class s extends zi{}return(0,R.Z)(s,"id","pie"),(0,R.Z)(s,"defaults",{cutout:0,rotation:0,circumference:360,radius:"100%"}),s})(),PolarAreaController:_t,RadarController:(()=>{class s extends It{getLabelAndValue(t){const e=this._cachedMeta.vScale,n=this.getParsed(t);return{label:e.getLabels()[t],value:""+e.getLabelForValue(n[e.axis])}}parseObjectData(t,e,n,o){return u.y.bind(this)(t,e,n,o)}update(t){const e=this._cachedMeta,n=e.dataset,o=e.data||[],r=e.iScale.getLabels();if(n.points=o,"resize"!==t){const l=this.resolveDatasetElementOptions(t);this.options.showLine||(l.borderWidth=0),this.updateElement(n,void 0,{_loop:!0,_fullLoop:r.length===o.length,options:l},t)}this.updateElements(o,0,o.length,t)}updateElements(t,e,n,o){const r=this._cachedMeta.rScale,l="reset"===o;for(let c=e;c{class s extends It{getLabelAndValue(t){const e=this._cachedMeta,n=this.chart.data.labels||[],{xScale:o,yScale:r}=e,l=this.getParsed(t),c=o.getLabelForValue(l.x),d=r.getLabelForValue(l.y);return{label:n[t]||"",value:"("+c+", "+d+")"}}update(t){const e=this._cachedMeta,{data:n=[]}=e,o=this.chart._animationsDisabled;let{start:r,count:l}=(0,u.q)(e,n,o);if(this._drawStart=r,this._drawCount=l,(0,u.w)(e)&&(r=0,l=n.length),this.options.showLine){this.datasetElementType||this.addElements();const{dataset:c,_dataset:d}=e;c._chart=this.chart,c._datasetIndex=this.index,c._decimated=!!d._decimated,c.points=n;const g=this.resolveDatasetElementOptions(t);g.segment=this.options.segment,this.updateElement(c,void 0,{animated:!o,options:g},t)}else this.datasetElementType&&(delete e.dataset,this.datasetElementType=!1);this.updateElements(n,r,l,t)}addElements(){const{showLine:t}=this.options;!this.datasetElementType&&t&&(this.datasetElementType=this.chart.registry.getElement("line")),super.addElements()}updateElements(t,e,n,o){const r="reset"===o,{iScale:l,vScale:c,_stacked:d,_dataset:g}=this._cachedMeta,m=this.resolveDataElementOptions(e,o),x=this.getSharedOptions(m),v=this.includeOptions(o,x),S=l.axis,P=c.axis,{spanGaps:O,segment:z}=this.options,A=(0,u.x)(O)?O:Number.POSITIVE_INFINITY,B=this.chart._animationsDisabled||r||"none"===o;let j=e>0&&this.getParsed(e-1);for(let L=e;L0&&Math.abs(W[S]-j[S])>A,z&&(V.parsed=W,V.raw=g.data[L]),v&&(V.options=x||this.resolveDataElementOptions(L,H.active?"active":o)),B||this.updateElement(H,L,V,o),j=W}this.updateSharedOptions(x,o,m)}getMaxOverflow(){const t=this._cachedMeta,e=t.data||[];if(!this.options.showLine){let c=0;for(let d=e.length-1;d>=0;--d)c=Math.max(c,e[d].size(this.resolveDataElementOptions(d))/2);return c>0&&c}const n=t.dataset,o=n.options&&n.options.borderWidth||0;if(!e.length)return o;const r=e[0].size(this.resolveDataElementOptions(0)),l=e[e.length-1].size(this.resolveDataElementOptions(e.length-1));return Math.max(o,r,l)/2}}return(0,R.Z)(s,"id","scatter"),(0,R.Z)(s,"defaults",{datasetElementType:!1,dataElementType:"point",showLine:!1,fill:!1}),(0,R.Z)(s,"overrides",{interaction:{mode:"point"},scales:{x:{type:"linear"},y:{type:"linear"}}}),s})()});function oe(){throw new Error("This method is not implemented: Check that a complete date adapter is provided.")}class Je{static override(i){Object.assign(Je.prototype,i)}constructor(i){(0,R.Z)(this,"options",void 0),this.options=i||{}}init(){}formats(){return oe()}parse(){return oe()}format(){return oe()}add(){return oe()}diff(){return oe()}startOf(){return oe()}endOf(){return oe()}}var ls__date=Je;function cs(s,i,t,e){const{controller:n,data:o,_sorted:r}=s,l=n._cachedMeta.iScale;if(l&&i===l.axis&&"r"!==i&&r&&o.length){const c=l._reversePixels?u.A:u.B;if(!e)return c(o,i,t);if(n._sharedOptions){const d=o[0],g="function"==typeof d.getRange&&d.getRange(i);if(g){const m=c(o,i,t-g),x=c(o,i,t+g);return{lo:m.lo,hi:x.hi}}}}return{lo:0,hi:o.length-1}}function Ae(s,i,t,e,n){const o=s.getSortedVisibleDatasetMetas(),r=t[i];for(let l=0,c=o.length;l{c[r](i[t],n)&&(o.push({element:c,datasetIndex:d,index:g}),l=l||c.inRange(i.x,i.y,n))}),e&&!l?[]:o}var fo={evaluateInteractionItems:Ae,modes:{index(s,i,t,e){const n=(0,u.z)(i,s),o=t.axis||"x",r=t.includeInvisible||!1,l=t.intersect?ti(s,n,o,e,r):ei(s,n,o,!1,e,r),c=[];return l.length?(s.getSortedVisibleDatasetMetas().forEach(d=>{const g=l[0].index,m=d.data[g];m&&!m.skip&&c.push({element:m,datasetIndex:d.index,index:g})}),c):[]},dataset(s,i,t,e){const n=(0,u.z)(i,s),o=t.axis||"xy",r=t.includeInvisible||!1;let l=t.intersect?ti(s,n,o,e,r):ei(s,n,o,!1,e,r);if(l.length>0){const c=l[0].datasetIndex,d=s.getDatasetMeta(c).data;l=[];for(let g=0;gti(s,(0,u.z)(i,s),t.axis||"xy",e,t.includeInvisible||!1),nearest:(s,i,t,e)=>ei(s,(0,u.z)(i,s),t.axis||"xy",t.intersect,e,t.includeInvisible||!1),x:(s,i,t,e)=>rn(s,(0,u.z)(i,s),"x",t.intersect,e),y:(s,i,t,e)=>rn(s,(0,u.z)(i,s),"y",t.intersect,e)}};const Ei=["left","top","right","bottom"];function Te(s,i){return s.filter(t=>t.pos===i)}function an(s,i){return s.filter(t=>-1===Ei.indexOf(t.pos)&&t.box.axis===i)}function re(s,i){return s.sort((t,e)=>{const n=i?e:t,o=i?t:e;return n.weight===o.weight?n.index-o.index:n.weight-o.weight})}function ln(s,i,t,e){return Math.max(s[t],i[t])+Math.max(s[e],i[e])}function ft(s,i){s.top=Math.max(s.top,i.top),s.left=Math.max(s.left,i.left),s.bottom=Math.max(s.bottom,i.bottom),s.right=Math.max(s.right,i.right)}function Et(s,i,t,e){const{pos:n,box:o}=t,r=s.maxPadding;if(!(0,u.i)(n)){t.size&&(s[n]-=t.size);const m=e[t.stack]||{size:0,count:1};m.size=Math.max(m.size,t.horizontal?o.height:o.width),t.size=m.size/m.count,s[n]+=t.size}o.getPadding&&ft(r,o.getPadding());const l=Math.max(0,i.outerWidth-ln(r,s,"left","right")),c=Math.max(0,i.outerHeight-ln(r,s,"top","bottom")),d=l!==s.w,g=c!==s.h;return s.w=l,s.h=c,t.horizontal?{same:d,other:g}:{same:g,other:d}}function ii(s,i){const t=i.maxPadding;return function e(n){const o={left:0,top:0,right:0,bottom:0};return n.forEach(r=>{o[r]=Math.max(i[r],t[r])}),o}(s?["left","right"]:["top","bottom"])}function Le(s,i,t,e){const n=[];let o,r,l,c,d,g;for(o=0,r=s.length,d=0;od.box.fullSize),!0),e=re(Te(i,"left"),!0),n=re(Te(i,"right")),o=re(Te(i,"top"),!0),r=re(Te(i,"bottom")),l=an(i,"x"),c=an(i,"y");return{fullSize:t,leftAndTop:e.concat(o),rightAndBottom:n.concat(c).concat(r).concat(l),chartArea:Te(i,"chartArea"),vertical:e.concat(n).concat(c),horizontal:o.concat(r).concat(l)}}(s.boxes),c=l.vertical,d=l.horizontal;(0,u.F)(s.boxes,P=>{"function"==typeof P.beforeLayout&&P.beforeLayout()});const g=c.reduce((P,O)=>O.box.options&&!1===O.box.options.display?P:P+1,0)||1,m=Object.freeze({outerWidth:i,outerHeight:t,padding:n,availableWidth:o,availableHeight:r,vBoxMaxWidth:o/2/g,hBoxMaxHeight:r/2}),x=Object.assign({},n);ft(x,(0,u.E)(e));const v=Object.assign({maxPadding:x,w:o,h:r,x:n.left,y:n.top},n),S=function ze(s,i){const t=function hs(s){const i={};for(const t of s){const{stack:e,pos:n,stackWeight:o}=t;if(!e||!Ei.includes(n))continue;const r=i[e]||(i[e]={count:0,placed:0,weight:0,size:0});r.count++,r.weight+=o}return i}(s),{vBoxMaxWidth:e,hBoxMaxHeight:n}=i;let o,r,l;for(o=0,r=s.length;o{const O=P.box;Object.assign(O,s.chartArea),O.update(v.w,v.h,{left:0,top:0,right:0,bottom:0})})}};class _e{acquireContext(i,t){}releaseContext(i){return!1}addEventListener(i,t,e){}removeEventListener(i,t,e){}getDevicePixelRatio(){return 1}getMaximumSize(i,t,e,n){return t=Math.max(0,t||i.width),e=e||i.height,{width:t,height:Math.max(0,n?Math.floor(t/n):e)}}isAttached(i){return!0}updateConfig(i){}}class Ii extends _e{acquireContext(i){return i&&i.getContext&&i.getContext("2d")||null}updateConfig(i){i.options.animation=!1}}const Bt="$chartjs",fs={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},cn=s=>null===s||""===s,hn=!!u.K&&{passive:!0};function ms(s,i,t){s.canvas.removeEventListener(i,t,hn)}function ni(s,i){for(const t of s)if(t===i||t.contains(i))return!0}function _s(s,i,t){const e=s.canvas,n=new MutationObserver(o=>{let r=!1;for(const l of o)r=r||ni(l.addedNodes,e),r=r&&!ni(l.removedNodes,e);r&&t()});return n.observe(document,{childList:!0,subtree:!0}),n}function Fi(s,i,t){const e=s.canvas,n=new MutationObserver(o=>{let r=!1;for(const l of o)r=r||ni(l.removedNodes,e),r=r&&!ni(l.addedNodes,e);r&&t()});return n.observe(document,{childList:!0,subtree:!0}),n}const Ee=new Map;let Ht=0;function Bi(){const s=window.devicePixelRatio;s!==Ht&&(Ht=s,Ee.forEach((i,t)=>{t.currentDevicePixelRatio!==s&&i()}))}function dn(s,i,t){const e=s.canvas,n=e&&(0,u.I)(e);if(!n)return;const o=(0,u.L)((l,c)=>{const d=n.clientWidth;t(l,c),d{const c=l[0],d=c.contentRect.width,g=c.contentRect.height;0===d&&0===g||o(d,g)});return r.observe(n),function si(s,i){Ee.size||window.addEventListener("resize",Bi),Ee.set(s,i)}(s,o),r}function jt(s,i,t){t&&t.disconnect(),"resize"===i&&function xs(s){Ee.delete(s),Ee.size||window.removeEventListener("resize",Bi)}(s)}function xe(s,i,t){const e=s.canvas,n=(0,u.L)(o=>{null!==s.ctx&&t(function bs(s,i){const t=fs[s.type]||s.type,{x:e,y:n}=(0,u.z)(s,i);return{type:t,chart:i,native:s,x:void 0!==e?e:null,y:void 0!==n?n:null}}(o,s))},s);return function ps(s,i,t){s.addEventListener(i,t,hn)}(e,i,n),n}class ys extends _e{acquireContext(i,t){const e=i&&i.getContext&&i.getContext("2d");return e&&e.canvas===i?(function gs(s,i){const t=s.style,e=s.getAttribute("height"),n=s.getAttribute("width");if(s[Bt]={initial:{height:e,width:n,style:{display:t.display,height:t.height,width:t.width}}},t.display=t.display||"block",t.boxSizing=t.boxSizing||"border-box",cn(n)){const o=(0,u.J)(s,"width");void 0!==o&&(s.width=o)}if(cn(e))if(""===s.style.height)s.height=s.width/(i||2);else{const o=(0,u.J)(s,"height");void 0!==o&&(s.height=o)}}(i,t),e):null}releaseContext(i){const t=i.canvas;if(!t[Bt])return!1;const e=t[Bt].initial;["height","width"].forEach(o=>{const r=e[o];(0,u.k)(r)?t.removeAttribute(o):t.setAttribute(o,r)});const n=e.style||{};return Object.keys(n).forEach(o=>{t.style[o]=n[o]}),t.width=t.width,delete t[Bt],!0}addEventListener(i,t,e){this.removeEventListener(i,t),(i.$proxies||(i.$proxies={}))[t]=({attach:_s,detach:Fi,resize:dn}[t]||xe)(i,t,e)}removeEventListener(i,t){const e=i.$proxies||(i.$proxies={}),n=e[t];n&&(({attach:jt,detach:jt,resize:jt}[t]||ms)(i,t,n),e[t]=void 0)}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(i,t,e,n){return(0,u.G)(i,t,e,n)}isAttached(i){const t=(0,u.I)(i);return!(!t||!t.isConnected)}}class wt{constructor(){(0,R.Z)(this,"x",void 0),(0,R.Z)(this,"y",void 0),(0,R.Z)(this,"active",!1),(0,R.Z)(this,"options",void 0),(0,R.Z)(this,"$animations",void 0)}tooltipPosition(i){const{x:t,y:e}=this.getProps(["x","y"],i);return{x:t,y:e}}hasValue(){return(0,u.x)(this.x)&&(0,u.x)(this.y)}getProps(i,t){const e=this.$animations;if(!t||!e)return this;const n={};return i.forEach(o=>{n[o]=e[o]&&e[o].active()?e[o]._to:this[o]}),n}}function ri(s,i,t,e,n){const o=(0,u.v)(e,0),r=Math.min((0,u.v)(n,s.length),s.length);let c,d,g,l=0;for(t=Math.ceil(t),n&&(c=n-e,t=c/Math.floor(c/t)),g=o;g<0;)l++,g=Math.round(o+l*t);for(d=Math.max(o,0);d"top"===i||"left"===i?s[i]+t:s[i]-t,gn=(s,i)=>Math.min(i||s,s);function pn(s,i){const t=[],e=s.length/i,n=s.length;let o=0;for(;or+l)))return c}function ve(s){return s.drawTicks?s.tickLength:0}function Hi(s,i){if(!s.display)return 0;const t=(0,u.a0)(s.font,i),e=(0,u.E)(s.padding);return((0,u.b)(s.text)?s.text.length:1)*t.lineHeight+e.height}function Ps(s,i,t){let e=(0,u.a1)(s);return(t&&"right"!==i||!t&&"right"===i)&&(e=(s=>"left"===s?"right":"right"===s?"left":s)(e)),e}class Nt extends wt{constructor(i){super(),this.id=i.id,this.type=i.type,this.options=void 0,this.ctx=i.ctx,this.chart=i.chart,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this._margins={left:0,right:0,top:0,bottom:0},this.maxWidth=void 0,this.maxHeight=void 0,this.paddingTop=void 0,this.paddingBottom=void 0,this.paddingLeft=void 0,this.paddingRight=void 0,this.axis=void 0,this.labelRotation=void 0,this.min=void 0,this.max=void 0,this._range=void 0,this.ticks=[],this._gridLineItems=null,this._labelItems=null,this._labelSizes=null,this._length=0,this._maxLength=0,this._longestTextCache={},this._startPixel=void 0,this._endPixel=void 0,this._reversePixels=!1,this._userMax=void 0,this._userMin=void 0,this._suggestedMax=void 0,this._suggestedMin=void 0,this._ticksLength=0,this._borderValue=0,this._cache={},this._dataLimitsCached=!1,this.$context=void 0}init(i){this.options=i.setContext(this.getContext()),this.axis=i.axis,this._userMin=this.parse(i.min),this._userMax=this.parse(i.max),this._suggestedMin=this.parse(i.suggestedMin),this._suggestedMax=this.parse(i.suggestedMax)}parse(i,t){return i}getUserBounds(){let{_userMin:i,_userMax:t,_suggestedMin:e,_suggestedMax:n}=this;return i=(0,u.O)(i,Number.POSITIVE_INFINITY),t=(0,u.O)(t,Number.NEGATIVE_INFINITY),e=(0,u.O)(e,Number.POSITIVE_INFINITY),n=(0,u.O)(n,Number.NEGATIVE_INFINITY),{min:(0,u.O)(i,e),max:(0,u.O)(t,n),minDefined:(0,u.g)(i),maxDefined:(0,u.g)(t)}}getMinMax(i){let r,{min:t,max:e,minDefined:n,maxDefined:o}=this.getUserBounds();if(n&&o)return{min:t,max:e};const l=this.getMatchingVisibleMetas();for(let c=0,d=l.length;ce?e:t,e=n&&t>e?t:e,{min:(0,u.O)(t,(0,u.O)(e,t)),max:(0,u.O)(e,(0,u.O)(t,e))}}getPadding(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}}getTicks(){return this.ticks}getLabels(){const i=this.chart.data;return this.options.labels||(this.isHorizontal()?i.xLabels:i.yLabels)||i.labels||[]}getLabelItems(i=this.chart.chartArea){return this._labelItems||(this._labelItems=this._computeLabelItems(i))}beforeLayout(){this._cache={},this._dataLimitsCached=!1}beforeUpdate(){(0,u.Q)(this.options.beforeUpdate,[this])}update(i,t,e){const{beginAtZero:n,grace:o,ticks:r}=this.options,l=r.sampleSize;this.beforeUpdate(),this.maxWidth=i,this.maxHeight=t,this._margins=e=Object.assign({left:0,right:0,top:0,bottom:0},e),this.ticks=null,this._labelSizes=null,this._gridLineItems=null,this._labelItems=null,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this._maxLength=this.isHorizontal()?this.width+e.left+e.right:this.height+e.top+e.bottom,this._dataLimitsCached||(this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this._range=(0,u.R)(this,o,n),this._dataLimitsCached=!0),this.beforeBuildTicks(),this.ticks=this.buildTicks()||[],this.afterBuildTicks();const c=ln)return function un(s,i,t,e){let r,n=0,o=t[0];for(e=Math.ceil(e),r=0;rn)return c}return Math.max(n,1)}(o,i,n);if(r>0){let m,x;const v=r>1?Math.round((c-l)/(r-1)):null;for(ri(i,d,g,(0,u.k)(v)?0:l-v,l),m=0,x=r-1;m=o||e<=1||!this.isHorizontal())return void(this.labelRotation=n);const g=this._getLabelSizes(),m=g.widest.width,x=g.highest.height,v=(0,u.S)(this.chart.width-m,0,this.maxWidth);l=i.offset?this.maxWidth/e:v/(e-1),m+6>l&&(l=v/(e-(i.offset?.5:1)),c=this.maxHeight-ve(i.grid)-t.padding-Hi(i.title,this.chart.options.font),d=Math.sqrt(m*m+x*x),r=(0,u.U)(Math.min(Math.asin((0,u.S)((g.highest.height+6)/l,-1,1)),Math.asin((0,u.S)(c/d,-1,1))-Math.asin((0,u.S)(x/d,-1,1)))),r=Math.max(n,Math.min(o,r))),this.labelRotation=r}afterCalculateLabelRotation(){(0,u.Q)(this.options.afterCalculateLabelRotation,[this])}afterAutoSkip(){}beforeFit(){(0,u.Q)(this.options.beforeFit,[this])}fit(){const i={width:0,height:0},{chart:t,options:{ticks:e,title:n,grid:o}}=this,r=this._isVisible(),l=this.isHorizontal();if(r){const c=Hi(n,t.options.font);if(l?(i.width=this.maxWidth,i.height=ve(o)+c):(i.height=this.maxHeight,i.width=ve(o)+c),e.display&&this.ticks.length){const{first:d,last:g,widest:m,highest:x}=this._getLabelSizes(),v=2*e.padding,S=(0,u.t)(this.labelRotation),P=Math.cos(S),O=Math.sin(S);l?i.height=Math.min(this.maxHeight,i.height+(e.mirror?0:O*m.width+P*x.height)+v):i.width=Math.min(this.maxWidth,i.width+(e.mirror?0:P*m.width+O*x.height)+v),this._calculatePadding(d,g,O,P)}}this._handleMargins(),l?(this.width=this._length=t.width-this._margins.left-this._margins.right,this.height=i.height):(this.width=i.width,this.height=this._length=t.height-this._margins.top-this._margins.bottom)}_calculatePadding(i,t,e,n){const{ticks:{align:o,padding:r},position:l}=this.options,c=0!==this.labelRotation,d="top"!==l&&"x"===this.axis;if(this.isHorizontal()){const g=this.getPixelForTick(0)-this.left,m=this.right-this.getPixelForTick(this.ticks.length-1);let x=0,v=0;c?d?(x=n*i.width,v=e*t.height):(x=e*i.height,v=n*t.width):"start"===o?v=t.width:"end"===o?x=i.width:"inner"!==o&&(x=i.width/2,v=t.width/2),this.paddingLeft=Math.max((x-g+r)*this.width/(this.width-g),0),this.paddingRight=Math.max((v-m+r)*this.width/(this.width-m),0)}else{let g=t.height/2,m=i.height/2;"start"===o?(g=0,m=i.height):"end"===o&&(g=t.height,m=0),this.paddingTop=g+r,this.paddingBottom=m+r}}_handleMargins(){this._margins&&(this._margins.left=Math.max(this.paddingLeft,this._margins.left),this._margins.top=Math.max(this.paddingTop,this._margins.top),this._margins.right=Math.max(this.paddingRight,this._margins.right),this._margins.bottom=Math.max(this.paddingBottom,this._margins.bottom))}afterFit(){(0,u.Q)(this.options.afterFit,[this])}isHorizontal(){const{axis:i,position:t}=this.options;return"top"===t||"bottom"===t||"x"===i}isFullSize(){return this.options.fullSize}_convertTicksToLabels(i){let t,e;for(this.beforeTickToLabelConversion(),this.generateTickLabels(i),t=0,e=i.length;t{const e=t.gc,n=e.length/2;let o;if(n>i){for(o=0;o({width:r[U]||0,height:l[U]||0});return{first:V(0),last:V(t-1),widest:V(H),highest:V(W),widths:r,heights:l}}getLabelForValue(i){return i}getPixelForValue(i,t){return NaN}getValueForPixel(i){}getPixelForTick(i){const t=this.ticks;return i<0||i>t.length-1?null:this.getPixelForValue(t[i].value)}getPixelForDecimal(i){this._reversePixels&&(i=1-i);const t=this._startPixel+i*this._length;return(0,u.W)(this._alignToPixels?(0,u.X)(this.chart,t,0):t)}getDecimalForPixel(i){const t=(i-this._startPixel)/this._length;return this._reversePixels?1-t:t}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){const{min:i,max:t}=this;return i<0&&t<0?t:i>0&&t>0?i:0}getContext(i){const t=this.ticks||[];if(i>=0&&il*n?l/e:c/n:c*n0}_computeGridLineItems(i){const t=this.axis,e=this.chart,n=this.options,{grid:o,position:r,border:l}=n,c=o.offset,d=this.isHorizontal(),m=this.ticks.length+(c?1:0),x=ve(o),v=[],S=l.setContext(this.getContext()),P=S.display?S.width:0,O=P/2,z=function(ut){return(0,u.X)(e,ut,P)};let A,B,j,L,H,W,V,U,tt,J,nt,Mt;if("top"===r)A=z(this.bottom),W=this.bottom-x,U=A-O,J=z(i.top)+O,Mt=i.bottom;else if("bottom"===r)A=z(this.top),J=i.top,Mt=z(i.bottom)-O,W=A+O,U=this.top+x;else if("left"===r)A=z(this.right),H=this.right-x,V=A-O,tt=z(i.left)+O,nt=i.right;else if("right"===r)A=z(this.left),tt=i.left,nt=z(i.right)-O,H=A+O,V=this.left+x;else if("x"===t){if("center"===r)A=z((i.top+i.bottom)/2+.5);else if((0,u.i)(r)){const ut=Object.keys(r)[0];A=z(this.chart.scales[ut].getPixelForValue(r[ut]))}J=i.top,Mt=i.bottom,W=A+O,U=W+x}else if("y"===t){if("center"===r)A=z((i.left+i.right)/2);else if((0,u.i)(r)){const ut=Object.keys(r)[0];A=z(this.chart.scales[ut].getPixelForValue(r[ut]))}H=A-O,V=H-x,tt=i.left,nt=i.right}const $t=(0,u.v)(n.ticks.maxTicksLimit,m),at=Math.max(1,Math.ceil(m/$t));for(B=0;Bo.value===i);return n>=0?t.setContext(this.getContext(n)).lineWidth:0}drawGrid(i){const t=this.options.grid,e=this.ctx,n=this._gridLineItems||(this._gridLineItems=this._computeGridLineItems(i));let o,r;const l=(c,d,g)=>{!g.width||!g.color||(e.save(),e.lineWidth=g.width,e.strokeStyle=g.color,e.setLineDash(g.borderDash||[]),e.lineDashOffset=g.borderDashOffset,e.beginPath(),e.moveTo(c.x,c.y),e.lineTo(d.x,d.y),e.stroke(),e.restore())};if(t.display)for(o=0,r=n.length;o{this.drawBackground(),this.drawGrid(o),this.drawTitle()}},{z:n,draw:()=>{this.drawBorder()}},{z:t,draw:o=>{this.drawLabels(o)}}]:[{z:t,draw:o=>{this.draw(o)}}]}getMatchingVisibleMetas(i){const t=this.chart.getSortedVisibleDatasetMetas(),e=this.axis+"AxisID",n=[];let o,r;for(o=0,r=t.length;o{const e=t.split("."),n=e.pop(),o=[s].concat(e).join("."),r=i[t].split("."),l=r.pop(),c=r.join(".");u.d.route(o,n,c,l)})}(i,s.defaultRoutes),s.descriptors&&u.d.describe(i,s.descriptors)}(i,r,e),this.override&&u.d.override(i.id,i.overrides)),r}get(i){return this.items[i]}unregister(i){const t=this.items,e=i.id,n=this.scope;e in t&&delete t[e],n&&e in u.d[n]&&(delete u.d[n][e],this.override&&delete u.a3[e])}}class Os{constructor(){this.controllers=new ci(It,"datasets",!0),this.elements=new ci(wt,"elements"),this.plugins=new ci(Object,"plugins"),this.scales=new ci(Nt,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...i){this._each("register",i)}remove(...i){this._each("unregister",i)}addControllers(...i){this._each("register",i,this.controllers)}addElements(...i){this._each("register",i,this.elements)}addPlugins(...i){this._each("register",i,this.plugins)}addScales(...i){this._each("register",i,this.scales)}getController(i){return this._get(i,this.controllers,"controller")}getElement(i){return this._get(i,this.elements,"element")}getPlugin(i){return this._get(i,this.plugins,"plugin")}getScale(i){return this._get(i,this.scales,"scale")}removeControllers(...i){this._each("unregister",i,this.controllers)}removeElements(...i){this._each("unregister",i,this.elements)}removePlugins(...i){this._each("unregister",i,this.plugins)}removeScales(...i){this._each("unregister",i,this.scales)}_each(i,t,e){[...t].forEach(n=>{const o=e||this._getRegistryForType(n);e||o.isForType(n)||o===this.plugins&&n.id?this._exec(i,o,n):(0,u.F)(n,r=>{const l=e||this._getRegistryForType(r);this._exec(i,l,r)})})}_exec(i,t,e){const n=(0,u.a5)(i);(0,u.Q)(e["before"+n],[],e),t[i](e),(0,u.Q)(e["after"+n],[],e)}_getRegistryForType(i){for(let t=0;to.filter(l=>!r.some(c=>l.plugin.id===c.plugin.id));this._notify(n(t,e),i,"stop"),this._notify(n(e,t),i,"start")}}function Vi(s,i){return i||!1!==s?!0===s?{}:s:null}function _n(s,{plugin:i,local:t},e,n){const o=s.pluginScopeKeys(i),r=s.getOptionScopes(e,o);return t&&i.defaults&&r.push(i.defaults),s.createResolver(r,n,[""],{scriptable:!1,indexable:!1,allKeys:!0})}function Re(s,i){return((i.datasets||{})[s]||{}).indexAxis||i.indexAxis||(u.d.datasets[s]||{}).indexAxis||"x"}function xn(s){if("x"===s||"y"===s||"r"===s)return s}function yn(s){return"top"===s||"bottom"===s?"x":"left"===s||"right"===s?"y":void 0}function di(s,...i){if(xn(s))return s;for(const t of i){const e=t.axis||yn(t.position)||s.length>1&&xn(s[0].toLowerCase());if(e)return e}throw new Error(`Cannot determine type of '${s}' axis. Please provide 'axis' or 'position' option.`)}function ui(s,i,t){if(t[i+"AxisID"]===s)return{axis:i}}function vn(s){const i=s.options||(s.options={});i.plugins=(0,u.v)(i.plugins,{}),i.scales=function Ts(s,i){const t=u.a3[s.type]||{scales:{}},e=i.scales||{},n=Re(s.type,i),o=Object.create(null);return Object.keys(e).forEach(r=>{const l=e[r];if(!(0,u.i)(l))return console.error(`Invalid scale configuration for scale: ${r}`);if(l._proxy)return console.warn(`Ignoring resolver passed as options for scale: ${r}`);const c=di(r,l,function As(s,i){if(i.data&&i.data.datasets){const t=i.data.datasets.filter(e=>e.xAxisID===s||e.yAxisID===s);if(t.length)return ui(s,"x",t[0])||ui(s,"y",t[0])}return{}}(r,s),u.d.scales[l.type]),d=function Wi(s,i){return s===i?"_index_":"_value_"}(c,n),g=t.scales||{};o[r]=(0,u.ab)(Object.create(null),[{axis:c},l,g[c],g[d]])}),s.data.datasets.forEach(r=>{const l=r.type||s.type,c=r.indexAxis||Re(l,i),g=(u.a3[l]||{}).scales||{};Object.keys(g).forEach(m=>{const x=function Me(s,i){let t=s;return"_index_"===s?t=i:"_value_"===s&&(t="x"===i?"y":"x"),t}(m,c),v=r[x+"AxisID"]||x;o[v]=o[v]||Object.create(null),(0,u.ab)(o[v],[{axis:x},e[v],g[m]])})}),Object.keys(o).forEach(r=>{const l=o[r];(0,u.ab)(l,[u.d.scales[l.type],u.d.scale])}),o}(s,i)}function Mn(s){return(s=s||{}).datasets=s.datasets||[],s.labels=s.labels||[],s}const Ie=new Map,wn=new Set;function fi(s,i){let t=Ie.get(s);return t||(t=i(),Ie.set(s,t),wn.add(t)),t}const Fe=(s,i,t)=>{const e=(0,u.f)(i,t);void 0!==e&&s.add(e)};class po{constructor(i){this._config=function Sn(s){return(s=s||{}).data=Mn(s.data),vn(s),s}(i),this._scopeCache=new Map,this._resolverCache=new Map}get platform(){return this._config.platform}get type(){return this._config.type}set type(i){this._config.type=i}get data(){return this._config.data}set data(i){this._config.data=Mn(i)}get options(){return this._config.options}set options(i){this._config.options=i}get plugins(){return this._config.plugins}update(){const i=this._config;this.clearCache(),vn(i)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(i){return fi(i,()=>[[`datasets.${i}`,""]])}datasetAnimationScopeKeys(i,t){return fi(`${i}.transition.${t}`,()=>[[`datasets.${i}.transitions.${t}`,`transitions.${t}`],[`datasets.${i}`,""]])}datasetElementScopeKeys(i,t){return fi(`${i}-${t}`,()=>[[`datasets.${i}.elements.${t}`,`datasets.${i}`,`elements.${t}`,""]])}pluginScopeKeys(i){const t=i.id;return fi(`${this.type}-plugin-${t}`,()=>[[`plugins.${t}`,...i.additionalOptionScopes||[]]])}_cachedScopes(i,t){const e=this._scopeCache;let n=e.get(i);return(!n||t)&&(n=new Map,e.set(i,n)),n}getOptionScopes(i,t,e){const{options:n,type:o}=this,r=this._cachedScopes(i,e),l=r.get(t);if(l)return l;const c=new Set;t.forEach(g=>{i&&(c.add(i),g.forEach(m=>Fe(c,i,m))),g.forEach(m=>Fe(c,n,m)),g.forEach(m=>Fe(c,u.a3[o]||{},m)),g.forEach(m=>Fe(c,u.d,m)),g.forEach(m=>Fe(c,u.a6,m))});const d=Array.from(c);return 0===d.length&&d.push(Object.create(null)),wn.has(t)&&r.set(t,d),d}chartOptionScopes(){const{options:i,type:t}=this;return[i,u.a3[t]||{},u.d.datasets[t]||{},{type:t},u.d,u.a6]}resolveNamedOptions(i,t,e,n=[""]){const o={$shared:!0},{resolver:r,subPrefixes:l}=kn(this._resolverCache,i,n);let c=r;if(function zs(s,i){const{isScriptable:t,isIndexable:e}=(0,u.aa)(s);for(const n of i){const o=t(n),r=e(n),l=(r||o)&&s[n];if(o&&((0,u.a7)(l)||Pn(l))||r&&(0,u.b)(l))return!0}return!1}(r,t)){o.$shared=!1,e=(0,u.a7)(e)?e():e;const d=this.createResolver(i,e,l);c=(0,u.a8)(r,e,d)}for(const d of t)o[d]=c[d];return o}createResolver(i,t,e=[""],n){const{resolver:o}=kn(this._resolverCache,i,e);return(0,u.i)(t)?(0,u.a8)(o,t,void 0,n):o}}function kn(s,i,t){let e=s.get(i);e||(e=new Map,s.set(i,e));const n=t.join();let o=e.get(n);return o||(o={resolver:(0,u.a9)(i,t),subPrefixes:t.filter(l=>!l.toLowerCase().includes("hover"))},e.set(n,o)),o}const Pn=s=>(0,u.i)(s)&&Object.getOwnPropertyNames(s).reduce((i,t)=>i||(0,u.a7)(s[t]),!1),Cn=["top","bottom","left","right","chartArea"];function On(s,i){return"top"===s||"bottom"===s||-1===Cn.indexOf(s)&&"x"===i}function Dn(s,i){return function(t,e){return t[s]===e[s]?t[i]-e[i]:t[s]-e[s]}}function An(s){const i=s.chart,t=i.options.animation;i.notifyPlugins("afterRender"),(0,u.Q)(t&&t.onComplete,[s],i)}function Be(s){const i=s.chart,t=i.options.animation;(0,u.Q)(t&&t.onProgress,[s],i)}function gi(s){return(0,u.M)()&&"string"==typeof s?s=document.getElementById(s):s&&s.length&&(s=s[0]),s&&s.canvas&&(s=s.canvas),s}const He={},Yi=s=>{const i=gi(s);return Object.values(He).filter(t=>t.canvas===i).pop()};function Tn(s,i,t){const e=Object.keys(s);for(const n of e){const o=+n;if(o>=i){const r=s[n];delete s[n],(t>0||o>i)&&(s[o+t]=r)}}}let zn=(()=>{class s{static register(...t){Vt.add(...t),je()}static unregister(...t){Vt.remove(...t),je()}constructor(t,e){const n=this.config=new po(e),o=gi(t),r=Yi(o);if(r)throw new Error("Canvas is already in use. Chart with ID '"+r.id+"' must be destroyed before the canvas with ID '"+r.canvas.id+"' can be reused.");const l=n.createResolver(n.chartOptionScopes(),this.getContext());this.platform=new(n.platform||function vs(s){return!(0,u.M)()||typeof OffscreenCanvas<"u"&&s instanceof OffscreenCanvas?Ii:ys}(o)),this.platform.updateConfig(n);const c=this.platform.acquireContext(o,l.aspectRatio),d=c&&c.canvas,g=d&&d.height,m=d&&d.width;this.id=(0,u.ac)(),this.ctx=c,this.canvas=d,this.width=m,this.height=g,this._options=l,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this._plugins=new mn,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=(0,u.ad)(x=>this.update(x),l.resizeDelay||0),this._dataChanges=[],He[this.id]=this,c&&d?(Ot.listen(this,"complete",An),Ot.listen(this,"progress",Be),this._initialize(),this.attached&&this.update()):console.error("Failed to create chart: can't acquire context from the given item")}get aspectRatio(){const{options:{aspectRatio:t,maintainAspectRatio:e},width:n,height:o,_aspectRatio:r}=this;return(0,u.k)(t)?e&&r?r:o?n/o:null:t}get data(){return this.config.data}set data(t){this.config.data=t}get options(){return this._options}set options(t){this.config.options=t}get registry(){return Vt}_initialize(){return this.notifyPlugins("beforeInit"),this.options.responsive?this.resize():(0,u.ae)(this,this.options.devicePixelRatio),this.bindEvents(),this.notifyPlugins("afterInit"),this}clear(){return(0,u.af)(this.canvas,this.ctx),this}stop(){return Ot.stop(this),this}resize(t,e){Ot.running(this)?this._resizeBeforeDraw={width:t,height:e}:this._resize(t,e)}_resize(t,e){const n=this.options,l=this.platform.getMaximumSize(this.canvas,t,e,n.maintainAspectRatio&&this.aspectRatio),c=n.devicePixelRatio||this.platform.getDevicePixelRatio(),d=this.width?"resize":"attach";this.width=l.width,this.height=l.height,this._aspectRatio=this.aspectRatio,(0,u.ae)(this,c,!0)&&(this.notifyPlugins("resize",{size:l}),(0,u.Q)(n.onResize,[this,l],this),this.attached&&this._doResize(d)&&this.render())}ensureScalesHaveIDs(){(0,u.F)(this.options.scales||{},(n,o)=>{n.id=o})}buildOrUpdateScales(){const t=this.options,e=t.scales,n=this.scales,o=Object.keys(n).reduce((l,c)=>(l[c]=!1,l),{});let r=[];e&&(r=r.concat(Object.keys(e).map(l=>{const c=e[l],d=di(l,c),g="r"===d,m="x"===d;return{options:c,dposition:g?"chartArea":m?"bottom":"left",dtype:g?"radialLinear":m?"category":"linear"}}))),(0,u.F)(r,l=>{const c=l.options,d=c.id,g=di(d,c),m=(0,u.v)(c.type,l.dtype);(void 0===c.position||On(c.position,g)!==On(l.dposition))&&(c.position=l.dposition),o[d]=!0;let x=null;d in n&&n[d].type===m?x=n[d]:(x=new(Vt.getScale(m))({id:d,type:m,ctx:this.ctx,chart:this}),n[x.id]=x),x.init(c,t)}),(0,u.F)(o,(l,c)=>{l||delete n[c]}),(0,u.F)(n,l=>{gt.configure(this,l,l.options),gt.addBox(this,l)})}_updateMetasets(){const t=this._metasets,e=this.data.datasets.length,n=t.length;if(t.sort((o,r)=>o.index-r.index),n>e){for(let o=e;oe.length&&delete this._stacks,t.forEach((n,o)=>{0===e.filter(r=>r===n._dataset).length&&this._destroyDatasetMeta(o)})}buildOrUpdateControllers(){const t=[],e=this.data.datasets;let n,o;for(this._removeUnreferencedMetasets(),n=0,o=e.length;n{this.getDatasetMeta(e).controller.reset()},this)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(t){const e=this.config;e.update();const n=this._options=e.createResolver(e.chartOptionScopes(),this.getContext()),o=this._animationsDisabled=!n.animation;if(this._updateScales(),this._checkEventBindings(),this._updateHiddenIndices(),this._plugins.invalidate(),!1===this.notifyPlugins("beforeUpdate",{mode:t,cancelable:!0}))return;const r=this.buildOrUpdateControllers();this.notifyPlugins("beforeElementsUpdate");let l=0;for(let g=0,m=this.data.datasets.length;g{g.reset()}),this._updateDatasets(t),this.notifyPlugins("afterUpdate",{mode:t}),this._layers.sort(Dn("z","_idx"));const{_active:c,_lastEvent:d}=this;d?this._eventHandler(d,!0):c.length&&this._updateHoverStyles(c,c,!0),this.render()}_updateScales(){(0,u.F)(this.scales,t=>{gt.removeBox(this,t)}),this.ensureScalesHaveIDs(),this.buildOrUpdateScales()}_checkEventBindings(){const t=this.options,e=new Set(Object.keys(this._listeners)),n=new Set(t.events);(!(0,u.ag)(e,n)||!!this._responsiveListeners!==t.responsive)&&(this.unbindEvents(),this.bindEvents())}_updateHiddenIndices(){const{_hiddenIndices:t}=this,e=this._getUniformDataChanges()||[];for(const{method:n,start:o,count:r}of e)Tn(t,o,"_removeElements"===n?-r:r)}_getUniformDataChanges(){const t=this._dataChanges;if(!t||!t.length)return;this._dataChanges=[];const e=this.data.datasets.length,n=r=>new Set(t.filter(l=>l[0]===r).map((l,c)=>c+","+l.splice(1).join(","))),o=n(0);for(let r=1;rr.split(",")).map(r=>({method:r[1],start:+r[2],count:+r[3]}))}_updateLayout(t){if(!1===this.notifyPlugins("beforeLayout",{cancelable:!0}))return;gt.update(this,this.width,this.height,t);const e=this.chartArea,n=e.width<=0||e.height<=0;this._layers=[],(0,u.F)(this.boxes,o=>{n&&"chartArea"===o.position||(o.configure&&o.configure(),this._layers.push(...o._layers()))},this),this._layers.forEach((o,r)=>{o._idx=r}),this.notifyPlugins("afterLayout")}_updateDatasets(t){if(!1!==this.notifyPlugins("beforeDatasetsUpdate",{mode:t,cancelable:!0})){for(let e=0,n=this.data.datasets.length;e=0;--e)this._drawDataset(t[e]);this.notifyPlugins("afterDatasetsDraw")}_drawDataset(t){const e=this.ctx,n=t._clip,o=!n.disabled,r=function Rs(s){const{xScale:i,yScale:t}=s;if(i&&t)return{left:i.left,right:i.right,top:t.top,bottom:t.bottom}}(t)||this.chartArea,l={meta:t,index:t.index,cancelable:!0};!1!==this.notifyPlugins("beforeDatasetDraw",l)&&(o&&(0,u.Y)(e,{left:!1===n.left?0:r.left-n.left,right:!1===n.right?this.width:r.right+n.right,top:!1===n.top?0:r.top-n.top,bottom:!1===n.bottom?this.height:r.bottom+n.bottom}),t.controller.draw(),o&&(0,u.$)(e),l.cancelable=!1,this.notifyPlugins("afterDatasetDraw",l))}isPointInArea(t){return(0,u.C)(t,this.chartArea,this._minPadding)}getElementsAtEventForMode(t,e,n,o){const r=fo.modes[e];return"function"==typeof r?r(this,t,n,o):[]}getDatasetMeta(t){const e=this.data.datasets[t],n=this._metasets;let o=n.filter(r=>r&&r._dataset===e).pop();return o||(o={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e&&e.order||0,index:t,_dataset:e,_parsed:[],_sorted:!1},n.push(o)),o}getContext(){return this.$context||(this.$context=(0,u.j)(null,{chart:this,type:"chart"}))}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(t){const e=this.data.datasets[t];if(!e)return!1;const n=this.getDatasetMeta(t);return"boolean"==typeof n.hidden?!n.hidden:!e.hidden}setDatasetVisibility(t,e){this.getDatasetMeta(t).hidden=!e}toggleDataVisibility(t){this._hiddenIndices[t]=!this._hiddenIndices[t]}getDataVisibility(t){return!this._hiddenIndices[t]}_updateVisibility(t,e,n){const o=n?"show":"hide",r=this.getDatasetMeta(t),l=r.controller._resolveAnimations(void 0,o);(0,u.h)(e)?(r.data[e].hidden=!n,this.update()):(this.setDatasetVisibility(t,n),l.update(r,{visible:n}),this.update(c=>c.datasetIndex===t?o:void 0))}hide(t,e){this._updateVisibility(t,e,!1)}show(t,e){this._updateVisibility(t,e,!0)}_destroyDatasetMeta(t){const e=this._metasets[t];e&&e.controller&&e.controller._destroy(),delete this._metasets[t]}_stop(){let t,e;for(this.stop(),Ot.remove(this),t=0,e=this.data.datasets.length;t{e.addEventListener(this,r,l),t[r]=l},o=(r,l,c)=>{r.offsetX=l,r.offsetY=c,this._eventHandler(r)};(0,u.F)(this.options.events,r=>n(r,o))}bindResponsiveEvents(){this._responsiveListeners||(this._responsiveListeners={});const t=this._responsiveListeners,e=this.platform,n=(d,g)=>{e.addEventListener(this,d,g),t[d]=g},o=(d,g)=>{t[d]&&(e.removeEventListener(this,d,g),delete t[d])},r=(d,g)=>{this.canvas&&this.resize(d,g)};let l;const c=()=>{o("attach",c),this.attached=!0,this.resize(),n("resize",r),n("detach",l)};l=()=>{this.attached=!1,o("resize",r),this._stop(),this._resize(0,0),n("attach",c)},e.isAttached(this.canvas)?c():l()}unbindEvents(){(0,u.F)(this._listeners,(t,e)=>{this.platform.removeEventListener(this,e,t)}),this._listeners={},(0,u.F)(this._responsiveListeners,(t,e)=>{this.platform.removeEventListener(this,e,t)}),this._responsiveListeners=void 0}updateHoverStyle(t,e,n){const o=n?"set":"remove";let r,l,c,d;for("dataset"===e&&(r=this.getDatasetMeta(t[0].datasetIndex),r.controller["_"+o+"DatasetHoverStyle"]()),c=0,d=t.length;c{const c=this.getDatasetMeta(r);if(!c)throw new Error("No dataset found at index "+r);return{datasetIndex:r,element:c.data[l],index:l}});!(0,u.ah)(n,e)&&(this._active=n,this._lastEvent=null,this._updateHoverStyles(n,e))}notifyPlugins(t,e,n){return this._plugins.notify(this,t,e,n)}isPluginEnabled(t){return 1===this._plugins._cache.filter(e=>e.plugin.id===t).length}_updateHoverStyles(t,e,n){const o=this.options.hover,r=(d,g)=>d.filter(m=>!g.some(x=>m.datasetIndex===x.datasetIndex&&m.index===x.index)),l=r(e,t),c=n?t:r(t,e);l.length&&this.updateHoverStyle(l,o.mode,!1),c.length&&o.mode&&this.updateHoverStyle(c,o.mode,!0)}_eventHandler(t,e){const n={event:t,replay:e,cancelable:!0,inChartArea:this.isPointInArea(t)},o=l=>(l.options.events||this.options.events).includes(t.native.type);if(!1===this.notifyPlugins("beforeEvent",n,o))return;const r=this._handleEvent(t,e,n.inChartArea);return n.cancelable=!1,this.notifyPlugins("afterEvent",n,o),(r||n.changed)&&this.render(),this}_handleEvent(t,e,n){const{_active:o=[],options:r}=this,c=this._getActiveElements(t,o,n,e),d=(0,u.ai)(t),g=function Es(s,i,t,e){return t&&"mouseout"!==s.type?e?i:s:null}(t,this._lastEvent,n,d);n&&(this._lastEvent=null,(0,u.Q)(r.onHover,[t,c,this],this),d&&(0,u.Q)(r.onClick,[t,c,this],this));const m=!(0,u.ah)(c,o);return(m||e)&&(this._active=c,this._updateHoverStyles(c,o,e)),this._lastEvent=g,m}_getActiveElements(t,e,n,o){if("mouseout"===t.type)return[];if(!n)return e;const r=this.options.hover;return this.getElementsAtEventForMode(t,r.mode,r,o)}}return(0,R.Z)(s,"defaults",u.d),(0,R.Z)(s,"instances",He),(0,R.Z)(s,"overrides",u.a3),(0,R.Z)(s,"registry",Vt),(0,R.Z)(s,"version","4.3.3"),(0,R.Z)(s,"getChart",Yi),s})();function je(){return(0,u.F)(zn.instances,s=>s._plugins.invalidate())}function Se(s,i,t,e){return{x:t+s*Math.cos(i),y:e+s*Math.sin(i)}}function le(s,i,t,e,n,o){const{x:r,y:l,startAngle:c,pixelMargin:d,innerRadius:g}=i,m=Math.max(i.outerRadius+e+t-d,0),x=g>0?g+e+t+d:0;let v=0;const S=n-c;if(e){const pt=((g>0?g-e:0)+(m>0?m-e:0))/2;v=(S-(0!==pt?S*pt/(pt+e):S))/2}const O=(S-Math.max(.001,S*m-t/u.P)/m)/2,z=c+O+v,A=n-O-v,{outerStart:B,outerEnd:j,innerStart:L,innerEnd:H}=function Ln(s,i,t,e){const n=function ae(s){return(0,u.ak)(s,["outerStart","outerEnd","innerStart","innerEnd"])}(s.options.borderRadius),o=(t-i)/2,r=Math.min(o,e*i/2),l=c=>{const d=(t-Math.min(o,c))*e/2;return(0,u.S)(c,0,Math.min(o,d))};return{outerStart:l(n.outerStart),outerEnd:l(n.outerEnd),innerStart:(0,u.S)(n.innerStart,0,r),innerEnd:(0,u.S)(n.innerEnd,0,r)}}(i,x,m,A-z),W=m-B,V=m-j,U=z+B/W,tt=A-j/V,J=x+L,nt=x+H,Mt=z+L/J,$t=A-H/nt;if(s.beginPath(),o){const at=(U+tt)/2;if(s.arc(r,l,m,U,at),s.arc(r,l,m,at,tt),j>0){const Ct=Se(V,tt,r,l);s.arc(Ct.x,Ct.y,j,tt,A+u.H)}const ut=Se(nt,A,r,l);if(s.lineTo(ut.x,ut.y),H>0){const Ct=Se(nt,$t,r,l);s.arc(Ct.x,Ct.y,H,A+u.H,$t+Math.PI)}const pt=(A-H/x+(z+L/x))/2;if(s.arc(r,l,x,A-H/x,pt,!0),s.arc(r,l,x,pt,z+L/x,!0),L>0){const Ct=Se(J,Mt,r,l);s.arc(Ct.x,Ct.y,L,Mt+Math.PI,z-u.H)}const he=Se(W,z,r,l);if(s.lineTo(he.x,he.y),B>0){const Ct=Se(W,U,r,l);s.arc(Ct.x,Ct.y,B,z-u.H,U)}}else{s.moveTo(r,l);const at=Math.cos(U)*m+r,ut=Math.sin(U)*m+l;s.lineTo(at,ut);const pt=Math.cos(tt)*m+r,he=Math.sin(tt)*m+l;s.lineTo(pt,he)}s.closePath()}class we extends wt{constructor(i){super(),(0,R.Z)(this,"circumference",void 0),(0,R.Z)(this,"endAngle",void 0),(0,R.Z)(this,"fullCircles",void 0),(0,R.Z)(this,"innerRadius",void 0),(0,R.Z)(this,"outerRadius",void 0),(0,R.Z)(this,"pixelMargin",void 0),(0,R.Z)(this,"startAngle",void 0),this.options=void 0,this.circumference=void 0,this.startAngle=void 0,this.endAngle=void 0,this.innerRadius=void 0,this.outerRadius=void 0,this.pixelMargin=0,this.fullCircles=0,i&&Object.assign(this,i)}inRange(i,t,e){const n=this.getProps(["x","y"],e),{angle:o,distance:r}=(0,u.D)(n,{x:i,y:t}),{startAngle:l,endAngle:c,innerRadius:d,outerRadius:g,circumference:m}=this.getProps(["startAngle","endAngle","innerRadius","outerRadius","circumference"],e),x=(this.options.spacing+this.options.borderWidth)/2,S=(0,u.v)(m,c-l)>=u.T||(0,u.p)(o,l,c),P=(0,u.aj)(r,d+x,g+x);return S&&P}getCenterPoint(i){const{x:t,y:e,startAngle:n,endAngle:o,innerRadius:r,outerRadius:l}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius"],i),{offset:c,spacing:d}=this.options,g=(n+o)/2,m=(r+l+d+c)/2;return{x:t+Math.cos(g)*m,y:e+Math.sin(g)*m}}tooltipPosition(i){return this.getCenterPoint(i)}draw(i){const{options:t,circumference:e}=this,n=(t.offset||0)/4,o=(t.spacing||0)/2,r=t.circular;if(this.pixelMargin="inner"===t.borderAlign?.33:0,this.fullCircles=e>u.T?Math.floor(e/u.T):0,0===e||this.innerRadius<0||this.outerRadius<0)return;i.save();const l=(this.startAngle+this.endAngle)/2;i.translate(Math.cos(l)*n,Math.sin(l)*n);const d=n*(1-Math.sin(Math.min(u.P,e||0)));i.fillStyle=t.backgroundColor,i.strokeStyle=t.borderColor,function En(s,i,t,e,n){const{fullCircles:o,startAngle:r,circumference:l}=i;let c=i.endAngle;if(o){le(s,i,t,e,c,n);for(let d=0;dn?(d=n/c,s.arc(o,r,c,t+d,e-d,!0)):s.arc(o,r,n,t+u.H,e-u.H),s.closePath(),s.clip()}(s,i,S),o||(le(s,i,t,e,S,n),s.stroke())}(i,this,d,o,r),i.restore()}}function Rn(s,i,t=i){s.lineCap=(0,u.v)(t.borderCapStyle,i.borderCapStyle),s.setLineDash((0,u.v)(t.borderDash,i.borderDash)),s.lineDashOffset=(0,u.v)(t.borderDashOffset,i.borderDashOffset),s.lineJoin=(0,u.v)(t.borderJoinStyle,i.borderJoinStyle),s.lineWidth=(0,u.v)(t.borderWidth,i.borderWidth),s.strokeStyle=(0,u.v)(t.borderColor,i.borderColor)}function In(s,i,t){s.lineTo(t.x,t.y)}function Zi(s,i,t={}){const e=s.length,{start:n=0,end:o=e-1}=t,{start:r,end:l}=i,c=Math.max(n,r),d=Math.min(o,l);return{count:e,start:c,loop:i.loop,ilen:dl&&o>l)?e+d-c:d-c}}function pi(s,i,t,e){const{points:n,options:o}=i,{count:r,start:l,loop:c,ilen:d}=Zi(n,t,e),g=function Is(s){return s.stepped?u.ar:s.tension||"monotone"===s.cubicInterpolationMode?u.as:In}(o);let v,S,P,{move:m=!0,reverse:x}=e||{};for(v=0;v<=d;++v)S=n[(l+(x?d-v:v))%r],!S.skip&&(m?(s.moveTo(S.x,S.y),m=!1):g(s,P,S,x,o.stepped),P=S);return c&&(S=n[(l+(x?d:0))%r],g(s,P,S,x,o.stepped)),!!c}function Fn(s,i,t,e){const n=i.points,{count:o,start:r,ilen:l}=Zi(n,t,e),{move:c=!0,reverse:d}=e||{};let x,v,S,P,O,z,g=0,m=0;const A=j=>(r+(d?l-j:j))%o,B=()=>{P!==O&&(s.lineTo(g,O),s.lineTo(g,P),s.lineTo(g,z))};for(c&&(v=n[A(0)],s.moveTo(v.x,v.y)),x=0;x<=l;++x){if(v=n[A(x)],v.skip)continue;const j=v.x,L=v.y,H=0|j;H===S?(LO&&(O=L),g=(m*g+j)/++m):(B(),s.lineTo(j,L),S=H,m=0,P=O=L),z=L}B()}function mi(s){const i=s.options;return s._decimated||s._loop||i.tension||"monotone"===i.cubicInterpolationMode||i.stepped||i.borderDash&&i.borderDash.length?pi:Fn}(0,R.Z)(we,"id","arc"),(0,R.Z)(we,"defaults",{borderAlign:"center",borderColor:"#fff",borderDash:[],borderDashOffset:0,borderJoinStyle:void 0,borderRadius:0,borderWidth:2,offset:0,spacing:0,angle:void 0,circular:!0}),(0,R.Z)(we,"defaultRoutes",{backgroundColor:"backgroundColor"}),(0,R.Z)(we,"descriptors",{_scriptable:!0,_indexable:s=>"borderDash"!==s});const Bs="function"==typeof Path2D;let bi=(()=>{class s extends wt{constructor(t){super(),this.animated=!0,this.options=void 0,this._chart=void 0,this._loop=void 0,this._fullLoop=void 0,this._path=void 0,this._points=void 0,this._segments=void 0,this._decimated=!1,this._pointsUpdated=!1,this._datasetIndex=void 0,t&&Object.assign(this,t)}updateControlPoints(t,e){const n=this.options;!n.tension&&"monotone"!==n.cubicInterpolationMode||n.stepped||this._pointsUpdated||((0,u.al)(this._points,n,t,n.spanGaps?this._loop:this._fullLoop,e),this._pointsUpdated=!0)}set points(t){this._points=t,delete this._segments,delete this._path,this._pointsUpdated=!1}get points(){return this._points}get segments(){return this._segments||(this._segments=(0,u.am)(this,this.options.segment))}first(){const t=this.segments;return t.length&&this.points[t[0].start]}last(){const t=this.segments,n=t.length;return n&&this.points[t[n-1].end]}interpolate(t,e){const n=this.options,o=t[e],r=this.points,l=(0,u.an)(this,{property:e,start:o,end:o});if(!l.length)return;const c=[],d=function Fs(s){return s.stepped?u.ao:s.tension||"monotone"===s.cubicInterpolationMode?u.ap:u.aq}(n);let g,m;for(g=0,m=l.length;g"borderDash"!==i&&"fill"!==i}),s})();function Kt(s,i,t,e){const n=s.options,{[t]:o}=s.getProps([t],e);return Math.abs(i-o){class s extends wt{constructor(t){super(),(0,R.Z)(this,"parsed",void 0),(0,R.Z)(this,"skip",void 0),(0,R.Z)(this,"stop",void 0),this.options=void 0,this.parsed=void 0,this.skip=void 0,this.stop=void 0,t&&Object.assign(this,t)}inRange(t,e,n){const o=this.options,{x:r,y:l}=this.getProps(["x","y"],n);return Math.pow(t-r,2)+Math.pow(e-l,2)s.replace("rgb(","rgba(").replace(")",", 0.5)"));function qt(s){return Qi[s%Qi.length]}function Nn(s){return jn[s%jn.length]}function Vn(s){let i;for(i in s)if(s[i].borderColor||s[i].backgroundColor)return!0;return!1}var Xs={id:"colors",defaults:{enabled:!0,forceOverride:!1},beforeLayout(s,i,t){if(!t.enabled)return;const{data:{datasets:e},options:n}=s.config,{elements:o}=n;if(!t.forceOverride&&(Vn(e)||function Zs(s){return s&&(s.borderColor||s.backgroundColor)}(n)||o&&Vn(o)))return;const r=function vi(s){let i=0;return(t,e)=>{const n=s.getDatasetMeta(e).controller;n instanceof zi?i=function $s(s,i){return s.backgroundColor=s.data.map(()=>qt(i++)),i}(t,i):n instanceof _t?i=function Us(s,i){return s.backgroundColor=s.data.map(()=>Nn(i++)),i}(t,i):n&&(i=function Ys(s,i){return s.borderColor=qt(i),s.backgroundColor=Nn(i),++i}(t,i))}}(s);e.forEach(r)}};function Wn(s){if(s._decimated){const i=s._data;delete s._decimated,delete s._data,Object.defineProperty(s,"data",{configurable:!0,enumerable:!0,writable:!0,value:i})}}function Yn(s){s.data.datasets.forEach(i=>{Wn(i)})}var rt={id:"decimation",defaults:{algorithm:"min-max",enabled:!1},beforeElementsUpdate:(s,i,t)=>{if(!t.enabled)return void Yn(s);const e=s.width;s.data.datasets.forEach((n,o)=>{const{_data:r,indexAxis:l}=n,c=s.getDatasetMeta(o),d=r||n.data;if("y"===(0,u.a)([l,s.options.indexAxis])||!c.controller.supportsDecimation)return;const g=s.scales[c.xAxisID];if("linear"!==g.type&&"time"!==g.type||s.options.parsing)return;let S,{start:m,count:x}=function Gs(s,i){const t=i.length;let n,e=0;const{iScale:o}=s,{min:r,max:l,minDefined:c,maxDefined:d}=o.getUserBounds();return c&&(e=(0,u.S)((0,u.B)(i,o.axis,r).lo,0,t-1)),n=d?(0,u.S)((0,u.B)(i,o.axis,l).hi+1,e,t)-e:t-e,{start:e,count:n}}(c,d);if(x<=(t.threshold||4*e))Wn(n);else{switch((0,u.k)(r)&&(n._data=d,delete n.data,Object.defineProperty(n,"data",{configurable:!0,enumerable:!0,get:function(){return this._decimated},set:function(P){this._data=P}})),t.algorithm){case"lttb":S=function Ks(s,i,t,e,n){const o=n.samples||e;if(o>=t)return s.slice(i,i+t);const r=[],l=(t-2)/(o-2);let c=0;const d=i+t-1;let m,x,v,S,P,g=i;for(r[c++]=s[g],m=0;mv&&(v=S,x=s[A],P=A);r[c++]=x,g=P}return r[c++]=s[d],r}(d,m,x,e,t);break;case"min-max":S=function ce(s,i,t,e){let r,l,c,d,g,m,x,v,S,P,n=0,o=0;const O=[],A=s[i].x,j=s[i+t-1].x-A;for(r=i;rP&&(P=d,x=r),n=(o*n+l.x)/++o;else{const H=r-1;if(!(0,u.k)(m)&&!(0,u.k)(x)){const W=Math.min(m,x),V=Math.max(m,x);W!==v&&W!==H&&O.push({...s[W],x:n}),V!==v&&V!==H&&O.push({...s[V],x:n})}r>0&&H!==v&&O.push(s[H]),O.push(l),g=L,o=0,S=P=d,m=x=v=r}}return O}(d,m,x,e);break;default:throw new Error(`Unsupported decimation algorithm '${t.algorithm}'`)}n._decimated=S}})},destroy(s){Yn(s)}};function qi(s,i,t,e){if(e)return;let n=i[s],o=t[s];return"angle"===s&&(n=(0,u.ay)(n),o=(0,u.ay)(o)),{property:s,start:n,end:o}}function Mi(s,i,t){for(;i>s;i--){const e=t[i];if(!isNaN(e.x)&&!isNaN(e.y))break}return i}function Si(s,i,t,e){return s&&i?e(s[t],i[t]):s?s[t]:i?i[t]:0}function ke(s,i){let t=[],e=!1;return(0,u.b)(s)?(e=!0,t=s):t=function qs(s,i){const{x:t=null,y:e=null}=s||{},n=i.points,o=[];return i.segments.forEach(({start:r,end:l})=>{l=Mi(r,l,n);const c=n[r],d=n[l];null!==e?(o.push({x:c.x,y:e}),o.push({x:d.x,y:e})):null!==t&&(o.push({x:t,y:c.y}),o.push({x:t,y:d.y}))}),o}(s,i),t.length?new bi({points:t,options:{tension:0},_loop:e,_fullLoop:e}):null}function Ji(s){return s&&!1!==s.fill}function tn(s,i,t){let n=s[i].fill;const o=[i];let r;if(!t)return n;for(;!1!==n&&-1===o.indexOf(n);){if(!(0,u.g)(n))return n;if(r=s[n],!r)return!1;if(r.visible)return n;o.push(n),n=r.fill}return!1}function Js(s,i,t){const e=function Xn(s){const i=s.options,t=i.fill;let e=(0,u.v)(t&&t.target,t);return void 0===e&&(e=!!i.backgroundColor),!1!==e&&null!==e&&(!0===e?"origin":e)}(s);if((0,u.i)(e))return!isNaN(e.value)&&e;let n=parseFloat(e);return(0,u.g)(n)&&Math.floor(n)===n?function $n(s,i,t,e){return("-"===s||"+"===s)&&(t=i+t),!(t===i||t<0||t>=e)&&t}(e[0],i,n,t):["origin","start","end","stack","shape"].indexOf(e)>=0&&e}function a(s,i,t){const e=[];for(let n=0;n=0;--r){const l=n[r].$filler;l&&(l.line.updateControlPoints(o,l.axis),e&&l.fill&&C(s.ctx,l,o))}},beforeDatasetsDraw(s,i,t){if("beforeDatasetsDraw"!==t.drawTime)return;const e=s.getSortedVisibleDatasetMetas();for(let n=e.length-1;n>=0;--n){const o=e[n].$filler;Ji(o)&&C(s.ctx,o,s.chartArea)}},beforeDatasetDraw(s,i,t){const e=i.meta.$filler;!Ji(e)||"beforeDatasetDraw"!==t.drawTime||C(s.ctx,e,s.chartArea)},defaults:{propagate:!0,drawTime:"beforeDatasetDraw"}};const K=(s,i)=>{let{boxHeight:t=i,boxWidth:e=i}=s;return s.usePointStyle&&(t=Math.min(t,i),e=s.pointStyleWidth||Math.min(e,i)),{boxWidth:e,boxHeight:t,itemHeight:Math.max(i,t)}};class bt extends wt{constructor(i){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=i.chart,this.options=i.options,this.ctx=i.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(i,t,e){this.maxWidth=i,this.maxHeight=t,this._margins=e,this.setDimensions(),this.buildLabels(),this.fit()}setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.left=this._margins.left,this.right=this.width):(this.height=this.maxHeight,this.top=this._margins.top,this.bottom=this.height)}buildLabels(){const i=this.options.labels||{};let t=(0,u.Q)(i.generateLabels,[this.chart],this)||[];i.filter&&(t=t.filter(e=>i.filter(e,this.chart.data))),i.sort&&(t=t.sort((e,n)=>i.sort(e,n,this.chart.data))),this.options.reverse&&t.reverse(),this.legendItems=t}fit(){const{options:i,ctx:t}=this;if(!i.display)return void(this.width=this.height=0);const e=i.labels,n=(0,u.a0)(e.font),o=n.size,r=this._computeTitleHeight(),{boxWidth:l,itemHeight:c}=K(e,o);let d,g;t.font=n.string,this.isHorizontal()?(d=this.maxWidth,g=this._fitRows(r,o,l,c)+10):(g=this.maxHeight,d=this._fitCols(r,n,l,c)+10),this.width=Math.min(d,i.maxWidth||this.maxWidth),this.height=Math.min(g,i.maxHeight||this.maxHeight)}_fitRows(i,t,e,n){const{ctx:o,maxWidth:r,options:{labels:{padding:l}}}=this,c=this.legendHitBoxes=[],d=this.lineWidths=[0],g=n+l;let m=i;o.textAlign="left",o.textBaseline="middle";let x=-1,v=-g;return this.legendItems.forEach((S,P)=>{const O=e+t/2+o.measureText(S.text).width;(0===P||d[d.length-1]+O+2*l>r)&&(m+=g,d[d.length-(P>0?0:1)]=0,v+=g,x++),c[P]={left:0,top:v,row:x,width:O,height:n},d[d.length-1]+=O+l}),m}_fitCols(i,t,e,n){const{ctx:o,maxHeight:r,options:{labels:{padding:l}}}=this,c=this.legendHitBoxes=[],d=this.columnSizes=[],g=r-i;let m=l,x=0,v=0,S=0,P=0;return this.legendItems.forEach((O,z)=>{const{itemWidth:A,itemHeight:B}=function Pe(s,i,t,e,n){const o=function Ne(s,i,t,e){let n=s.text;return n&&"string"!=typeof n&&(n=n.reduce((o,r)=>o.length>r.length?o:r)),i+t.size/2+e.measureText(n).width}(e,s,i,t),r=function to(s,i,t){let e=s;return"string"!=typeof i.text&&(e=Gn(i,t)),e}(n,e,i.lineHeight);return{itemWidth:o,itemHeight:r}}(e,t,o,O,n);z>0&&v+B+2*l>g&&(m+=x+l,d.push({width:x,height:v}),S+=x+l,P++,x=v=0),c[z]={left:S,top:v,col:P,width:A,height:B},x=Math.max(x,A),v+=B+l}),m+=x,d.push({width:x,height:v}),m}adjustHitBoxes(){if(!this.options.display)return;const i=this._computeTitleHeight(),{legendHitBoxes:t,options:{align:e,labels:{padding:n},rtl:o}}=this,r=(0,u.az)(o,this.left,this.width);if(this.isHorizontal()){let l=0,c=(0,u.a2)(e,this.left+n,this.right-this.lineWidths[l]);for(const d of t)l!==d.row&&(l=d.row,c=(0,u.a2)(e,this.left+n,this.right-this.lineWidths[l])),d.top+=this.top+i+n,d.left=r.leftForLtr(r.x(c),d.width),c+=d.width+n}else{let l=0,c=(0,u.a2)(e,this.top+i+n,this.bottom-this.columnSizes[l].height);for(const d of t)d.col!==l&&(l=d.col,c=(0,u.a2)(e,this.top+i+n,this.bottom-this.columnSizes[l].height)),d.top=c,d.left+=this.left+n,d.left=r.leftForLtr(r.x(d.left),d.width),c+=d.height+n}}isHorizontal(){return"top"===this.options.position||"bottom"===this.options.position}draw(){if(this.options.display){const i=this.ctx;(0,u.Y)(i,this),this._draw(),(0,u.$)(i)}}_draw(){const{options:i,columnSizes:t,lineWidths:e,ctx:n}=this,{align:o,labels:r}=i,l=u.d.color,c=(0,u.az)(i.rtl,this.left,this.width),d=(0,u.a0)(r.font),{padding:g}=r,m=d.size,x=m/2;let v;this.drawTitle(),n.textAlign=c.textAlign("left"),n.textBaseline="middle",n.lineWidth=.5,n.font=d.string;const{boxWidth:S,boxHeight:P,itemHeight:O}=K(r,m),B=this.isHorizontal(),j=this._computeTitleHeight();v=B?{x:(0,u.a2)(o,this.left+g,this.right-e[0]),y:this.top+g+j,line:0}:{x:this.left+g,y:(0,u.a2)(o,this.top+j+g,this.bottom-t[0].height),line:0},(0,u.aA)(this.ctx,i.textDirection);const L=O+g;this.legendItems.forEach((H,W)=>{n.strokeStyle=H.fontColor,n.fillStyle=H.fontColor;const V=n.measureText(H.text).width,U=c.textAlign(H.textAlign||(H.textAlign=r.textAlign)),tt=S+x+V;let J=v.x,nt=v.y;c.setWidth(this.width),B?W>0&&J+tt+g>this.right&&(nt=v.y+=L,v.line++,J=v.x=(0,u.a2)(o,this.left+g,this.right-e[v.line])):W>0&&nt+L>this.bottom&&(J=v.x=J+t[v.line].width+g,v.line++,nt=v.y=(0,u.a2)(o,this.top+j+g,this.bottom-t[v.line].height)),function(H,W,V){if(isNaN(S)||S<=0||isNaN(P)||P<0)return;n.save();const U=(0,u.v)(V.lineWidth,1);if(n.fillStyle=(0,u.v)(V.fillStyle,l),n.lineCap=(0,u.v)(V.lineCap,"butt"),n.lineDashOffset=(0,u.v)(V.lineDashOffset,0),n.lineJoin=(0,u.v)(V.lineJoin,"miter"),n.lineWidth=U,n.strokeStyle=(0,u.v)(V.strokeStyle,l),n.setLineDash((0,u.v)(V.lineDash,[])),r.usePointStyle){const tt={radius:P*Math.SQRT2/2,pointStyle:V.pointStyle,rotation:V.rotation,borderWidth:U},J=c.xPlus(H,S/2);(0,u.aD)(n,tt,J,W+x,r.pointStyleWidth&&S)}else{const tt=W+Math.max((m-P)/2,0),J=c.leftForLtr(H,S),nt=(0,u.aw)(V.borderRadius);n.beginPath(),Object.values(nt).some(Mt=>0!==Mt)?(0,u.au)(n,{x:J,y:tt,w:S,h:P,radius:nt}):n.rect(J,tt,S,P),n.fill(),0!==U&&n.stroke()}n.restore()}(c.x(J),nt,H),J=(0,u.aB)(U,J+S+x,B?J+tt:this.right,i.rtl),function(H,W,V){(0,u.Z)(n,V.text,H,W+O/2,d,{strikethrough:V.hidden,textAlign:c.textAlign(V.textAlign)})}(c.x(J),nt,H),B?v.x+=tt+g:v.y+="string"!=typeof H.text?Gn(H,d.lineHeight)+g:L}),(0,u.aC)(this.ctx,i.textDirection)}drawTitle(){const i=this.options,t=i.title,e=(0,u.a0)(t.font),n=(0,u.E)(t.padding);if(!t.display)return;const o=(0,u.az)(i.rtl,this.left,this.width),r=this.ctx,l=t.position,d=n.top+e.size/2;let g,m=this.left,x=this.width;if(this.isHorizontal())x=Math.max(...this.lineWidths),g=this.top+d,m=(0,u.a2)(i.align,m,this.right-x);else{const S=this.columnSizes.reduce((P,O)=>Math.max(P,O.height),0);g=d+(0,u.a2)(i.align,this.top,this.bottom-S-i.labels.padding-this._computeTitleHeight())}const v=(0,u.a2)(l,m,m+x);r.textAlign=o.textAlign((0,u.a1)(l)),r.textBaseline="middle",r.strokeStyle=t.color,r.fillStyle=t.color,r.font=e.string,(0,u.Z)(r,t.text,v,g,e)}_computeTitleHeight(){const i=this.options.title,t=(0,u.a0)(i.font),e=(0,u.E)(i.padding);return i.display?t.lineHeight+e.height:0}_getLegendItemAt(i,t){let e,n,o;if((0,u.aj)(i,this.left,this.right)&&(0,u.aj)(t,this.top,this.bottom))for(o=this.legendHitBoxes,e=0;enull!==s&&null!==i&&s.datasetIndex===i.datasetIndex&&s.index===i.index)(n,e);n&&!o&&(0,u.Q)(t.onLeave,[i,n,this],this),this._hoveredItem=e,e&&!o&&(0,u.Q)(t.onHover,[i,e,this],this)}else e&&(0,u.Q)(t.onClick,[i,e,this],this)}}function Gn(s,i){return i*(s.text?s.text.length:0)}var Qn={id:"legend",_element:bt,start(s,i,t){const e=s.legend=new bt({ctx:s.ctx,options:t,chart:s});gt.configure(s,e,t),gt.addBox(s,e)},stop(s){gt.removeBox(s,s.legend),delete s.legend},beforeUpdate(s,i,t){const e=s.legend;gt.configure(s,e,t),e.options=t},afterUpdate(s){const i=s.legend;i.buildLabels(),i.adjustHitBoxes()},afterEvent(s,i){i.replay||s.legend.handleEvent(i.event)},defaults:{display:!0,position:"top",align:"center",fullSize:!0,reverse:!1,weight:1e3,onClick(s,i,t){const e=i.datasetIndex,n=t.chart;n.isDatasetVisible(e)?(n.hide(e),i.hidden=!0):(n.show(e),i.hidden=!1)},onHover:null,onLeave:null,labels:{color:s=>s.chart.options.color,boxWidth:40,padding:10,generateLabels(s){const i=s.data.datasets,{labels:{usePointStyle:t,pointStyle:e,textAlign:n,color:o,useBorderRadius:r,borderRadius:l}}=s.legend.options;return s._getSortedDatasetMetas().map(c=>{const d=c.controller.getStyle(t?0:void 0),g=(0,u.E)(d.borderWidth);return{text:i[c.index].label,fillStyle:d.backgroundColor,fontColor:o,hidden:!c.visible,lineCap:d.borderCapStyle,lineDash:d.borderDash,lineDashOffset:d.borderDashOffset,lineJoin:d.borderJoinStyle,lineWidth:(g.width+g.height)/4,strokeStyle:d.borderColor,pointStyle:e||d.pointStyle,rotation:d.rotation,textAlign:n||d.textAlign,borderRadius:r&&(l||d.borderRadius),datasetIndex:c.index}},this)}},title:{color:s=>s.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:s=>!s.startsWith("on"),labels:{_scriptable:s=>!["generateLabels","filter","sort"].includes(s)}}};class Jt extends wt{constructor(i){super(),this.chart=i.chart,this.options=i.options,this.ctx=i.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(i,t){const e=this.options;if(this.left=0,this.top=0,!e.display)return void(this.width=this.height=this.right=this.bottom=0);this.width=this.right=i,this.height=this.bottom=t;const n=(0,u.b)(e.text)?e.text.length:1;this._padding=(0,u.E)(e.padding);const o=n*(0,u.a0)(e.font).lineHeight+this._padding.height;this.isHorizontal()?this.height=o:this.width=o}isHorizontal(){const i=this.options.position;return"top"===i||"bottom"===i}_drawArgs(i){const{top:t,left:e,bottom:n,right:o,options:r}=this,l=r.align;let d,g,m,c=0;return this.isHorizontal()?(g=(0,u.a2)(l,e,o),m=t+i,d=o-e):("left"===r.position?(g=e+i,m=(0,u.a2)(l,n,t),c=-.5*u.P):(g=o-i,m=(0,u.a2)(l,t,n),c=.5*u.P),d=n-t),{titleX:g,titleY:m,maxWidth:d,rotation:c}}draw(){const i=this.ctx,t=this.options;if(!t.display)return;const e=(0,u.a0)(t.font),o=e.lineHeight/2+this._padding.top,{titleX:r,titleY:l,maxWidth:c,rotation:d}=this._drawArgs(o);(0,u.Z)(i,t.text,0,0,e,{color:t.color,maxWidth:c,rotation:d,textAlign:(0,u.a1)(t.align),textBaseline:"middle",translation:[r,l]})}}var Lo={id:"title",_element:Jt,start(s,i,t){!function qn(s,i){const t=new Jt({ctx:s.ctx,options:i,chart:s});gt.configure(s,t,i),gt.addBox(s,t),s.titleBlock=t}(s,t)},stop(s){gt.removeBox(s,s.titleBlock),delete s.titleBlock},beforeUpdate(s,i,t){const e=s.titleBlock;gt.configure(s,e,t),e.options=t},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const Jn=new WeakMap;var mo={id:"subtitle",start(s,i,t){const e=new Jt({ctx:s.ctx,options:t,chart:s});gt.configure(s,e,t),gt.addBox(s,e),Jn.set(s,e)},stop(s){gt.removeBox(s,Jn.get(s)),Jn.delete(s)},beforeUpdate(s,i,t){const e=Jn.get(s);gt.configure(s,e,t),e.options=t},defaults:{align:"center",display:!1,font:{weight:"normal"},fullSize:!0,padding:0,position:"top",text:"",weight:1500},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const nn={average(s){if(!s.length)return!1;let i,t,e=0,n=0,o=0;for(i=0,t=s.length;i-1?s.split("\n"):s}function Eo(s,i){const{element:t,datasetIndex:e,index:n}=i,o=s.getDatasetMeta(e).controller,{label:r,value:l}=o.getLabelAndValue(n);return{chart:s,label:r,parsed:o.getParsed(n),raw:s.data.datasets[e].data[n],formattedValue:l,dataset:o.getDataset(),dataIndex:n,datasetIndex:e,element:t}}function bo(s,i){const t=s.chart.ctx,{body:e,footer:n,title:o}=s,{boxWidth:r,boxHeight:l}=i,c=(0,u.a0)(i.bodyFont),d=(0,u.a0)(i.titleFont),g=(0,u.a0)(i.footerFont),m=o.length,x=n.length,v=e.length,S=(0,u.E)(i.padding);let P=S.height,O=0,z=e.reduce((j,L)=>j+L.before.length+L.lines.length+L.after.length,0);z+=s.beforeBody.length+s.afterBody.length,m&&(P+=m*d.lineHeight+(m-1)*i.titleSpacing+i.titleMarginBottom),z&&(P+=v*(i.displayColors?Math.max(l,c.lineHeight):c.lineHeight)+(z-v)*c.lineHeight+(z-1)*i.bodySpacing),x&&(P+=i.footerMarginTop+x*g.lineHeight+(x-1)*i.footerSpacing);let A=0;const B=function(j){O=Math.max(O,t.measureText(j).width+A)};return t.save(),t.font=d.string,(0,u.F)(s.title,B),t.font=c.string,(0,u.F)(s.beforeBody.concat(s.afterBody),B),A=i.displayColors?r+2+i.boxPadding:0,(0,u.F)(e,j=>{(0,u.F)(j.before,B),(0,u.F)(j.lines,B),(0,u.F)(j.after,B)}),A=0,t.font=g.string,(0,u.F)(s.footer,B),t.restore(),O+=S.width,{width:O,height:P}}function Io(s,i,t,e){const{x:n,width:o}=t,{width:r,chartArea:{left:l,right:c}}=s;let d="center";return"center"===e?d=n<=(l+c)/2?"left":"right":n<=o/2?d="left":n>=r-o/2&&(d="right"),function Ro(s,i,t,e){const{x:n,width:o}=e,r=t.caretSize+t.caretPadding;if("left"===s&&n+o+r>i.width||"right"===s&&n-o-r<0)return!0}(d,s,i,t)&&(d="center"),d}function xo(s,i,t){const e=t.yAlign||i.yAlign||function _o(s,i){const{y:t,height:e}=i;return ts.height-e/2?"bottom":"center"}(s,t);return{xAlign:t.xAlign||i.xAlign||Io(s,i,t,e),yAlign:e}}function yo(s,i,t,e){const{caretSize:n,caretPadding:o,cornerRadius:r}=s,{xAlign:l,yAlign:c}=t,d=n+o,{topLeft:g,topRight:m,bottomLeft:x,bottomRight:v}=(0,u.aw)(r);let S=function Fo(s,i){let{x:t,width:e}=s;return"right"===i?t-=e:"center"===i&&(t-=e/2),t}(i,l);const P=function Bo(s,i,t){let{y:e,height:n}=s;return"top"===i?e+=t:e-="bottom"===i?n+t:n/2,e}(i,c,d);return"center"===c?"left"===l?S+=d:"right"===l&&(S-=d):"left"===l?S-=Math.max(g,x)+n:"right"===l&&(S+=Math.max(m,v)+n),{x:(0,u.S)(S,0,e.width-i.width),y:(0,u.S)(P,0,e.height-i.height)}}function ts(s,i,t){const e=(0,u.E)(t.padding);return"center"===i?s.x+s.width/2:"right"===i?s.x+s.width-e.right:s.x+e.left}function vo(s){return te([],ee(s))}function Mo(s,i){const t=i&&i.dataset&&i.dataset.tooltip&&i.dataset.tooltip.callbacks;return t?s.override(t):s}const jo={beforeTitle:u.aF,title(s){if(s.length>0){const i=s[0],t=i.chart.data.labels,e=t?t.length:0;if(this&&this.options&&"dataset"===this.options.mode)return i.dataset.label||"";if(i.label)return i.label;if(e>0&&i.dataIndex"u"?jo[i].call(t,e):n}let So=(()=>{class s extends wt{constructor(t){super(),this.opacity=0,this._active=[],this._eventPosition=void 0,this._size=void 0,this._cachedAnimations=void 0,this._tooltipItems=[],this.$animations=void 0,this.$context=void 0,this.chart=t.chart,this.options=t.options,this.dataPoints=void 0,this.title=void 0,this.beforeBody=void 0,this.body=void 0,this.afterBody=void 0,this.footer=void 0,this.xAlign=void 0,this.yAlign=void 0,this.x=void 0,this.y=void 0,this.height=void 0,this.width=void 0,this.caretX=void 0,this.caretY=void 0,this.labelColors=void 0,this.labelPointStyles=void 0,this.labelTextColors=void 0}initialize(t){this.options=t,this._cachedAnimations=void 0,this.$context=void 0}_resolveAnimations(){const t=this._cachedAnimations;if(t)return t;const e=this.chart,n=this.options.setContext(this.getContext()),o=n.enabled&&e.options.animation&&n.animations,r=new mt(this.chart,o);return o._cacheable&&(this._cachedAnimations=Object.freeze(r)),r}getContext(){return this.$context||(this.$context=function Ho(s,i,t){return(0,u.j)(s,{tooltip:i,tooltipItems:t,type:"tooltip"})}(this.chart.getContext(),this,this._tooltipItems))}getTitle(t,e){const{callbacks:n}=e,o=kt(n,"beforeTitle",this,t),r=kt(n,"title",this,t),l=kt(n,"afterTitle",this,t);let c=[];return c=te(c,ee(o)),c=te(c,ee(r)),c=te(c,ee(l)),c}getBeforeBody(t,e){return vo(kt(e.callbacks,"beforeBody",this,t))}getBody(t,e){const{callbacks:n}=e,o=[];return(0,u.F)(t,r=>{const l={before:[],lines:[],after:[]},c=Mo(n,r);te(l.before,ee(kt(c,"beforeLabel",this,r))),te(l.lines,kt(c,"label",this,r)),te(l.after,ee(kt(c,"afterLabel",this,r))),o.push(l)}),o}getAfterBody(t,e){return vo(kt(e.callbacks,"afterBody",this,t))}getFooter(t,e){const{callbacks:n}=e,o=kt(n,"beforeFooter",this,t),r=kt(n,"footer",this,t),l=kt(n,"afterFooter",this,t);let c=[];return c=te(c,ee(o)),c=te(c,ee(r)),c=te(c,ee(l)),c}_createItems(t){const e=this._active,n=this.chart.data,o=[],r=[],l=[];let d,g,c=[];for(d=0,g=e.length;dt.filter(m,x,v,n))),t.itemSort&&(c=c.sort((m,x)=>t.itemSort(m,x,n))),(0,u.F)(c,m=>{const x=Mo(t.callbacks,m);o.push(kt(x,"labelColor",this,m)),r.push(kt(x,"labelPointStyle",this,m)),l.push(kt(x,"labelTextColor",this,m))}),this.labelColors=o,this.labelPointStyles=r,this.labelTextColors=l,this.dataPoints=c,c}update(t,e){const n=this.options.setContext(this.getContext()),o=this._active;let r,l=[];if(o.length){const c=nn[n.position].call(this,o,this._eventPosition);l=this._createItems(n),this.title=this.getTitle(l,n),this.beforeBody=this.getBeforeBody(l,n),this.body=this.getBody(l,n),this.afterBody=this.getAfterBody(l,n),this.footer=this.getFooter(l,n);const d=this._size=bo(this,n),g=Object.assign({},c,d),m=xo(this.chart,n,g),x=yo(n,g,m,this.chart);this.xAlign=m.xAlign,this.yAlign=m.yAlign,r={opacity:1,x:x.x,y:x.y,width:d.width,height:d.height,caretX:c.x,caretY:c.y}}else 0!==this.opacity&&(r={opacity:0});this._tooltipItems=l,this.$context=void 0,r&&this._resolveAnimations().update(this,r),t&&n.external&&n.external.call(this,{chart:this.chart,tooltip:this,replay:e})}drawCaret(t,e,n,o){const r=this.getCaretPosition(t,n,o);e.lineTo(r.x1,r.y1),e.lineTo(r.x2,r.y2),e.lineTo(r.x3,r.y3)}getCaretPosition(t,e,n){const{xAlign:o,yAlign:r}=this,{caretSize:l,cornerRadius:c}=n,{topLeft:d,topRight:g,bottomLeft:m,bottomRight:x}=(0,u.aw)(c),{x:v,y:S}=t,{width:P,height:O}=e;let z,A,B,j,L,H;return"center"===r?(L=S+O/2,"left"===o?(z=v,A=z-l,j=L+l,H=L-l):(z=v+P,A=z+l,j=L-l,H=L+l),B=z):(A="left"===o?v+Math.max(d,m)+l:"right"===o?v+P-Math.max(g,x)-l:this.caretX,"top"===r?(j=S,L=j-l,z=A-l,B=A+l):(j=S+O,L=j+l,z=A+l,B=A-l),H=j),{x1:z,x2:A,x3:B,y1:j,y2:L,y3:H}}drawTitle(t,e,n){const o=this.title,r=o.length;let l,c,d;if(r){const g=(0,u.az)(n.rtl,this.x,this.width);for(t.x=ts(this,n.titleAlign,n),e.textAlign=g.textAlign(n.titleAlign),e.textBaseline="middle",l=(0,u.a0)(n.titleFont),c=n.titleSpacing,e.fillStyle=n.titleColor,e.font=l.string,d=0;d0!==B)?(t.beginPath(),t.fillStyle=r.multiKeyBackground,(0,u.au)(t,{x:O,y:P,w:g,h:d,radius:A}),t.fill(),t.stroke(),t.fillStyle=l.backgroundColor,t.beginPath(),(0,u.au)(t,{x:z,y:P+1,w:g-2,h:d-2,radius:A}),t.fill()):(t.fillStyle=r.multiKeyBackground,t.fillRect(O,P,g,d),t.strokeRect(O,P,g,d),t.fillStyle=l.backgroundColor,t.fillRect(z,P+1,g-2,d-2))}t.fillStyle=this.labelTextColors[n]}drawBody(t,e,n){const{body:o}=this,{bodySpacing:r,bodyAlign:l,displayColors:c,boxHeight:d,boxWidth:g,boxPadding:m}=n,x=(0,u.a0)(n.bodyFont);let v=x.lineHeight,S=0;const P=(0,u.az)(n.rtl,this.x,this.width),O=function(U){e.fillText(U,P.x(t.x+S),t.y+v/2),t.y+=v+r},z=P.textAlign(l);let A,B,j,L,H,W,V;for(e.textAlign=l,e.textBaseline="middle",e.font=x.string,t.x=ts(this,z,n),e.fillStyle=n.bodyColor,(0,u.F)(this.beforeBody,O),S=c&&"right"!==z?"center"===l?g/2+m:g+2+m:0,L=0,W=o.length;L0&&e.stroke()}_updateAnimationTarget(t){const e=this.chart,n=this.$animations,o=n&&n.x,r=n&&n.y;if(o||r){const l=nn[t.position].call(this,this._active,this._eventPosition);if(!l)return;const c=this._size=bo(this,t),d=Object.assign({},l,this._size),g=xo(e,t,d),m=yo(t,d,g,e);(o._to!==m.x||r._to!==m.y)&&(this.xAlign=g.xAlign,this.yAlign=g.yAlign,this.width=c.width,this.height=c.height,this.caretX=l.x,this.caretY=l.y,this._resolveAnimations().update(this,m))}}_willRender(){return!!this.opacity}draw(t){const e=this.options.setContext(this.getContext());let n=this.opacity;if(!n)return;this._updateAnimationTarget(e);const o={width:this.width,height:this.height},r={x:this.x,y:this.y};n=Math.abs(n)<.001?0:n;const l=(0,u.E)(e.padding);e.enabled&&(this.title.length||this.beforeBody.length||this.body.length||this.afterBody.length||this.footer.length)&&(t.save(),t.globalAlpha=n,this.drawBackground(r,t,o,e),(0,u.aA)(t,e.textDirection),r.y+=l.top,this.drawTitle(r,t,e),this.drawBody(r,t,e),this.drawFooter(r,t,e),(0,u.aC)(t,e.textDirection),t.restore())}getActiveElements(){return this._active||[]}setActiveElements(t,e){const n=this._active,o=t.map(({datasetIndex:c,index:d})=>{const g=this.chart.getDatasetMeta(c);if(!g)throw new Error("Cannot find a dataset at index "+c);return{datasetIndex:c,element:g.data[d],index:d}}),r=!(0,u.ah)(n,o),l=this._positionChanged(o,e);(r||l)&&(this._active=o,this._eventPosition=e,this._ignoreReplayEvents=!0,this.update(!0))}handleEvent(t,e,n=!0){if(e&&this._ignoreReplayEvents)return!1;this._ignoreReplayEvents=!1;const o=this.options,r=this._active||[],l=this._getActiveElements(t,r,e,n),c=this._positionChanged(l,t),d=e||!(0,u.ah)(l,r)||c;return d&&(this._active=l,(o.enabled||o.external)&&(this._eventPosition={x:t.x,y:t.y},this.update(!0,e))),d}_getActiveElements(t,e,n,o){const r=this.options;if("mouseout"===t.type)return[];if(!o)return e;const l=this.chart.getElementsAtEventForMode(t,r.mode,r,n);return r.reverse&&l.reverse(),l}_positionChanged(t,e){const{caretX:n,caretY:o,options:r}=this,l=nn[r.position].call(this,t,e);return!1!==l&&(n!==l.x||o!==l.y)}}return(0,R.Z)(s,"positioners",nn),s})();var Vo=Object.freeze({__proto__:null,Colors:Xs,Decimation:rt,Filler:dt,Legend:Qn,SubTitle:mo,Title:Lo,Tooltip:{id:"tooltip",_element:So,positioners:nn,afterInit(s,i,t){t&&(s.tooltip=new So({chart:s,options:t}))},beforeUpdate(s,i,t){s.tooltip&&s.tooltip.initialize(t)},reset(s,i,t){s.tooltip&&s.tooltip.initialize(t)},afterDraw(s){const i=s.tooltip;if(i&&i._willRender()){const t={tooltip:i};if(!1===s.notifyPlugins("beforeTooltipDraw",{...t,cancelable:!0}))return;i.draw(s.ctx),s.notifyPlugins("afterTooltipDraw",t)}},afterEvent(s,i){s.tooltip&&s.tooltip.handleEvent(i.event,i.replay,i.inChartArea)&&(i.changed=!0)},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(s,i)=>i.bodyFont.size,boxWidth:(s,i)=>i.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,boxPadding:0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:jo},defaultRoutes:{bodyFont:"font",footerFont:"font",titleFont:"font"},descriptors:{_scriptable:s=>"filter"!==s&&"itemSort"!==s&&"external"!==s,_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]}});function Uo(s){const i=this.getLabels();return s>=0&&s{class s extends Nt{constructor(t){super(t),this._startValue=void 0,this._valueRange=0,this._addedLabels=[]}init(t){const e=this._addedLabels;if(e.length){const n=this.getLabels();for(const{index:o,label:r}of e)n[o]===r&&n.splice(o,1);this._addedLabels=[]}super.init(t)}parse(t,e){if((0,u.k)(t))return null;const n=this.getLabels();return((s,i)=>null===s?null:(0,u.S)(Math.round(s),0,i))(e=isFinite(e)&&n[e]===t?e:function Yo(s,i,t,e){const n=s.indexOf(i);return-1===n?((s,i,t,e)=>("string"==typeof i?(t=s.push(i)-1,e.unshift({index:t,label:i})):isNaN(i)&&(t=null),t))(s,i,t,e):n!==s.lastIndexOf(i)?t:n}(n,t,(0,u.v)(e,t),this._addedLabels),n.length-1)}determineDataLimits(){const{minDefined:t,maxDefined:e}=this.getUserBounds();let{min:n,max:o}=this.getMinMax(!0);"ticks"===this.options.bounds&&(t||(n=0),e||(o=this.getLabels().length-1)),this.min=n,this.max=o}buildTicks(){const t=this.min,e=this.max,n=this.options.offset,o=[];let r=this.getLabels();r=0===t&&e===r.length-1?r:r.slice(t,e+1),this._valueRange=Math.max(r.length-(n?0:1),1),this._startValue=this.min-(n?.5:0);for(let l=t;l<=e;l++)o.push({value:l});return o}getLabelForValue(t){return Uo.call(this,t)}configure(){super.configure(),this.isHorizontal()||(this._reversePixels=!this._reversePixels)}getPixelForValue(t){return"number"!=typeof t&&(t=this.parse(t)),null===t?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getValueForPixel(t){return Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange)}getBasePixel(){return this.bottom}}return(0,R.Z)(s,"id","category"),(0,R.Z)(s,"defaults",{ticks:{callback:Uo}}),s})();function ko(s,i,{horizontal:t,minRotation:e}){const n=(0,u.t)(e),o=(t?Math.sin(n):Math.cos(n))||.001;return Math.min(i/o,.75*i*(""+s).length)}class es extends Nt{constructor(i){super(i),this.start=void 0,this.end=void 0,this._startValue=void 0,this._endValue=void 0,this._valueRange=0}parse(i,t){return(0,u.k)(i)||("number"==typeof i||i instanceof Number)&&!isFinite(+i)?null:+i}handleTickRangeOptions(){const{beginAtZero:i}=this.options,{minDefined:t,maxDefined:e}=this.getUserBounds();let{min:n,max:o}=this;const r=c=>n=t?n:c,l=c=>o=e?o:c;if(i){const c=(0,u.s)(n),d=(0,u.s)(o);c<0&&d<0?l(0):c>0&&d>0&&r(0)}if(n===o){let c=0===o?1:Math.abs(.05*o);l(o+c),i||r(n-c)}this.min=n,this.max=o}getTickLimit(){const i=this.options.ticks;let n,{maxTicksLimit:t,stepSize:e}=i;return e?(n=Math.ceil(this.max/e)-Math.floor(this.min/e)+1,n>1e3&&(console.warn(`scales.${this.id}.ticks.stepSize: ${e} would result generating up to ${n} ticks. Limiting to 1000.`),n=1e3)):(n=this.computeTickLimit(),t=t||11),t&&(n=Math.min(t,n)),n}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){const i=this.options,t=i.ticks;let e=this.getTickLimit();e=Math.max(2,e);const r=function wo(s,i){const t=[],{bounds:n,step:o,min:r,max:l,precision:c,count:d,maxTicks:g,maxDigits:m,includeBounds:x}=s,v=o||1,S=g-1,{min:P,max:O}=i,z=!(0,u.k)(r),A=!(0,u.k)(l),B=!(0,u.k)(d),j=(O-P)/(m+1);let H,W,V,U,L=(0,u.aH)((O-P)/S/v)*v;if(L<1e-14&&!z&&!A)return[{value:P},{value:O}];U=Math.ceil(O/L)-Math.floor(P/L),U>S&&(L=(0,u.aH)(U*L/S/v)*v),(0,u.k)(c)||(H=Math.pow(10,c),L=Math.ceil(L*H)/H),"ticks"===n?(W=Math.floor(P/L)*L,V=Math.ceil(O/L)*L):(W=P,V=O),z&&A&&o&&(0,u.aI)((l-r)/o,L/1e3)?(U=Math.round(Math.min((l-r)/L,g)),L=(l-r)/U,W=r,V=l):B?(W=z?r:W,V=A?l:V,U=d-1,L=(V-W)/U):(U=(V-W)/L,U=(0,u.aJ)(U,Math.round(U),L/1e3)?Math.round(U):Math.ceil(U));const tt=Math.max((0,u.aK)(L),(0,u.aK)(W));H=Math.pow(10,(0,u.k)(c)?tt:c),W=Math.round(W*H)/H,V=Math.round(V*H)/H;let J=0;for(z&&(x&&W!==r?(t.push({value:r}),Wl)break;t.push({value:nt})}return A&&x&&V!==l?t.length&&(0,u.aJ)(t[t.length-1].value,l,ko(l,j,s))?t[t.length-1].value=l:t.push({value:l}):(!A||V===l)&&t.push({value:V}),t}({maxTicks:e,bounds:i.bounds,min:i.min,max:i.max,precision:t.precision,step:t.stepSize,count:t.count,maxDigits:this._maxDigits(),horizontal:this.isHorizontal(),minRotation:t.minRotation||0,includeBounds:!1!==t.includeBounds},this._range||this);return"ticks"===i.bounds&&(0,u.aG)(r,this,"value"),i.reverse?(r.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),r}configure(){const i=this.ticks;let t=this.min,e=this.max;if(super.configure(),this.options.offset&&i.length){const n=(e-t)/Math.max(i.length-1,1)/2;t-=n,e+=n}this._startValue=t,this._endValue=e,this._valueRange=e-t}getLabelForValue(i){return(0,u.o)(i,this.chart.options.locale,this.options.ticks.format)}}class io extends es{determineDataLimits(){const{min:i,max:t}=this.getMinMax(!0);this.min=(0,u.g)(i)?i:0,this.max=(0,u.g)(t)?t:1,this.handleTickRangeOptions()}computeTickLimit(){const i=this.isHorizontal(),t=i?this.width:this.height,e=(0,u.t)(this.options.ticks.minRotation),n=(i?Math.sin(e):Math.cos(e))||.001,o=this._resolveTickFontOptions(0);return Math.ceil(t/Math.min(40,o.lineHeight/n))}getPixelForValue(i){return null===i?NaN:this.getPixelForDecimal((i-this._startValue)/this._valueRange)}getValueForPixel(i){return this._startValue+this.getDecimalForPixel(i)*this._valueRange}}(0,R.Z)(io,"id","linear"),(0,R.Z)(io,"defaults",{ticks:{callback:u.aL.formatters.numeric}});const sn=s=>Math.floor((0,u.aM)(s)),Pt=(s,i)=>Math.pow(10,sn(s)+i);function is(s){return s/Math.pow(10,sn(s))==1}function Po(s,i,t){const e=Math.pow(10,t),n=Math.floor(s/e);return Math.ceil(i/e)-n}class no extends Nt{constructor(i){super(i),this.start=void 0,this.end=void 0,this._startValue=void 0,this._valueRange=0}parse(i,t){const e=es.prototype.parse.apply(this,[i,t]);if(0!==e)return(0,u.g)(e)&&e>0?e:null;this._zero=!0}determineDataLimits(){const{min:i,max:t}=this.getMinMax(!0);this.min=(0,u.g)(i)?Math.max(0,i):null,this.max=(0,u.g)(t)?Math.max(0,t):null,this.options.beginAtZero&&(this._zero=!0),this._zero&&this.min!==this._suggestedMin&&!(0,u.g)(this._userMin)&&(this.min=i===Pt(this.min,0)?Pt(this.min,-1):Pt(this.min,0)),this.handleTickRangeOptions()}handleTickRangeOptions(){const{minDefined:i,maxDefined:t}=this.getUserBounds();let e=this.min,n=this.max;const o=l=>e=i?e:l,r=l=>n=t?n:l;e===n&&(e<=0?(o(1),r(10)):(o(Pt(e,-1)),r(Pt(n,1)))),e<=0&&o(Pt(n,-1)),n<=0&&r(Pt(e,1)),this.min=e,this.max=n}buildTicks(){const i=this.options,e=function Co(s,{min:i,max:t}){i=(0,u.O)(s.min,i);const e=[],n=sn(i);let o=function Ce(s,i){let e=sn(i-s);for(;Po(s,i,e)>10;)e++;for(;Po(s,i,e)<10;)e--;return Math.min(e,sn(s))}(i,t),r=o<0?Math.pow(10,Math.abs(o)):1;const l=Math.pow(10,o),c=n>o?Math.pow(10,n):0,d=Math.round((i-c)*r)/r,g=Math.floor((i-c)/l/10)*l*10;let m=Math.floor((d-g)/Math.pow(10,o)),x=(0,u.O)(s.min,Math.round((c+g+m*Math.pow(10,o))*r)/r);for(;x=10?m=m<15?15:20:m++,m>=20&&(o++,m=2,r=o>=0?1:r),x=Math.round((c+g+m*Math.pow(10,o))*r)/r;const v=(0,u.O)(s.max,x);return e.push({value:v,major:is(v),significand:m}),e}({min:this._userMin,max:this._userMax},this);return"ticks"===i.bounds&&(0,u.aG)(e,this,"value"),i.reverse?(e.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),e}getLabelForValue(i){return void 0===i?"0":(0,u.o)(i,this.chart.options.locale,this.options.ticks.format)}configure(){const i=this.min;super.configure(),this._startValue=(0,u.aM)(i),this._valueRange=(0,u.aM)(this.max)-(0,u.aM)(i)}getPixelForValue(i){return(void 0===i||0===i)&&(i=this.min),null===i||isNaN(i)?NaN:this.getPixelForDecimal(i===this.min?0:((0,u.aM)(i)-this._startValue)/this._valueRange)}getValueForPixel(i){const t=this.getDecimalForPixel(i);return Math.pow(10,this._startValue+t*this._valueRange)}}function _(s){const i=s.ticks;if(i.display&&s.display){const t=(0,u.E)(i.backdropPadding);return(0,u.v)(i.font&&i.font.size,u.d.font.size)+t.height}return 0}function w(s,i,t){return t=(0,u.b)(t)?t:[t],{w:(0,u.aN)(s,i.string,t),h:t.length*i.lineHeight}}function D(s,i,t,e,n){return s===e||s===n?{start:i-t/2,end:i+t/2}:sn?{start:i-t,end:i}:{start:i,end:i+t}}function Q(s,i,t,e,n){const o=Math.abs(Math.sin(t)),r=Math.abs(Math.cos(t));let l=0,c=0;e.starti.r&&(l=(e.end-i.r)/o,s.r=Math.max(s.r,i.r+l)),n.starti.b&&(c=(n.end-i.b)/r,s.b=Math.max(s.b,i.b+c))}function st(s,i,t){const e=s.drawingArea,{extra:n,additionalAngle:o,padding:r,size:l}=t,c=s.getPointPosition(i,e+n+r,o),d=Math.round((0,u.U)((0,u.ay)(c.angle+u.H))),g=function Rt(s,i,t){return 90===t||270===t?s-=i/2:(t>270||t<90)&&(s-=i),s}(c.y,l.h,d),m=function ht(s){return 0===s||180===s?"center":s<180?"left":"right"}(d),x=function Wt(s,i,t){return"right"===t?s-=i:"center"===t&&(s-=i/2),s}(c.x,l.w,m);return{visible:!0,x:c.x,y:g,textAlign:m,left:x,top:g,right:x+l.w,bottom:g+l.h}}function it(s,i){if(!i)return!0;const{left:t,top:e,right:n,bottom:o}=s;return!((0,u.C)({x:t,y:e},i)||(0,u.C)({x:t,y:o},i)||(0,u.C)({x:n,y:e},i)||(0,u.C)({x:n,y:o},i))}function on(s,i,t){const{left:e,top:n,right:o,bottom:r}=t,{backdropColor:l}=i;if(!(0,u.k)(l)){const c=(0,u.aw)(i.borderRadius),d=(0,u.E)(i.backdropPadding);s.fillStyle=l;const g=e-d.left,m=n-d.top,x=o-e+d.width,v=r-n+d.height;Object.values(c).some(S=>0!==S)?(s.beginPath(),(0,u.au)(s,{x:g,y:m,w:x,h:v,radius:c}),s.fill()):s.fillRect(g,m,x,v)}}function ns(s,i,t,e){const{ctx:n}=s;if(t)n.arc(s.xCenter,s.yCenter,i,0,u.T);else{let o=s.getPointPosition(0,i);n.moveTo(o.x,o.y);for(let r=1;r{const n=(0,u.Q)(this.options.pointLabels.callback,[t,e],this);return n||0===n?n:""}).filter((t,e)=>this.chart.getDataVisibility(e))}fit(){const i=this.options;i.display&&i.pointLabels.display?function I(s){const i={l:s.left+s._padding.left,r:s.right-s._padding.right,t:s.top+s._padding.top,b:s.bottom-s._padding.bottom},t=Object.assign({},i),e=[],n=[],o=s._pointLabels.length,r=s.options.pointLabels,l=r.centerPointLabels?u.P/o:0;for(let c=0;c=0&&i=0;n--){const o=s._pointLabelItems[n];if(!o.visible)continue;const r=e.setContext(s.getPointLabelContext(n));on(t,r,o);const l=(0,u.a0)(r.font),{x:c,y:d,textAlign:g}=o;(0,u.Z)(t,s._pointLabels[n],c,d+l.lineHeight/2,l,{color:r.color,textAlign:g,textBaseline:"middle"})}}(this,r),n.display&&this.ticks.forEach((g,m)=>{if(0!==m){c=this.getDistanceFromCenterForValue(g.value);const x=this.getContext(m),v=n.setContext(x),S=o.setContext(x);!function Oo(s,i,t,e,n){const o=s.ctx,r=i.circular,{color:l,lineWidth:c}=i;!r&&!e||!l||!c||t<0||(o.save(),o.strokeStyle=l,o.lineWidth=c,o.setLineDash(n.dash),o.lineDashOffset=n.dashOffset,o.beginPath(),ns(s,t,r,e),o.closePath(),o.stroke(),o.restore())}(this,v,c,r,S)}}),e.display){for(i.save(),l=r-1;l>=0;l--){const g=e.setContext(this.getPointLabelContext(l)),{color:m,lineWidth:x}=g;!x||!m||(i.lineWidth=x,i.strokeStyle=m,i.setLineDash(g.borderDash),i.lineDashOffset=g.borderDashOffset,c=this.getDistanceFromCenterForValue(t.ticks.reverse?this.min:this.max),d=this.getPointPosition(l,c),i.beginPath(),i.moveTo(this.xCenter,this.yCenter),i.lineTo(d.x,d.y),i.stroke())}i.restore()}}drawBorder(){}drawLabels(){const i=this.ctx,t=this.options,e=t.ticks;if(!e.display)return;const n=this.getIndexAngle(0);let o,r;i.save(),i.translate(this.xCenter,this.yCenter),i.rotate(n),i.textAlign="center",i.textBaseline="middle",this.ticks.forEach((l,c)=>{if(0===c&&!t.reverse)return;const d=e.setContext(this.getContext(c)),g=(0,u.a0)(d.font);if(o=this.getDistanceFromCenterForValue(this.ticks[c].value),d.showLabelBackdrop){i.font=g.string,r=i.measureText(l.label).width,i.fillStyle=d.backdropColor;const m=(0,u.E)(d.backdropPadding);i.fillRect(-r/2-m.left,-o-g.size/2-m.top,r+m.width,g.size+m.height)}(0,u.Z)(i,l.label,0,-o,g,{color:d.color,strokeColor:d.textStrokeColor,strokeWidth:d.textStrokeWidth})}),i.restore()}drawTitle(){}}(0,R.Z)(oo,"id","radialLinear"),(0,R.Z)(oo,"defaults",{display:!0,animate:!0,position:"chartArea",angleLines:{display:!0,lineWidth:1,borderDash:[],borderDashOffset:0},grid:{circular:!1},startAngle:0,ticks:{showLabelBackdrop:!0,callback:u.aL.formatters.numeric},pointLabels:{backdropColor:void 0,backdropPadding:2,display:!0,font:{size:10},callback:s=>s,padding:5,centerPointLabels:!1}}),(0,R.Z)(oo,"defaultRoutes",{"angleLines.color":"borderColor","pointLabels.color":"color","ticks.color":"color"}),(0,R.Z)(oo,"descriptors",{angleLines:{_fallback:"grid"}});const Do={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},Yt=Object.keys(Do);function Go(s,i){return s-i}function Qo(s,i){if((0,u.k)(i))return null;const t=s._adapter,{parser:e,round:n,isoWeekday:o}=s._parseOpts;let r=i;return"function"==typeof e&&(r=e(r)),(0,u.g)(r)||(r="string"==typeof e?t.parse(r,e):t.parse(r)),null===r?null:(n&&(r="week"!==n||!(0,u.x)(o)&&!0!==o?t.startOf(r,n):t.startOf(r,"isoWeek",o)),+r)}function qo(s,i,t,e){const n=Yt.length;for(let o=Yt.indexOf(s);o=i?t[e]:t[n]]=!0}}else s[i]=!0}function tr(s,i,t){const e=[],n={},o=i.length;let r,l;for(r=0;r=0&&(i[c].major=!0);return i}(s,e,n,t):e}let Xo=(()=>{class s extends Nt{constructor(t){super(t),this._cache={data:[],labels:[],all:[]},this._unit="day",this._majorUnit=void 0,this._offsets={},this._normalized=!1,this._parseOpts=void 0}init(t,e={}){const n=t.time||(t.time={}),o=this._adapter=new ls__date(t.adapters.date);o.init(e),(0,u.ab)(n.displayFormats,o.formats()),this._parseOpts={parser:n.parser,round:n.round,isoWeekday:n.isoWeekday},super.init(t),this._normalized=e.normalized}parse(t,e){return void 0===t?null:Qo(this,t)}beforeLayout(){super.beforeLayout(),this._cache={data:[],labels:[],all:[]}}determineDataLimits(){const t=this.options,e=this._adapter,n=t.time.unit||"day";let{min:o,max:r,minDefined:l,maxDefined:c}=this.getUserBounds();function d(g){!l&&!isNaN(g.min)&&(o=Math.min(o,g.min)),!c&&!isNaN(g.max)&&(r=Math.max(r,g.max))}(!l||!c)&&(d(this._getLabelBounds()),("ticks"!==t.bounds||"labels"!==t.ticks.source)&&d(this.getMinMax(!1))),o=(0,u.g)(o)&&!isNaN(o)?o:+e.startOf(Date.now(),n),r=(0,u.g)(r)&&!isNaN(r)?r:+e.endOf(Date.now(),n)+1,this.min=Math.min(o,r-1),this.max=Math.max(o+1,r)}_getLabelBounds(){const t=this.getLabelTimestamps();let e=Number.POSITIVE_INFINITY,n=Number.NEGATIVE_INFINITY;return t.length&&(e=t[0],n=t[t.length-1]),{min:e,max:n}}buildTicks(){const t=this.options,e=t.time,n=t.ticks,o="labels"===n.source?this.getLabelTimestamps():this._generate();"ticks"===t.bounds&&o.length&&(this.min=this._userMin||o[0],this.max=this._userMax||o[o.length-1]);const r=this.min,c=(0,u.aO)(o,r,this.max);return this._unit=e.unit||(n.autoSkip?qo(e.minUnit,this.min,this.max,this._getLabelCapacity(r)):function ir(s,i,t,e,n){for(let o=Yt.length-1;o>=Yt.indexOf(t);o--){const r=Yt[o];if(Do[r].common&&s._adapter.diff(n,e,r)>=i-1)return r}return Yt[t?Yt.indexOf(t):0]}(this,c.length,e.minUnit,this.min,this.max)),this._majorUnit=n.major.enabled&&"year"!==this._unit?function nr(s){for(let i=Yt.indexOf(s)+1,t=Yt.length;i+t.value))}initOffsets(t=[]){let o,r,e=0,n=0;this.options.offset&&t.length&&(o=this.getDecimalForValue(t[0]),e=1===t.length?1-o:(this.getDecimalForValue(t[1])-o)/2,r=this.getDecimalForValue(t[t.length-1]),n=1===t.length?r:(r-this.getDecimalForValue(t[t.length-2]))/2);const l=t.length<3?.5:.25;e=(0,u.S)(e,0,l),n=(0,u.S)(n,0,l),this._offsets={start:e,end:n,factor:1/(e+1+n)}}_generate(){const t=this._adapter,e=this.min,n=this.max,o=this.options,r=o.time,l=r.unit||qo(r.minUnit,e,n,this._getLabelCapacity(e)),c=(0,u.v)(o.ticks.stepSize,1),d="week"===l&&r.isoWeekday,g=(0,u.x)(d)||!0===d,m={};let v,S,x=e;if(g&&(x=+t.startOf(x,"isoWeek",d)),x=+t.startOf(x,g?"day":l),t.diff(n,e,l)>1e5*c)throw new Error(e+" and "+n+" are too far apart with stepSize of "+c+" "+l);const P="data"===o.ticks.source&&this.getDataTimestamps();for(v=x,S=0;v+O)}getLabelForValue(t){const n=this.options.time;return this._adapter.format(t,n.tooltipFormat?n.tooltipFormat:n.displayFormats.datetime)}format(t,e){return this._adapter.format(t,e||this.options.time.displayFormats[this._unit])}_tickFormatFunction(t,e,n,o){const r=this.options,l=r.ticks.callback;if(l)return(0,u.Q)(l,[t,e,n],this);const c=r.time.displayFormats,d=this._unit,g=this._majorUnit,x=g&&c[g],v=n[e];return this._adapter.format(t,o||(g&&x&&v&&v.major?x:d&&c[d]))}generateTickLabels(t){let e,n,o;for(e=0,n=t.length;e0?c:1}getDataTimestamps(){let e,n,t=this._cache.data||[];if(t.length)return t;const o=this.getMatchingVisibleMetas();if(this._normalized&&o.length)return this._cache.data=o[0].controller.getAllParsedValues(this);for(e=0,n=o.length;e=s[e].pos&&i<=s[n].pos&&({lo:e,hi:n}=(0,u.B)(s,"pos",i)),({pos:o,time:l}=s[e]),({pos:r,time:c}=s[n])):(i>=s[e].time&&i<=s[n].time&&({lo:e,hi:n}=(0,u.B)(s,"time",i)),({time:o,pos:l}=s[e]),({time:r,pos:c}=s[n]));const d=r-o;return d?l+(c-l)*(i-o)/d:l}class Ko extends Xo{constructor(i){super(i),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){const i=this._getTimestampsForTable(),t=this._table=this.buildLookupTable(i);this._minPos=Ao(t,this.min),this._tableRange=Ao(t,this.max)-this._minPos,super.initOffsets(i)}buildLookupTable(i){const{min:t,max:e}=this,n=[],o=[];let r,l,c,d,g;for(r=0,l=i.length;r=t&&d<=e&&n.push(d);if(n.length<2)return[{time:t,pos:0},{time:e,pos:1}];for(r=0,l=n.length;rn-o)}_getTimestampsForTable(){let i=this._cache.all||[];if(i.length)return i;const t=this.getDataTimestamps(),e=this.getLabelTimestamps();return i=t.length&&e.length?this.normalize(t.concat(e)):t.length?t:e,i=this._cache.all=i,i}getDecimalForValue(i){return(Ao(this._table,i)-this._minPos)/this._tableRange}getValueForPixel(i){const t=this._offsets,e=this.getDecimalForPixel(i)/t.factor-t.end;return Ao(this._table,e*this._tableRange+this._minPos,!0)}}(0,R.Z)(Ko,"id","timeseries"),(0,R.Z)(Ko,"defaults",Xo.defaults);const rr=[yt,yi,Vo,Object.freeze({__proto__:null,CategoryScale:Zo,LinearScale:io,LogarithmicScale:no,RadialLinearScale:oo,TimeScale:Xo,TimeSeriesScale:Ko})]},98137:(zo,wi,ct)=>{function R(a){return a+.5|0}ct.d(wi,{$:()=>fi,A:()=>ys,B:()=>xe,C:()=>Ie,D:()=>_s,E:()=>Tn,F:()=>cs,G:()=>Vn,H:()=>vt,I:()=>xi,J:()=>Ks,K:()=>Xs,L:()=>ri,M:()=>Ws,N:()=>cn,O:()=>uo,P:()=>ft,Q:()=>ls,R:()=>zn,S:()=>si,T:()=>Et,U:()=>bs,V:()=>ui,W:()=>xs,X:()=>Ts,Y:()=>wn,Z:()=>Ls,_:()=>oi,a:()=>Rs,a0:()=>Es,a1:()=>ks,a2:()=>fn,a3:()=>_n,a4:()=>be,a5:()=>Ri,a6:()=>Re,a7:()=>ze,a8:()=>ae,a9:()=>$i,aA:()=>qi,aB:()=>gn,aC:()=>qs,aD:()=>Sn,aE:()=>Fi,aF:()=>as,aG:()=>ps,aH:()=>fs,aI:()=>hn,aJ:()=>Bt,aK:()=>ni,aL:()=>Vi,aM:()=>_e,aN:()=>As,aO:()=>vs,aP:()=>jt,aa:()=>Ln,ab:()=>ei,ac:()=>zi,ad:()=>ws,ae:()=>Zs,af:()=>vn,ag:()=>ds,ah:()=>Ae,ai:()=>ln,aj:()=>dn,ak:()=>gi,al:()=>Vs,am:()=>Un,an:()=>tn,ao:()=>Wn,ap:()=>Yn,aq:()=>ce,ar:()=>Fe,as:()=>po,at:()=>Mn,au:()=>Cn,av:()=>He,aw:()=>Yi,ax:()=>Ji,ay:()=>Ht,az:()=>Qs,b:()=>_t,c:()=>Ps,d:()=>yn,e:()=>li,f:()=>re,g:()=>De,h:()=>hs,i:()=>xt,j:()=>je,k:()=>Oe,l:()=>Ms,m:()=>oe,n:()=>Je,o:()=>Vt,p:()=>Bi,q:()=>pn,r:()=>un,s:()=>Ii,t:()=>ms,u:()=>Ss,v:()=>yt,w:()=>ye,x:()=>gs,y:()=>Hs,z:()=>$s});const u=(a,h,f)=>Math.max(Math.min(a,f),h);function E(a){return u(R(2.55*a),0,255)}function St(a){return u(R(255*a),0,255)}function Dt(a){return u(R(a/2.55)/100,0,1)}function Ve(a){return u(R(100*a),0,100)}const mt={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Ut=[..."0123456789ABCDEF"],We=a=>Ut[15&a],ie=a=>Ut[(240&a)>>4]+Ut[15&a],de=a=>(240&a)>>4==(15&a);const Ue=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function Ze(a,h,f){const p=h*Math.min(f,1-f),b=(y,M=(y+a/30)%12)=>f-p*Math.max(Math.min(M-3,9-M,1),-1);return[b(0),b(8),b(4)]}function Ci(a,h,f){const p=(b,y=(b+a/60)%6)=>f-f*h*Math.max(Math.min(y,4-y,1),0);return[p(5),p(3),p(1)]}function Oi(a,h,f){const p=Ze(a,1,.5);let b;for(h+f>1&&(b=1/(h+f),h*=b,f*=b),b=0;b<3;b++)p[b]*=1-h-f,p[b]+=h;return p}function At(a){const f=a.r/255,p=a.g/255,b=a.b/255,y=Math.max(f,p,b),M=Math.min(f,p,b),k=(y+M)/2;let C,T,F;return y!==M&&(F=y-M,T=k>.5?F/(2-y-M):F/(y+M),C=function Xe(a,h,f,p,b){return a===b?(h-f)/p+(ha<=.0031308?12.92*a:1.055*Math.pow(a,1/2.4)-.055,zt=a=>a<=.04045?a/12.92:Math.pow((a+.055)/1.055,2.4);function Lt(a,h,f){if(a){let p=At(a);p[h]=Math.max(0,Math.min(p[h]+p[h]*f,0===h?360:1)),p=ue(p),a.r=p[0],a.g=p[1],a.b=p[2]}}function qe(a,h){return a&&Object.assign(h||{},a)}function Ti(a){var h={r:0,g:0,b:0,a:255};return Array.isArray(a)?a.length>=3&&(h={r:a[0],g:a[1],b:a[2],a:255},a.length>3&&(h.a=St(a[3]))):(h=qe(a,{r:0,g:0,b:0,a:1})).a=St(h.a),h}function rs(a){return"r"===a.charAt(0)?function q(a){const h=X.exec(a);let p,b,y,f=255;if(h){if(h[7]!==p){const M=+h[7];f=h[8]?E(M):u(255*M,0,255)}return p=+h[1],b=+h[3],y=+h[5],p=255&(h[2]?E(p):u(p,0,255)),b=255&(h[4]?E(b):u(b,0,255)),y=255&(h[6]?E(y):u(y,0,255)),{r:p,g:b,b:y,a:f}}}(a):function fe(a){const h=Ue.exec(a);let p,f=255;if(!h)return;h[5]!==p&&(f=h[6]?E(+h[5]):St(+h[5]));const b=se(+h[2]),y=+h[3]/100,M=+h[4]/100;return p="hwb"===h[1]?function Di(a,h,f){return Tt(Oi,a,h,f)}(b,y,M):"hsv"===h[1]?function ne(a,h,f){return Tt(Ci,a,h,f)}(b,y,M):ue(b,y,M),{r:p[0],g:p[1],b:p[2],a:f}}(a)}class pe{constructor(h){if(h instanceof pe)return h;const f=typeof h;let p;"object"===f?p=Ti(h):"string"===f&&(p=function Ye(a){var f,h=a.length;return"#"===a[0]&&(4===h||5===h?f={r:255&17*mt[a[1]],g:255&17*mt[a[2]],b:255&17*mt[a[3]],a:5===h?17*mt[a[4]]:255}:(7===h||9===h)&&(f={r:mt[a[1]]<<4|mt[a[2]],g:mt[a[3]]<<4|mt[a[4]],b:mt[a[5]]<<4|mt[a[6]],a:9===h?mt[a[7]]<<4|mt[a[8]]:255})),f}(h)||function N(a){ge||(ge=function Ai(){const a={},h=Object.keys(Qe),f=Object.keys(Ge);let p,b,y,M,k;for(p=0;p>16&255,y>>8&255,255&y]}return a}(),ge.transparent=[0,0,0,0]);const h=ge[a.toLowerCase()];return h&&{r:h[0],g:h[1],b:h[2],a:4===h.length?h[3]:255}}(h)||rs(h)),this._rgb=p,this._valid=!!p}get valid(){return this._valid}get rgb(){var h=qe(this._rgb);return h&&(h.a=Dt(h.a)),h}set rgb(h){this._rgb=Ti(h)}rgbString(){return this._valid?function G(a){return a&&(a.a<255?`rgba(${a.r}, ${a.g}, ${a.b}, ${Dt(a.a)})`:`rgb(${a.r}, ${a.g}, ${a.b})`)}(this._rgb):void 0}hexString(){return this._valid?function Pi(a){var h=(a=>de(a.r)&&de(a.g)&&de(a.b)&&de(a.a))(a)?We:ie;return a?"#"+h(a.r)+h(a.g)+h(a.b)+((a,h)=>a<255?h(a):"")(a.a,h):void 0}(this._rgb):void 0}hslString(){return this._valid?function It(a){if(!a)return;const h=At(a),f=h[0],p=Ve(h[1]),b=Ve(h[2]);return a.a<255?`hsla(${f}, ${p}%, ${b}%, ${Dt(a.a)})`:`hsl(${f}, ${p}%, ${b}%)`}(this._rgb):void 0}mix(h,f){if(h){const p=this.rgb,b=h.rgb;let y;const M=f===y?.5:f,k=2*M-1,C=p.a-b.a,T=((k*C==-1?k:(k+C)/(1+k*C))+1)/2;y=1-T,p.r=255&T*p.r+y*b.r+.5,p.g=255&T*p.g+y*b.g+.5,p.b=255&T*p.b+y*b.b+.5,p.a=M*p.a+(1-M)*b.a,this.rgb=p}return this}interpolate(h,f){return h&&(this._rgb=function Zt(a,h,f){const p=zt(Dt(a.r)),b=zt(Dt(a.g)),y=zt(Dt(a.b));return{r:St(lt(p+f*(zt(Dt(h.r))-p))),g:St(lt(b+f*(zt(Dt(h.g))-b))),b:St(lt(y+f*(zt(Dt(h.b))-y))),a:a.a+f*(h.a-a.a)}}(this._rgb,h._rgb,f)),this}clone(){return new pe(this.rgb)}alpha(h){return this._rgb.a=St(h),this}clearer(h){return this._rgb.a*=1-h,this}greyscale(){const h=this._rgb,f=R(.3*h.r+.59*h.g+.11*h.b);return h.r=h.g=h.b=f,this}opaquer(h){return this._rgb.a*=1+h,this}negate(){const h=this._rgb;return h.r=255-h.r,h.g=255-h.g,h.b=255-h.b,this}lighten(h){return Lt(this._rgb,2,h),this}darken(h){return Lt(this._rgb,2,-h),this}saturate(h){return Lt(this._rgb,1,h),this}desaturate(h){return Lt(this._rgb,1,-h),this}rotate(h){return function Ke(a,h){var f=At(a);f[0]=se(f[0]+h),f=ue(f),a.r=f[0],a.g=f[1],a.b=f[2]}(this._rgb,h),this}}function as(){}const zi=(()=>{let a=0;return()=>a++})();function Oe(a){return null===a||typeof a>"u"}function _t(a){if(Array.isArray&&Array.isArray(a))return!0;const h=Object.prototype.toString.call(a);return"[object"===h.slice(0,7)&&"Array]"===h.slice(-6)}function xt(a){return null!==a&&"[object Object]"===Object.prototype.toString.call(a)}function De(a){return("number"==typeof a||a instanceof Number)&&isFinite(+a)}function uo(a,h){return De(a)?a:h}function yt(a,h){return typeof a>"u"?h:a}const oe=(a,h)=>"string"==typeof a&&a.endsWith("%")?parseFloat(a)/100:+a/h,Je=(a,h)=>"string"==typeof a&&a.endsWith("%")?parseFloat(a)/100*h:+a;function ls(a,h,f){if(a&&"function"==typeof a.call)return a.apply(f,h)}function cs(a,h,f,p){let b,y,M;if(_t(a))if(y=a.length,p)for(b=y-1;b>=0;b--)h.call(f,a[b],b);else for(b=0;ba,x:a=>a.x,y:a=>a.y};function re(a,h){return(Ei[h]||(Ei[h]=function an(a){const h=function Te(a){const h=a.split("."),f=[];let p="";for(const b of h)p+=b,p.endsWith("\\")?p=p.slice(0,-1)+".":(f.push(p),p="");return f}(a);return f=>{for(const p of h){if(""===p)break;f=f&&f[p]}return f}}(h)))(a)}function Ri(a){return a.charAt(0).toUpperCase()+a.slice(1)}const hs=a=>typeof a<"u",ze=a=>"function"==typeof a,ds=(a,h)=>{if(a.size!==h.size)return!1;for(const f of a)if(!h.has(f))return!1;return!0};function ln(a){return"mouseup"===a.type||"click"===a.type||"contextmenu"===a.type}const ft=Math.PI,Et=2*ft,us=Et+ft,ii=Number.POSITIVE_INFINITY,Le=ft/180,vt=ft/2,Ft=ft/4,gt=2*ft/3,_e=Math.log10,Ii=Math.sign;function Bt(a,h,f){return Math.abs(a-h)b-y).pop(),h}function gs(a){return!isNaN(parseFloat(a))&&isFinite(a)}function hn(a,h){const f=Math.round(a);return f-h<=a&&f+h>=a}function ps(a,h,f){let p,b,y;for(p=0,b=a.length;pC&&T=Math.min(h,f)-p&&a<=Math.max(h,f)+p}function jt(a,h,f){f=f||(M=>a[M]1;)y=b+p>>1,f(y)?b=y:p=y;return{lo:b,hi:p}}const xe=(a,h,f,p)=>jt(a,f,p?b=>{const y=a[b][h];return ya[b][h]jt(a,f,p=>a[p][h]>=f);function vs(a,h,f){let p=0,b=a.length;for(;pp&&a[b-1]>f;)b--;return p>0||b{const p="_onData"+Ri(f),b=a[f];Object.defineProperty(a,f,{configurable:!0,enumerable:!1,value(...y){const M=b.apply(this,y);return a._chartjs.listeners.forEach(k=>{"function"==typeof k[p]&&k[p](...y)}),M}})}))}function Ss(a,h){const f=a._chartjs;if(!f)return;const p=f.listeners,b=p.indexOf(h);-1!==b&&p.splice(b,1),!(p.length>0)&&(wt.forEach(y=>{delete a[y]}),delete a._chartjs)}function oi(a){const h=new Set(a);return h.size===a.length?a:Array.from(h)}const un=typeof window>"u"?function(a){return a()}:window.requestAnimationFrame;function ri(a,h){let f=[],p=!1;return function(...b){f=b,p||(p=!0,un.call(window,()=>{p=!1,a.apply(h,f)}))}}function ws(a,h){let f;return function(...p){return h?(clearTimeout(f),f=setTimeout(a,h,p)):a.apply(this,p),h}}const ks=a=>"start"===a?"left":"end"===a?"right":"center",fn=(a,h,f)=>"start"===a?h:"end"===a?f:(h+f)/2,gn=(a,h,f,p)=>a===(p?"left":"right")?f:"center"===a?(h+f)/2:h;function pn(a,h,f){const p=h.length;let b=0,y=p;if(a._sorted){const{iScale:M,_parsed:k}=a,C=M.axis,{min:T,max:F,minDefined:$,maxDefined:Z}=M.getUserBounds();$&&(b=si(Math.min(xe(k,C,T).lo,f?p:xe(h,C,M.getPixelForValue(T)).lo),0,p-1)),y=Z?si(Math.max(xe(k,M.axis,F,!0).hi+1,f?0:xe(h,C,M.getPixelForValue(F),!0).hi+1),b,p)-b:p-b}return{start:b,count:y}}function ye(a){const{xScale:h,yScale:f,_scaleRanges:p}=a,b={xmin:h.min,xmax:h.max,ymin:f.min,ymax:f.max};if(!p)return a._scaleRanges=b,!0;const y=p.xmin!==h.min||p.xmax!==h.max||p.ymin!==f.min||p.ymax!==f.max;return Object.assign(p,b),y}const ai=a=>0===a||1===a,ve=(a,h,f)=>-Math.pow(2,10*(a-=1))*Math.sin((a-h)*Et/f),Hi=(a,h,f)=>Math.pow(2,-10*a)*Math.sin((a-h)*Et/f)+1,li={linear:a=>a,easeInQuad:a=>a*a,easeOutQuad:a=>-a*(a-2),easeInOutQuad:a=>(a/=.5)<1?.5*a*a:-.5*(--a*(a-2)-1),easeInCubic:a=>a*a*a,easeOutCubic:a=>(a-=1)*a*a+1,easeInOutCubic:a=>(a/=.5)<1?.5*a*a*a:.5*((a-=2)*a*a+2),easeInQuart:a=>a*a*a*a,easeOutQuart:a=>-((a-=1)*a*a*a-1),easeInOutQuart:a=>(a/=.5)<1?.5*a*a*a*a:-.5*((a-=2)*a*a*a-2),easeInQuint:a=>a*a*a*a*a,easeOutQuint:a=>(a-=1)*a*a*a*a+1,easeInOutQuint:a=>(a/=.5)<1?.5*a*a*a*a*a:.5*((a-=2)*a*a*a*a+2),easeInSine:a=>1-Math.cos(a*vt),easeOutSine:a=>Math.sin(a*vt),easeInOutSine:a=>-.5*(Math.cos(ft*a)-1),easeInExpo:a=>0===a?0:Math.pow(2,10*(a-1)),easeOutExpo:a=>1===a?1:1-Math.pow(2,-10*a),easeInOutExpo:a=>ai(a)?a:a<.5?.5*Math.pow(2,10*(2*a-1)):.5*(2-Math.pow(2,-10*(2*a-1))),easeInCirc:a=>a>=1?a:-(Math.sqrt(1-a*a)-1),easeOutCirc:a=>Math.sqrt(1-(a-=1)*a),easeInOutCirc:a=>(a/=.5)<1?-.5*(Math.sqrt(1-a*a)-1):.5*(Math.sqrt(1-(a-=2)*a)+1),easeInElastic:a=>ai(a)?a:ve(a,.075,.3),easeOutElastic:a=>ai(a)?a:Hi(a,.075,.3),easeInOutElastic:a=>ai(a)?a:a<.5?.5*ve(2*a,.1125,.45):.5+.5*Hi(2*a-1,.1125,.45),easeInBack:a=>a*a*(2.70158*a-1.70158),easeOutBack:a=>(a-=1)*a*(2.70158*a+1.70158)+1,easeInOutBack(a){let h=1.70158;return(a/=.5)<1?a*a*((1+(h*=1.525))*a-h)*.5:.5*((a-=2)*a*((1+(h*=1.525))*a+h)+2)},easeInBounce:a=>1-li.easeOutBounce(1-a),easeOutBounce:a=>a<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+.9375:7.5625*(a-=2.625/2.75)*a+.984375,easeInOutBounce:a=>a<.5?.5*li.easeInBounce(2*a):.5*li.easeOutBounce(2*a-1)+.5};function Xt(a){if(a&&"object"==typeof a){const h=a.toString();return"[object CanvasPattern]"===h||"[object CanvasGradient]"===h}return!1}function Ps(a){return Xt(a)?a:new pe(a)}function ji(a){return Xt(a)?a:new pe(a).saturate(.5).darken(.1).hexString()}const Nt=["x","y","borderWidth","radius","tension"],ci=["color","borderColor","backgroundColor"],hi=new Map;function Vt(a,h,f){return function Os(a,h){h=h||{};const f=a+JSON.stringify(h);let p=hi.get(f);return p||(p=new Intl.NumberFormat(a,h),hi.set(f,p)),p}(h,f).format(a)}const mn={values:a=>_t(a)?a:""+a,numeric(a,h,f){if(0===a)return"0";const p=this.chart.options.locale;let b,y=a;if(f.length>1){const T=Math.max(Math.abs(f[0].value),Math.abs(f[f.length-1].value));(T<1e-4||T>1e15)&&(b="scientific"),y=function Ds(a,h){let f=h.length>3?h[2].value-h[1].value:h[1].value-h[0].value;return Math.abs(f)>=1&&a!==Math.floor(a)&&(f=a-Math.floor(a)),f}(a,f)}const M=_e(Math.abs(y)),k=isNaN(M)?1:Math.max(Math.min(-1*Math.floor(M),20),0),C={notation:b,minimumFractionDigits:k,maximumFractionDigits:k};return Object.assign(C,this.options.ticks.format),Vt(a,p,C)},logarithmic(a,h,f){if(0===a)return"0";const p=f[h].significand||a/Math.pow(10,Math.floor(_e(a)));return[1,2,3,5,10,15].includes(p)||h>.8*f.length?mn.numeric.call(this,a,h,f):""}};var Vi={formatters:mn};const _n=Object.create(null),Re=Object.create(null);function Me(a,h){if(!h)return a;const f=h.split(".");for(let p=0,b=f.length;pp.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(p,b)=>ji(b.backgroundColor),this.hoverBorderColor=(p,b)=>ji(b.borderColor),this.hoverColor=(p,b)=>ji(b.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0,includeInvisible:!1},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.drawActiveElementsOnTop=!0,this.describe(h),this.apply(f)}set(h,f){return Wi(this,h,f)}get(h){return Me(this,h)}describe(h,f){return Wi(Re,h,f)}override(h,f){return Wi(_n,h,f)}route(h,f,p,b){const y=Me(this,h),M=Me(this,p),k="_"+f;Object.defineProperties(y,{[k]:{value:y[f],writable:!0},[f]:{enumerable:!0,get(){const C=this[k],T=M[b];return xt(C)?Object.assign({},T,C):yt(C,T)},set(C){this[k]=C}}})}apply(h){h.forEach(f=>f(this))}}var yn=new xn({_scriptable:a=>!a.startsWith("on"),_indexable:a=>"events"!==a,hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}},[function Ni(a){a.set("animation",{delay:void 0,duration:1e3,easing:"easeOutQuart",fn:void 0,from:void 0,loop:void 0,to:void 0,type:void 0}),a.describe("animation",{_fallback:!1,_indexable:!1,_scriptable:h=>"onProgress"!==h&&"onComplete"!==h&&"fn"!==h}),a.set("animations",{colors:{type:"color",properties:ci},numbers:{type:"number",properties:Nt}}),a.describe("animations",{_fallback:"animation"}),a.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:h=>0|h}}}})},function Cs(a){a.set("layout",{autoPadding:!0,padding:{top:0,right:0,bottom:0,left:0}})},function bn(a){a.set("scale",{display:!0,offset:!1,reverse:!1,beginAtZero:!1,bounds:"ticks",grace:0,grid:{display:!0,lineWidth:1,drawOnChartArea:!0,drawTicks:!0,tickLength:8,tickWidth:(h,f)=>f.lineWidth,tickColor:(h,f)=>f.color,offset:!1},border:{display:!0,dash:[],dashOffset:0,width:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:Vi.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}}),a.route("scale.ticks","color","","color"),a.route("scale.grid","color","","borderColor"),a.route("scale.border","color","","borderColor"),a.route("scale.title","color","","color"),a.describe("scale",{_fallback:!1,_scriptable:h=>!h.startsWith("before")&&!h.startsWith("after")&&"callback"!==h&&"parser"!==h,_indexable:h=>"borderDash"!==h&&"tickBorderDash"!==h&&"dash"!==h}),a.describe("scales",{_fallback:"scale"}),a.describe("scale.ticks",{_scriptable:h=>"backdropPadding"!==h&&"callback"!==h,_indexable:h=>"backdropPadding"!==h})}]);function ui(a,h,f,p,b){let y=h[b];return y||(y=h[b]=a.measureText(b).width,f.push(b)),y>p&&(p=y),p}function As(a,h,f,p){let b=(p=p||{}).data=p.data||{},y=p.garbageCollect=p.garbageCollect||[];p.font!==h&&(b=p.data={},y=p.garbageCollect=[],p.font=h),a.save(),a.font=h;let M=0;const k=f.length;let C,T,F,$,Z;for(C=0;Cf.length){for(C=0;C0&&a.stroke()}}function Ie(a,h,f){return f=f||.5,!h||a&&a.x>h.left-f&&a.xh.top-f&&a.y0&&""!==y.strokeColor;let C,T;for(a.save(),a.font=b.string,function kn(a,h){h.translation&&a.translate(h.translation[0],h.translation[1]),Oe(h.rotation)||a.rotate(h.rotation),h.color&&(a.fillStyle=h.color),h.textAlign&&(a.textAlign=h.textAlign),h.textBaseline&&(a.textBaseline=h.textBaseline)}(a,y),C=0;C+a||0;function gi(a,h){const f={},p=xt(h),b=p?Object.keys(h):h,y=xt(a)?p?M=>yt(a[M],a[h[M]]):M=>a[M]:()=>a;for(const M of b)f[M]=Be(y(M));return f}function He(a){return gi(a,{top:"y",right:"x",bottom:"y",left:"x"})}function Yi(a){return gi(a,["topLeft","topRight","bottomLeft","bottomRight"])}function Tn(a){const h=He(a);return h.width=h.left+h.right,h.height=h.top+h.bottom,h}function Es(a,h){let f=yt((a=a||{}).size,(h=h||yn.font).size);"string"==typeof f&&(f=parseInt(f,10));let p=yt(a.style,h.style);p&&!(""+p).match(Dn)&&(console.warn('Invalid font style specified: "'+p+'"'),p=void 0);const b={family:yt(a.family,h.family),lineHeight:An(yt(a.lineHeight,h.lineHeight),f),size:f,style:p,weight:yt(a.weight,h.weight),string:""};return b.string=function di(a){return!a||Oe(a.size)||Oe(a.family)?null:(a.style?a.style+" ":"")+(a.weight?a.weight+" ":"")+a.size+"px "+a.family}(b),b}function Rs(a,h,f,p){let y,M,k,b=!0;for(y=0,M=a.length;yf&&0===k?0:k+C;return{min:M(p,-Math.abs(y)),max:M(b,y)}}function je(a,h){return Object.assign(Object.create(a),h)}function $i(a,h=[""],f,p,b=(()=>a[0])){const y=f||a;typeof p>"u"&&(p=Bn("_fallback",a));const M={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:a,_rootScopes:y,_fallback:p,_getTarget:b,override:k=>$i([k,...a],h,y,p)};return new Proxy(M,{deleteProperty:(k,C)=>(delete k[C],delete k._keys,delete a[0][C],!0),get:(k,C)=>En(k,C,()=>function Fs(a,h,f,p){let b;for(const y of h)if(b=Bn(Se(y,a),f),typeof b<"u")return le(a,b)?pi(f,p,a,b):b}(C,h,a,k)),getOwnPropertyDescriptor:(k,C)=>Reflect.getOwnPropertyDescriptor(k._scopes[0],C),getPrototypeOf:()=>Reflect.getPrototypeOf(a[0]),has:(k,C)=>Xi(k).includes(C),ownKeys:k=>Xi(k),set(k,C,T){const F=k._storage||(k._storage=b());return k[C]=F[C]=T,delete k._keys,!0}})}function ae(a,h,f,p){const b={_cacheable:!1,_proxy:a,_context:h,_subProxy:f,_stack:new Set,_descriptors:Ln(a,p),setContext:y=>ae(a,y,f,p),override:y=>ae(a.override(y),h,f,p)};return new Proxy(b,{deleteProperty:(y,M)=>(delete y[M],delete a[M],!0),get:(y,M,k)=>En(y,M,()=>function Ui(a,h,f){const{_proxy:p,_context:b,_subProxy:y,_descriptors:M}=a;let k=p[h];return ze(k)&&M.isScriptable(h)&&(k=function we(a,h,f,p){const{_proxy:b,_context:y,_subProxy:M,_stack:k}=f;if(k.has(a))throw new Error("Recursion detected: "+Array.from(k).join("->")+"->"+a);k.add(a);let C=h(y,M||p);return k.delete(a),le(a,C)&&(C=pi(b._scopes,b,a,C)),C}(h,k,a,f)),_t(k)&&k.length&&(k=function Rn(a,h,f,p){const{_proxy:b,_context:y,_subProxy:M,_descriptors:k}=f;if(typeof y.index<"u"&&p(a))return h[y.index%h.length];if(xt(h[0])){const C=h,T=b._scopes.filter(F=>F!==C);h=[];for(const F of C){const $=pi(T,b,a,F);h.push(ae($,y,M&&M[a],k))}}return h}(h,k,a,M.isIndexable)),le(h,k)&&(k=ae(k,b,y&&y[h],M)),k}(y,M,k)),getOwnPropertyDescriptor:(y,M)=>y._descriptors.allKeys?Reflect.has(a,M)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(a,M),getPrototypeOf:()=>Reflect.getPrototypeOf(a),has:(y,M)=>Reflect.has(a,M),ownKeys:()=>Reflect.ownKeys(a),set:(y,M,k)=>(a[M]=k,delete y[M],!0)})}function Ln(a,h={scriptable:!0,indexable:!0}){const{_scriptable:f=h.scriptable,_indexable:p=h.indexable,_allKeys:b=h.allKeys}=a;return{allKeys:b,scriptable:f,indexable:p,isScriptable:ze(f)?f:()=>f,isIndexable:ze(p)?p:()=>p}}const Se=(a,h)=>a?a+Ri(h):h,le=(a,h)=>xt(h)&&"adapters"!==a&&(null===Object.getPrototypeOf(h)||h.constructor===Object);function En(a,h,f){if(Object.prototype.hasOwnProperty.call(a,h))return a[h];const p=f();return a[h]=p,p}function In(a,h,f){return ze(a)?a(h,f):a}const Is=(a,h)=>!0===a?h:"string"==typeof a?re(h,a):void 0;function Zi(a,h,f,p,b){for(const y of h){const M=Is(f,y);if(M){a.add(M);const k=In(M._fallback,f,b);if(typeof k<"u"&&k!==f&&k!==p)return k}else if(!1===M&&typeof p<"u"&&f!==p)return null}return!1}function pi(a,h,f,p){const b=h._rootScopes,y=In(h._fallback,f,p),M=[...a,...b],k=new Set;k.add(p);let C=Fn(k,M,f,y||f,p);return!(null===C||typeof y<"u"&&y!==f&&(C=Fn(k,M,y,C,p),null===C))&&$i(Array.from(k),[""],b,y,()=>function mi(a,h,f){const p=a._getTarget();h in p||(p[h]={});const b=p[h];return _t(b)&&xt(f)?f:b||{}}(h,f,p))}function Fn(a,h,f,p,b){for(;f;)f=Zi(a,h,f,p,b);return f}function Bn(a,h){for(const f of h){if(!f)continue;const p=f[a];if(typeof p<"u")return p}}function Xi(a){let h=a._keys;return h||(h=a._keys=function Bs(a){const h=new Set;for(const f of a)for(const p of Object.keys(f).filter(b=>!b.startsWith("_")))h.add(p);return Array.from(h)}(a._scopes)),h}function Hs(a,h,f,p){const{iScale:b}=a,{key:y="r"}=this._parsing,M=new Array(p);let k,C,T,F;for(k=0,C=p;kh"x"===a?"y":"x";function Hn(a,h,f,p){const b=a.skip?h:a,y=h,M=f.skip?h:f,k=Fi(y,b),C=Fi(M,y);let T=k/(k+C),F=C/(k+C);T=isNaN(T)?0:T,F=isNaN(F)?0:F;const $=p*T,Z=p*F;return{previous:{x:y.x-$*(M.x-b.x),y:y.y-$*(M.y-b.y)},next:{x:y.x+Z*(M.x-b.x),y:y.y+Z*(M.y-b.y)}}}function _i(a,h,f){return Math.max(Math.min(a,f),h)}function Vs(a,h,f,p,b){let y,M,k,C;if(h.spanGaps&&(a=a.filter(T=>!T.skip)),"monotone"===h.cubicInterpolationMode)!function Ns(a,h="x"){const f=Ki(h),p=a.length,b=Array(p).fill(0),y=Array(p);let M,k,C,T=Kt(a,0);for(M=0;Ma.ownerDocument.defaultView.getComputedStyle(a,null),jn=["top","right","bottom","left"];function qt(a,h,f){const p={};f=f?"-"+f:"";for(let b=0;b<4;b++){const y=jn[b];p[y]=parseFloat(a[h+"-"+y+f])||0}return p.width=p.left+p.right,p.height=p.top+p.bottom,p}const Nn=(a,h,f)=>(a>0||h>0)&&(!f||!f.shadowRoot);function $s(a,h){if("native"in a)return a;const{canvas:f,currentDevicePixelRatio:p}=h,b=yi(f),y="border-box"===b.boxSizing,M=qt(b,"padding"),k=qt(b,"border","width"),{x:C,y:T,box:F}=function Ys(a,h){const f=a.touches,p=f&&f.length?f[0]:a,{offsetX:b,offsetY:y}=p;let k,C,M=!1;if(Nn(b,y,a.target))k=b,C=y;else{const T=h.getBoundingClientRect();k=p.clientX-T.left,C=p.clientY-T.top,M=!0}return{x:k,y:C,box:M}}(a,f),$=M.left+(F&&k.left),Z=M.top+(F&&k.top);let{width:ot,height:dt}=h;return y&&(ot-=M.width+k.width,dt-=M.height+k.height),{x:Math.round((C-$)/ot*f.width/p),y:Math.round((T-Z)/dt*f.height/p)}}const vi=a=>Math.round(10*a)/10;function Vn(a,h,f,p){const b=yi(a),y=qt(b,"margin"),M=Qt(b.maxWidth,a,"clientWidth")||ii,k=Qt(b.maxHeight,a,"clientHeight")||ii,C=function Us(a,h,f){let p,b;if(void 0===h||void 0===f){const y=xi(a);if(y){const M=y.getBoundingClientRect(),k=yi(y),C=qt(k,"border","width"),T=qt(k,"padding");h=M.width-T.width-C.width,f=M.height-T.height-C.height,p=Qt(k.maxWidth,y,"clientWidth"),b=Qt(k.maxHeight,y,"clientHeight")}else h=a.clientWidth,f=a.clientHeight}return{width:h,height:f,maxWidth:p||ii,maxHeight:b||ii}}(a,h,f);let{width:T,height:F}=C;if("content-box"===b.boxSizing){const Z=qt(b,"border","width"),ot=qt(b,"padding");T-=ot.width+Z.width,F-=ot.height+Z.height}return T=Math.max(0,T-y.width),F=Math.max(0,p?T/p:F-y.height),T=vi(Math.min(T,M,C.maxWidth)),F=vi(Math.min(F,k,C.maxHeight)),T&&!F&&(F=vi(T/2)),(void 0!==h||void 0!==f)&&p&&C.height&&F>C.height&&(F=C.height,T=vi(Math.floor(F*p))),{width:T,height:F}}function Zs(a,h,f){const p=h||1,b=Math.floor(a.height*p),y=Math.floor(a.width*p);a.height=Math.floor(a.height),a.width=Math.floor(a.width);const M=a.canvas;return M.style&&(f||!M.style.height&&!M.style.width)&&(M.style.height=`${a.height}px`,M.style.width=`${a.width}px`),(a.currentDevicePixelRatio!==p||M.height!==b||M.width!==y)&&(a.currentDevicePixelRatio=p,M.height=b,M.width=y,a.ctx.setTransform(p,0,0,p,0,0),!0)}const Xs=function(){let a=!1;try{const h={get passive(){return a=!0,!1}};window.addEventListener("test",null,h),window.removeEventListener("test",null,h)}catch{}return a}();function Ks(a,h){const f=function Qi(a,h){return yi(a).getPropertyValue(h)}(a,h),p=f&&f.match(/^(\d+)(\.\d+)?px$/);return p?+p[1]:void 0}function ce(a,h,f,p){return{x:a.x+f*(h.x-a.x),y:a.y+f*(h.y-a.y)}}function Wn(a,h,f,p){return{x:a.x+f*(h.x-a.x),y:"middle"===p?f<.5?a.y:h.y:"after"===p?f<1?a.y:h.y:f>0?h.y:a.y}}function Yn(a,h,f,p){const b={x:a.cp2x,y:a.cp2y},y={x:h.cp1x,y:h.cp1y},M=ce(a,b,f),k=ce(b,y,f),C=ce(y,h,f),T=ce(M,k,f),F=ce(k,C,f);return ce(T,F,f)}const Gs=function(a,h){return{x:f=>a+a+h-f,setWidth(f){h=f},textAlign:f=>"center"===f?f:"right"===f?"left":"right",xPlus:(f,p)=>f-p,leftForLtr:(f,p)=>f-p}},rt=function(){return{x:a=>a,setWidth(a){},textAlign:a=>a,xPlus:(a,h)=>a+h,leftForLtr:(a,h)=>a}};function Qs(a,h,f){return a?Gs(h,f):rt()}function qi(a,h){let f,p;("ltr"===h||"rtl"===h)&&(f=a.canvas.style,p=[f.getPropertyValue("direction"),f.getPropertyPriority("direction")],f.setProperty("direction",h,"important"),a.prevTextDirection=p)}function qs(a,h){void 0!==h&&(delete a.prevTextDirection,a.canvas.style.setProperty("direction",h[0],h[1]))}function Mi(a){return"angle"===a?{between:Bi,compare:Ee,normalize:Ht}:{between:dn,compare:(h,f)=>h-f,normalize:h=>h}}function Si({start:a,end:h,count:f,loop:p,style:b}){return{start:a%f,end:h%f,loop:p&&(h-a+1)%f==0,style:b}}function Ji(a,h,f){if(!f)return[a];const{property:p,start:b,end:y}=f,M=h.length,{compare:k,between:C,normalize:T}=Mi(p),{start:F,end:$,loop:Z,style:ot}=function ke(a,h,f){const{property:p,start:b,end:y}=f,{between:M,normalize:k}=Mi(p),C=h.length;let Z,ot,{start:T,end:F,loop:$}=a;if($){for(T+=C,F+=C,Z=0,ot=C;Zb&&a[y%h].skip;)y--;return y%=h,{start:b,end:y}}(f,b,y,p);return Zn(a,!0===p?[{start:M,end:k,loop:y}]:function $n(a,h,f,p){const b=a.length,y=[];let C,M=h,k=a[h];for(C=h+1;C<=f;++C){const T=a[C%b];T.skip||T.stop?k.skip||(y.push({start:h%b,end:(C-1)%b,loop:p=!1}),h=M=T.stop?C:null):(M=C,k.skip&&(h=C)),k=T}return null!==M&&y.push({start:h%b,end:M%b,loop:p}),y}(f,M,k{ct.d(wi,{$:()=>fe,$O:()=>Qe,Jp:()=>se,KJ:()=>ge,u9:()=>Tt,yG:()=>ue});var R=ct(49388),u=ct(96814),E=ct(65879),Ot=ct(8324),St=ct(62595),Dt=ct(97582),Ve=ct(78645),mt=ct(59773),Ut=ct(37398),We=ct(40874),ie=ct(1608),de=ct(28802);function ki(N,X){if(1&N&&(E.ynx(0),E._UZ(1,"span",9),E.BQk()),2&N){const q=X.$implicit,G=E.oxw(2);E.xp6(1),E.Q6J("nzType",q||G.getBackIcon())}}function Ye(N,X){if(1&N){const q=E.EpF();E.TgZ(0,"div",6),E.NdJ("click",function(){E.CHM(q);const lt=E.oxw();return E.KtG(lt.onBack())}),E.TgZ(1,"div",7),E.YNc(2,ki,2,1,"ng-container",8),E.qZA()()}if(2&N){const q=E.oxw();E.xp6(2),E.Q6J("nzStringTemplateOutlet",q.nzBackIcon)}}function $e(N,X){if(1&N&&(E.ynx(0),E._uU(1),E.BQk()),2&N){const q=E.oxw(2);E.xp6(1),E.Oqu(q.nzTitle)}}function Pi(N,X){if(1&N&&(E.TgZ(0,"span",10),E.YNc(1,$e,2,1,"ng-container",8),E.qZA()),2&N){const q=E.oxw();E.xp6(1),E.Q6J("nzStringTemplateOutlet",q.nzTitle)}}function Ue(N,X){1&N&&E.Hsn(0,6,["*ngIf","!nzTitle"])}function Ze(N,X){if(1&N&&(E.ynx(0),E._uU(1),E.BQk()),2&N){const q=E.oxw(2);E.xp6(1),E.Oqu(q.nzSubtitle)}}function Ci(N,X){if(1&N&&(E.TgZ(0,"span",11),E.YNc(1,Ze,2,1,"ng-container",8),E.qZA()),2&N){const q=E.oxw();E.xp6(1),E.Q6J("nzStringTemplateOutlet",q.nzSubtitle)}}function Oi(N,X){1&N&&E.Hsn(0,7,["*ngIf","!nzSubtitle"])}const Xe=[[["nz-breadcrumb","nz-page-header-breadcrumb",""]],[["nz-avatar","nz-page-header-avatar",""]],[["nz-page-header-tags"],["","nz-page-header-tags",""]],[["nz-page-header-extra"],["","nz-page-header-extra",""]],[["nz-page-header-content"],["","nz-page-header-content",""]],[["nz-page-header-footer"],["","nz-page-header-footer",""]],[["nz-page-header-title"],["","nz-page-header-title",""]],[["nz-page-header-subtitle"],["","nz-page-header-subtitle",""]]],At=["nz-breadcrumb[nz-page-header-breadcrumb]","nz-avatar[nz-page-header-avatar]","nz-page-header-tags, [nz-page-header-tags]","nz-page-header-extra, [nz-page-header-extra]","nz-page-header-content, [nz-page-header-content]","nz-page-header-footer, [nz-page-header-footer]","nz-page-header-title, [nz-page-header-title]","nz-page-header-subtitle, [nz-page-header-subtitle]"];let Tt=(()=>{var N;class X{}return(N=X).\u0275fac=function(G){return new(G||N)},N.\u0275dir=E.lG2({type:N,selectors:[["nz-page-header-title"],["","nz-page-header-title",""]],hostAttrs:[1,"ant-page-header-heading-title"],exportAs:["nzPageHeaderTitle"]}),X})(),ue=(()=>{var N;class X{}return(N=X).\u0275fac=function(G){return new(G||N)},N.\u0275dir=E.lG2({type:N,selectors:[["nz-page-header-subtitle"],["","nz-page-header-subtitle",""]],hostAttrs:[1,"ant-page-header-heading-sub-title"],exportAs:["nzPageHeaderSubtitle"]}),X})(),se=(()=>{var N;class X{}return(N=X).\u0275fac=function(G){return new(G||N)},N.\u0275dir=E.lG2({type:N,selectors:[["nz-page-header-extra"],["","nz-page-header-extra",""]],hostAttrs:[1,"ant-page-header-heading-extra"],exportAs:["nzPageHeaderExtra"]}),X})(),fe=(()=>{var N;class X{}return(N=X).\u0275fac=function(G){return new(G||N)},N.\u0275dir=E.lG2({type:N,selectors:[["nz-page-header-footer"],["","nz-page-header-footer",""]],hostAttrs:[1,"ant-page-header-footer"],exportAs:["nzPageHeaderFooter"]}),X})(),Ke=(()=>{var N;class X{}return(N=X).\u0275fac=function(G){return new(G||N)},N.\u0275dir=E.lG2({type:N,selectors:[["nz-breadcrumb","nz-page-header-breadcrumb",""]],exportAs:["nzPageHeaderBreadcrumb"]}),X})(),Qe=(()=>{var N;class X{constructor(G,lt,zt,Zt,Lt,qe){this.location=G,this.nzConfigService=lt,this.elementRef=zt,this.nzResizeObserver=Zt,this.cdr=Lt,this.directionality=qe,this._nzModuleName="pageHeader",this.nzBackIcon=null,this.nzGhost=!0,this.nzBack=new E.vpe,this.compact=!1,this.destroy$=new Ve.x,this.dir="ltr"}ngOnInit(){this.directionality.change?.pipe((0,mt.R)(this.destroy$)).subscribe(G=>{this.dir=G,this.cdr.detectChanges()}),this.dir=this.directionality.value}ngAfterViewInit(){this.nzResizeObserver.observe(this.elementRef).pipe((0,Ut.U)(([G])=>G.contentRect.width),(0,mt.R)(this.destroy$)).subscribe(G=>{this.compact=G<768,this.cdr.markForCheck()})}onBack(){if(this.nzBack.observers.length)this.nzBack.emit();else{if(!this.location)throw new Error(`${ie.Bq} you should import 'RouterModule' or register 'Location' if you want to use 'nzBack' default event!`);this.location.back()}}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}getBackIcon(){return"rtl"===this.dir?"arrow-right":"arrow-left"}}return(N=X).\u0275fac=function(G){return new(G||N)(E.Y36(u.Ye,8),E.Y36(We.jY),E.Y36(E.SBq),E.Y36(de.D3),E.Y36(E.sBO),E.Y36(R.Is,8))},N.\u0275cmp=E.Xpm({type:N,selectors:[["nz-page-header"]],contentQueries:function(G,lt,zt){if(1&G&&(E.Suo(zt,fe,5),E.Suo(zt,Ke,5)),2&G){let Zt;E.iGM(Zt=E.CRH())&&(lt.nzPageHeaderFooter=Zt.first),E.iGM(Zt=E.CRH())&&(lt.nzPageHeaderBreadcrumb=Zt.first)}},hostAttrs:[1,"ant-page-header"],hostVars:10,hostBindings:function(G,lt){2&G&&E.ekj("has-footer",lt.nzPageHeaderFooter)("ant-page-header-ghost",lt.nzGhost)("has-breadcrumb",lt.nzPageHeaderBreadcrumb)("ant-page-header-compact",lt.compact)("ant-page-header-rtl","rtl"===lt.dir)},inputs:{nzBackIcon:"nzBackIcon",nzTitle:"nzTitle",nzSubtitle:"nzSubtitle",nzGhost:"nzGhost"},outputs:{nzBack:"nzBack"},exportAs:["nzPageHeader"],ngContentSelectors:At,decls:13,vars:5,consts:[[1,"ant-page-header-heading"],[1,"ant-page-header-heading-left"],["class","ant-page-header-back",3,"click",4,"ngIf"],["class","ant-page-header-heading-title",4,"ngIf"],[4,"ngIf"],["class","ant-page-header-heading-sub-title",4,"ngIf"],[1,"ant-page-header-back",3,"click"],["role","button","tabindex","0",1,"ant-page-header-back-button"],[4,"nzStringTemplateOutlet"],["nz-icon","","nzTheme","outline",3,"nzType"],[1,"ant-page-header-heading-title"],[1,"ant-page-header-heading-sub-title"]],template:function(G,lt){1&G&&(E.F$t(Xe),E.Hsn(0),E.TgZ(1,"div",0)(2,"div",1),E.YNc(3,Ye,3,1,"div",2),E.Hsn(4,1),E.YNc(5,Pi,2,1,"span",3),E.YNc(6,Ue,1,0,"ng-content",4),E.YNc(7,Ci,2,1,"span",5),E.YNc(8,Oi,1,0,"ng-content",4),E.Hsn(9,2),E.qZA(),E.Hsn(10,3),E.qZA(),E.Hsn(11,4),E.Hsn(12,5)),2&G&&(E.xp6(3),E.Q6J("ngIf",null!==lt.nzBackIcon),E.xp6(2),E.Q6J("ngIf",lt.nzTitle),E.xp6(1),E.Q6J("ngIf",!lt.nzTitle),E.xp6(1),E.Q6J("ngIf",lt.nzSubtitle),E.xp6(1),E.Q6J("ngIf",!lt.nzSubtitle))},dependencies:[u.O5,Ot.f,St.Ls],encapsulation:2,changeDetection:0}),(0,Dt.gn)([(0,We.oS)()],X.prototype,"nzGhost",void 0),X})(),ge=(()=>{var N;class X{}return(N=X).\u0275fac=function(G){return new(G||N)},N.\u0275mod=E.oAB({type:N}),N.\u0275inj=E.cJS({imports:[R.vT,u.ez,Ot.T,St.PV]}),X})()},47246:(zo,wi,ct)=>{ct.d(wi,{jh:()=>es,vQ:()=>no});var R=ct(65879),u=ct(17816),E=ct(98137),Ot=ct(65619),St=ct(93997);const Ut=function mt(_,w){return _===w||_!=_&&w!=w},ie=function We(_,w){for(var D=_.length;D--;)if(Ut(_[D][0],w))return D;return-1};var ki=Array.prototype.splice;function At(_){var w=-1,D=null==_?0:_.length;for(this.clear();++w-1},At.prototype.set=function Oi(_,w){var D=this.__data__,I=ie(D,_);return I<0?(++this.size,D.push([_,w])):D[I][1]=w,this};const Tt=At,Ai="object"==typeof global&&global&&global.Object===Object&&global;var ge="object"==typeof self&&self&&self.Object===Object&&self;const X=Ai||ge||Function("return this")();var q=X.Symbol,lt=Object.prototype,zt=lt.hasOwnProperty,Zt=lt.toString,Lt=q?q.toStringTag:void 0;var pe=Object.prototype.toString;var _t=q?q.toStringTag:void 0;const De=function xt(_){return null==_?void 0===_?"[object Undefined]":"[object Null]":_t&&_t in Object(_)?function qe(_){var w=zt.call(_,Lt),D=_[Lt];try{_[Lt]=void 0;var I=!0}catch{}var Q=Zt.call(_);return I&&(w?_[Lt]=D:delete _[Lt]),Q}(_):function ho(_){return pe.call(_)}(_)},yt=function uo(_){var w=typeof _;return null!=_&&("object"==w||"function"==w)},me=function Ae(_){if(!yt(_))return!1;var w=De(_);return"[object Function]"==w||"[object GeneratorFunction]"==w||"[object AsyncFunction]"==w||"[object Proxy]"==w};var _,ti=X["__core-js_shared__"],be=(_=/[^.]+$/.exec(ti&&ti.keys&&ti.keys.IE_PROTO||""))?"Symbol(src)_1."+_:"";var Ei=Function.prototype.toString;var Ri=/^\[object .+?Constructor\]$/,ft=RegExp("^"+Function.prototype.toString.call(Object.prototype.hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");const us=function Et(_){return!(!yt(_)||function ei(_){return!!be&&be in _}(_))&&(me(_)?ft:Ri).test(function Te(_){if(null!=_){try{return Ei.call(_)}catch{}try{return _+""}catch{}}return""}(_))},Ft=function vt(_,w){var D=function ii(_,w){return _?.[w]}(_,w);return us(D)?D:void 0},_e=Ft(X,"Map"),Bt=Ft(Object,"create");var bs=Object.prototype.hasOwnProperty;var Ee=Object.prototype.hasOwnProperty;function jt(_){var w=-1,D=null==_?0:_.length;for(this.clear();++w-1&&_%1==0&&_<=9007199254740991},Ui=function En(_){return null!=_&&le(_.length)&&!me(_)};var Zi="object"==typeof exports&&exports&&!exports.nodeType&&exports,pi=Zi&&"object"==typeof module&&module&&!module.nodeType&&module,mi=pi&&pi.exports===Zi?X.Buffer:void 0;const Xi=(mi?mi.isBuffer:void 0)||function In(){return!1};var Kt=Function.prototype.toString,Ki=Object.prototype.hasOwnProperty,Hn=Kt.call(Object);var rt={};rt["[object Float32Array]"]=rt["[object Float64Array]"]=rt["[object Int8Array]"]=rt["[object Int16Array]"]=rt["[object Int32Array]"]=rt["[object Uint8Array]"]=rt["[object Uint8ClampedArray]"]=rt["[object Uint16Array]"]=rt["[object Uint32Array]"]=!0,rt["[object Arguments]"]=rt["[object Array]"]=rt["[object ArrayBuffer]"]=rt["[object Boolean]"]=rt["[object DataView]"]=rt["[object Date]"]=rt["[object Error]"]=rt["[object Function]"]=rt["[object Map]"]=rt["[object Number]"]=rt["[object Object]"]=rt["[object RegExp]"]=rt["[object Set]"]=rt["[object String]"]=rt["[object WeakMap]"]=!1;var Si="object"==typeof exports&&exports&&!exports.nodeType&&exports,ke=Si&&"object"==typeof module&&module&&!module.nodeType&&module,tn=ke&&ke.exports===Si&&Ai.process,Js=function(){try{return ke&&ke.require&&ke.require("util").types||tn&&tn.binding&&tn.binding("util")}catch{}}(),Un=Js&&Js.isTypedArray;const Xn=Un?function qs(_){return function(w){return _(w)}}(Un):function Qs(_){return Be(_)&&le(_.length)&&!!rt[De(_)]},en=function Kn(_,w){if(("constructor"!==w||"function"!=typeof _[w])&&"__proto__"!=w)return _[w]};var h=Object.prototype.hasOwnProperty;const p=function f(_,w,D){var I=_[w];(!h.call(_,w)||!Ut(I,D)||void 0===D&&!(w in _))&&Ni(_,w,D)};var T=/^(?:0|[1-9]\d*)$/;const $=function F(_,w){var D=typeof _;return!!(w=w??9007199254740991)&&("number"==D||"symbol"!=D&&T.test(_))&&_>-1&&_%1==0&&_0){if(++w>=800)return arguments[0]}else w=0;return _.apply(void 0,arguments)}}(Bo);const So=kt,Vo=function No(_,w){return So(function Ro(_,w,D){return w=_o(void 0===w?_.length-1:w,0),function(){for(var I=arguments,Q=-1,st=_o(I.length-w,0),it=Array(st);++Q1?D[Q-1]:void 0,it=Q>2?D[2]:void 0;for(st=_.length>3&&"function"==typeof st?(Q--,st):void 0,it&&function Wo(_,w,D){if(!yt(D))return!1;var I=typeof w;return!!("number"==I?Ui(D)&&$(w,D.length):"string"==I&&w in D)&&Ut(D[w],_)}(D[0],D[1],it)&&(st=Q<3?void 0:st,Q=1),w=Object(w);++I{var _;class w{constructor(){this.colorschemesOptions=new Ot.X(void 0)}setColorschemesOptions(I){this.pColorschemesOptions=I,this.colorschemesOptions.next(I)}getColorschemesOptions(){return this.pColorschemesOptions}}return(_=w).\u0275fac=function(I){return new(I||_)},_.\u0275prov=R.Yz7({token:_,factory:_.\u0275fac,providedIn:"root"}),w})(),es=(()=>{var _;class w{constructor(I,Q,st){this.zone=Q,this.themeService=st,this.type="bar",this.plugins=[],this.chartClick=new R.vpe,this.chartHover=new R.vpe,this.subs=[],this.themeOverrides={},this.ctx=I.nativeElement.getContext("2d"),this.subs.push(this.themeService.colorschemesOptions.pipe((0,St.x)()).subscribe(it=>this.themeChanged(it)))}ngOnChanges(I){const Q=["type"],st=Object.getOwnPropertyNames(I);if(st.some(it=>Q.includes(it))||st.every(it=>I[it].isFirstChange()))this.render();else{const it=this.getChartConfiguration();this.chart&&(Object.assign(this.chart.config.data,it.data),this.chart.config.plugins&&Object.assign(this.chart.config.plugins,it.plugins),this.chart.config.options&&Object.assign(this.chart.config.options,it.options)),this.update()}}ngOnDestroy(){this.chart&&(this.chart.destroy(),this.chart=void 0),this.subs.forEach(I=>I.unsubscribe())}render(){return this.chart&&this.chart.destroy(),this.zone.runOutsideAngular(()=>this.chart=new u.kL(this.ctx,this.getChartConfiguration()))}update(I){this.chart&&this.zone.runOutsideAngular(()=>this.chart?.update(I))}hideDataset(I,Q){this.chart&&(this.chart.getDatasetMeta(I).hidden=Q,this.update())}isDatasetHidden(I){return this.chart?.getDatasetMeta(I)?.hidden}toBase64Image(){return this.chart?.toBase64Image()}themeChanged(I){this.themeOverrides=I,this.chart&&(this.chart.config.options&&Object.assign(this.chart.config.options,this.getChartOptions()),this.update())}getChartOptions(){return wo({onHover:(I,Q)=>{!this.chartHover.observed&&!this.chartHover.observers?.length||this.zone.run(()=>this.chartHover.emit({event:I,active:Q}))},onClick:(I,Q)=>{!this.chartClick.observed&&!this.chartClick.observers?.length||this.zone.run(()=>this.chartClick.emit({event:I,active:Q}))}},this.themeOverrides,this.options,{plugins:{legend:{display:this.legend}}})}getChartConfiguration(){return{type:this.type,data:this.getChartData(),options:this.getChartOptions(),plugins:this.plugins}}getChartData(){return this.data?this.data:{labels:this.labels||[],datasets:this.datasets||[]}}}return(_=w).\u0275fac=function(I){return new(I||_)(R.Y36(R.SBq),R.Y36(R.R0b),R.Y36(ko))},_.\u0275dir=R.lG2({type:_,selectors:[["canvas","baseChart",""]],inputs:{type:"type",legend:"legend",data:"data",options:"options",plugins:"plugins",labels:"labels",datasets:"datasets"},outputs:{chartClick:"chartClick",chartHover:"chartHover"},exportAs:["base-chart"],features:[R.TTD]}),w})();const io=[[255,99,132],[54,162,235],[255,206,86],[231,233,237],[75,192,192],[151,187,205],[220,220,220],[247,70,74],[70,191,189],[253,180,92],[148,159,177],[77,83,96]],sn={plugins:{colors:{enabled:!1}},datasets:{line:{backgroundColor:_=>Pt(Ce(_.datasetIndex),.4),borderColor:_=>Pt(Ce(_.datasetIndex),1),pointBackgroundColor:_=>Pt(Ce(_.datasetIndex),1),pointBorderColor:"#fff"},bar:{backgroundColor:_=>Pt(Ce(_.datasetIndex),.6),borderColor:_=>Pt(Ce(_.datasetIndex),1)},get radar(){return this.line},doughnut:{backgroundColor:_=>Pt(Ce(_.dataIndex),.6),borderColor:"#fff"},get pie(){return this.doughnut},polarArea:{backgroundColor:_=>Pt(Ce(_.dataIndex),.6),borderColor:_=>Pt(Ce(_.dataIndex),1)},get bubble(){return this.doughnut},get scatter(){return this.doughnut},get area(){return this.polarArea}}};function Pt(_,w){return"rgba("+_.concat(w).join(",")+")"}function is(_,w){return Math.floor(Math.random()*(w-_+1))+_}function Ce(_=0){return io[_]||function Po(){return[is(0,255),is(0,255),is(0,255)]}()}let Co=(()=>{var _;class w{constructor(){this.generateColors=!0}}return(_=w).\u0275fac=function(I){return new(I||_)},_.\u0275prov=R.Yz7({token:_,factory:_.\u0275fac,providedIn:"root"}),w})();u.kL.register(...u.zX);let no=(()=>{var _;class w{constructor(I){I?.plugins&&u.kL.register(...I.plugins);const Q=wo(I?.generateColors?sn:{},I?.defaults||{});E.d.set(Q)}static forRoot(I){return{ngModule:w,providers:[{provide:Co,useValue:I}]}}}return(_=w).\u0275fac=function(I){return new(I||_)(R.LFG(Co,8))},_.\u0275mod=R.oAB({type:_}),_.\u0275inj=R.cJS({}),w})()}}]); \ No newline at end of file diff --git a/worklenz-backend/src/public/150.aea42238d373936d.js b/worklenz-backend/src/public/150.aea42238d373936d.js deleted file mode 100644 index d1de3689..00000000 --- a/worklenz-backend/src/public/150.aea42238d373936d.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkworklenz=self.webpackChunkworklenz||[]).push([[150],{39787:(ye,se,c)=>{c.d(se,{l:()=>Y});var h=c(96109),N=c(42840),e=c(62595),w=c(65879),U=c(72042),D=c(70855);let Y=(()=>{var v;class T{constructor(X){this.menu=X}}return(v=T).\u0275fac=function(X){return new(X||v)(w.Y36(U.h))},v.\u0275cmp=w.Xpm({type:v,selectors:[["worklenz-toggle-menu-button"]],inputs:{key:"key"},standalone:!0,features:[w.jDz],decls:2,vars:2,consts:[["nz-button","","nz-tooltip","","nzShape","circle","nzType","link",3,"nzTooltipTitle","click"],["nz-icon","","nzType","pushpin",1,"pin-button",3,"nzTheme"]],template:function(X,ee){1&X&&(w.TgZ(0,"button",0),w.NdJ("click",function(){return ee.menu.toggle(ee.key)}),w._UZ(1,"span",1),w.qZA()),2&X&&(w.Q6J("nzTooltipTitle",ee.menu.isPinned(ee.key)?"Click to unpin this from the main menu":"Click to pin this into the main menu"),w.xp6(1),w.Q6J("nzTheme",ee.menu.isPinned(ee.key)?"fill":"outline"))},dependencies:[h.cg,h.SY,N.sL,N.ix,D.w,e.PV,e.Ls]}),T})()},16150:(ye,se,c)=>{c.r(se),c.d(se,{SettingsModule:()=>go});var h=c(96814),N=c(35420),e=c(65879),w=c(32333),U=c(90586),D=c(33410),Y=c(26254),v=c(70855),T=c(73460);const re=function(i){return[i]},X=function(){return["ant-menu-item-selected"]};function ee(i,a){if(1&i&&e._UZ(0,"li",7),2&i){const s=a.$implicit;e.Q6J("routerLink",e.VKq(4,re,s.url))("nzIcon",s.icon)("nzTitle",s.label)("routerLinkActive",e.DdM(6,X))}}let ve=(()=>{var i;class a{constructor(t,n){this.auth=t,this.app=n,this.navigation=[],this.app.setTitle("Settings")}get profile(){return this.auth.getCurrentSession()}ngOnInit(){this.buildNavigation()}isOwnerOrAdmin(){return this.profile?.owner||this.profile?.is_admin}buildNavigation(){this.navigation=[],this.navigation.push({label:"Profile",icon:"user",url:"profile"}),this.navigation.push({label:"Notifications",icon:"notification",url:"notifications"}),this.profile?.is_expired||(this.isOwnerOrAdmin()&&(this.navigation.push({label:"Clients",icon:"user-switch",url:"clients"}),this.navigation.push({label:"Job Titles",icon:"idcard",url:"job-titles"}),this.navigation.push({label:"Labels",icon:"tags",url:"labels"}),this.navigation.push({label:"Categories",icon:"group",url:"categories"}),this.navigation.push({label:"Project Templates",icon:"file-zip",url:"project-templates"}),this.navigation.push({label:"Task Templates",icon:"profile",url:"task-templates"}),this.navigation.push({label:"Team Members",icon:"team",url:"team-members"})),this.navigation.push({label:"Teams",icon:"bank",url:"teams"})),this.profile?.is_google||this.navigation.push({label:"Change Password",icon:"lock",url:"password"}),this.navigation.push({label:"Language & Region",icon:"global",url:"language-and-region"})}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(w.e),e.Y36(U.z))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-settings"]],decls:10,vars:4,consts:[[1,"container"],[1,"px-0",3,"nzGhost"],[1,"inner-layout"],[3,"nzWidth"],["nz-menu","",1,"border-0",3,"nzMode"],["class","rounded-4","nz-submenu","",3,"routerLink","nzIcon","nzTitle","routerLinkActive",4,"ngFor","ngForOf"],[1,"px-4","bg-white"],["nz-submenu","",1,"rounded-4",3,"routerLink","nzIcon","nzTitle","routerLinkActive"]],template:function(t,n){1&t&&(e.TgZ(0,"div",0)(1,"nz-page-header",1)(2,"nz-page-header-title"),e._uU(3,"Settings"),e.qZA()(),e.TgZ(4,"nz-layout",2)(5,"nz-sider",3)(6,"ul",4),e.YNc(7,ee,1,7,"li",5),e.qZA()(),e.TgZ(8,"nz-content",6),e._UZ(9,"router-outlet"),e.qZA()()()),2&t&&(e.xp6(1),e.Q6J("nzGhost",!1),e.xp6(4),e.Q6J("nzWidth","240px"),e.xp6(1),e.Q6J("nzMode","vertical"),e.xp6(1),e.Q6J("ngForOf",n.navigation))},dependencies:[h.sg,N.lC,N.rH,N.Od,D.hw,D.OK,D.t7,Y.$O,Y.u9,v.w,T.wO,T.rY],styles:["[nz-submenu][_ngcontent-%COMP%]{transition:none!important}"]}),a})();var m=c(15861),p=c(60095),me=c(59556),b=c(69649),A=c(86408),I=c(27782),he=c(16849),Se=c(44568),V=c(82962),M=c(62595),Z=c(10095),x=c(3599),P=c(20824),O=c(42840),L=c(41958),Q=c(8083),J=c(96109),$=c(24139),ge=c(34302);function Ue(i,a){1&i&&e._UZ(0,"span",22)}function Pe(i,a){1&i&&e._UZ(0,"span",23)}function u(i,a){if(1&i&&(e.TgZ(0,"div",18),e.YNc(1,Ue,1,0,"span",19),e.YNc(2,Pe,1,0,"span",20),e.TgZ(3,"div",21),e._uU(4),e.qZA()()),2&i){const s=e.oxw();e.xp6(1),e.Q6J("ngIf",!s.uploading),e.xp6(1),e.Q6J("ngIf",s.uploading),e.xp6(2),e.Oqu(s.uploading?"Uploading...":"Upload")}}function g(i,a){if(1&i&&e._UZ(0,"img",24),2&i){const s=e.oxw();e.Q6J("alt",null==s.profile?null:s.profile.name)("src",s.avatarUrl,e.LSH)}}function C(i,a){if(1&i&&(e.TgZ(0,"p",25)(1,"small",26),e.ALo(2,"date"),e._uU(3),e.ALo(4,"fromNow"),e.qZA()()),2&i){const s=e.oxw();e.xp6(1),e.s9C("nzTooltipTitle",e.xi3(2,2,null==s.profile?null:s.profile.joined_date,"medium")),e.xp6(2),e.hij(" Joined ",e.lcZ(4,5,null==s.profile?null:s.profile.joined_date)," ")}}function f(i,a){if(1&i&&(e.TgZ(0,"p",25)(1,"small",26),e.ALo(2,"date"),e._uU(3),e.ALo(4,"fromNow"),e.qZA()()),2&i){const s=e.oxw();e.xp6(1),e.s9C("nzTooltipTitle",e.xi3(2,2,null==s.profile?null:s.profile.last_updated,"medium")),e.xp6(2),e.hij(" Last updated ",e.lcZ(4,5,null==s.profile?null:s.profile.last_updated)," ")}}let F=(()=>{var i;class a{constructor(t,n,o,r,l,d){this.fb=t,this.api=n,this.app=o,this.auth=r,this.attachmentsApi=l,this.ref=d,this.model={},this.loading=!1,this.updating=!1,this.uploading=!1,this.avatarUrl=null,this.app.setTitle("Profile Settings"),A.s.track(I.S5),this.form=this.fb.group({name:[null,p.kI.required],email:[null,p.kI.required]}),this.avatarUrl=this.profile?.avatar_url||null,this.form.controls.email.disable()}get profile(){return this.auth.getCurrentSession()}ngOnInit(){var t=this;return(0,m.Z)(function*(){yield t.get()})()}isInvalidForm(){return this.form.invalid}get(){var t=this;return(0,m.Z)(function*(){t.loading=!0;try{const n=yield t.api.get();n.done&&(t.model=n.body,t.form.controls.name.setValue(t.model.name),t.form.controls.email.setValue(t.model.email),t.profile?.is_google&&t.form.controls.email.disable()),t.loading=!1}catch(n){t.loading=!1,(0,b.tu)(n)}})()}submit(){var t=this;return(0,m.Z)(function*(){if(t.form.invalid)t.app.displayErrorsOf(t.form);else{t.updating=!0;try{const n={name:t.form.controls.name.value};(yield t.api.update(n)).done&&(A.s.track(I.AE),yield t.get(),yield t.auth.authorize(),(0,me.Bk)()),t.updating=!1}catch(n){t.updating=!1,(0,b.tu)(n)}}})()}uploadFile(t){var n=this;return(0,m.Z)(function*(){if(n.uploading)return;try{const r=t.files||[];if(!r||!r.length)return;const l=r[0];n.uploading=!0;const d=yield(0,b.y3)(l),_=yield n.attachmentsApi.createAvatarAttachment({file:d,file_name:l.name,size:l.size});_.done&&(A.s.track(I.nX),yield n.auth.authorize(),(0,me.hQ)(),n.avatarUrl=_.body.url),n.uploading=!1}catch{n.uploading=!1}const o=new DataTransfer;t.files=o.files})()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(p.qu),e.Y36(he.G),e.Y36(U.z),e.Y36(w.e),e.Y36(Se.J),e.Y36(e.z2F))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-profile"]],decls:26,vars:16,consts:[[3,"nzActive","nzLoading"],[1,"ant-upload-list","ant-upload-list-picture-card","mb-3"],[1,"ant-upload","ant-upload-select","ant-upload-select-picture-card",2,"position","relative"],["tabindex","0","role","button","nz-tooltip","",1,"ant-upload",3,"nzTooltipTitle","click"],["type","file","accept","image/png, image/jpg, image/jpeg",2,"display","none",3,"change"],["fileInput",""],["class","avatar-upload-icon",4,"ngIf"],["style","width: 100%",3,"alt","src",4,"ngIf"],["nz-form","","nzLayout","vertical",3,"formGroup","ngSubmit"],["nzFor","name","nzRequired",""],["nzErrorTip","Name is required!",3,"nzSm","nzXs"],["id","name","nz-input","",3,"formControlName"],["nzFor","email","nzRequired",""],["nzErrorTip","The input is not valid E-mail!",3,"nzSm","nzXs"],["id","email","nz-input","","readonly","",3,"formControlName"],["nz-row","",1,"register-area","mb-3"],["nz-button","","nzType","primary",3,"disabled","nzLoading"],["class","mb-0","nz-typography","","nzType","secondary",4,"ngIf"],[1,"avatar-upload-icon"],["nz-icon","","nzType","plus","nzTheme","outline",4,"ngIf"],["nz-icon","","nzType","loading","nzTheme","outline",4,"ngIf"],[2,"margin-top","8px"],["nz-icon","","nzType","plus","nzTheme","outline"],["nz-icon","","nzType","loading","nzTheme","outline"],[2,"width","100%",3,"alt","src"],["nz-typography","","nzType","secondary",1,"mb-0"],["nz-tooltip","","nzTooltipPlacement","right",3,"nzTooltipTitle"]],template:function(t,n){if(1&t){const o=e.EpF();e.TgZ(0,"nz-card")(1,"nz-skeleton",0)(2,"div",1)(3,"div",2)(4,"div",3),e.NdJ("click",function(){e.CHM(o);const l=e.MAs(6);return e.KtG(l.click())}),e.TgZ(5,"input",4,5),e.NdJ("change",function(){e.CHM(o);const l=e.MAs(6);return e.KtG(n.uploadFile(l))}),e.qZA(),e.YNc(7,u,5,3,"div",6),e.YNc(8,g,1,2,"img",7),e.qZA()()(),e.TgZ(9,"form",8),e.NdJ("ngSubmit",function(){return n.submit()}),e.TgZ(10,"nz-form-item")(11,"nz-form-label",9),e._uU(12,"Name"),e.qZA(),e.TgZ(13,"nz-form-control",10),e._UZ(14,"input",11),e.qZA()(),e.TgZ(15,"nz-form-item")(16,"nz-form-label",12),e._uU(17,"E-mail"),e.qZA(),e.TgZ(18,"nz-form-control",13),e._UZ(19,"input",14),e.qZA()(),e.TgZ(20,"nz-form-item",15)(21,"nz-form-control")(22,"button",16),e._uU(23," Save Changes "),e.qZA()()(),e.YNc(24,C,5,7,"p",17),e.YNc(25,f,5,7,"p",17),e.qZA()()()}2&t&&(e.xp6(1),e.Q6J("nzActive",!0)("nzLoading",n.loading||n.updating),e.xp6(3),e.Q6J("nzTooltipTitle",n.avatarUrl?"Click to change the avatar":"Click to upload an avatar"),e.xp6(3),e.Q6J("ngIf",!n.avatarUrl||n.uploading),e.xp6(1),e.Q6J("ngIf",n.avatarUrl),e.xp6(1),e.Q6J("formGroup",n.form),e.xp6(4),e.Q6J("nzSm",14)("nzXs",24),e.xp6(1),e.Q6J("formControlName","name"),e.xp6(4),e.Q6J("nzSm",14)("nzXs",24),e.xp6(1),e.Q6J("formControlName","email"),e.xp6(3),e.Q6J("disabled",n.isInvalidForm()||n.loading)("nzLoading",n.updating),e.xp6(2),e.Q6J("ngIf",null==n.profile?null:n.profile.joined_date),e.xp6(1),e.Q6J("ngIf",null==n.profile?null:n.profile.last_updated))},dependencies:[h.O5,V.bd,M.Ls,p._Y,p.Fj,p.JJ,p.JL,p.sg,p.u,Z.t3,Z.SK,x.Lr,x.Nx,x.iK,x.Fd,P.Zp,O.ix,v.w,L.dQ,Q.ZU,J.SY,$.ng,h.uU,ge.d],styles:["[nz-form][_ngcontent-%COMP%]{max-width:600px}.avatar-upload-icon[_ngcontent-%COMP%]{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.6196078431)}"]}),a})();var pe=c(83916),te=c(72042),z=c(13740),fe=c(81221),H=c(92574),Me=c(39787),ie=c(33640);function Ke(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",19),e.NdJ("click",function(){e.CHM(s);const n=e.oxw(2);return e.KtG(n.editTeam(n.currentTeam))}),e._UZ(1,"span",20),e.qZA()}}function We(i,a){if(1&i&&(e.TgZ(0,"tr",14)(1,"td"),e._UZ(2,"nz-badge",15),e.qZA(),e.TgZ(3,"td"),e._uU(4),e.ALo(5,"fromNow"),e.qZA(),e.TgZ(6,"td"),e._uU(7),e.qZA(),e.TgZ(8,"td",16)(9,"div",17)(10,"nz-space"),e.YNc(11,Ke,2,0,"button",18),e.qZA()()()()),2&i){const s=e.oxw();e.xp6(2),e.Q6J("nzText",s.currentTeam.name),e.xp6(2),e.Oqu(e.lcZ(5,4,s.currentTeam.created_at)),e.xp6(3),e.Oqu(s.currentTeam.owns_by),e.xp6(4),e.Q6J("ngIf",s.currentTeam.owner)}}function Xe(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",19),e.NdJ("click",function(){e.CHM(s);const n=e.oxw().$implicit,o=e.oxw();return e.KtG(o.editTeam(n))}),e._UZ(1,"span",20),e.qZA()}}function et(i,a){if(1&i&&(e.TgZ(0,"tr",14)(1,"td"),e._uU(2),e.qZA(),e.TgZ(3,"td"),e._uU(4),e.ALo(5,"fromNow"),e.qZA(),e.TgZ(6,"td"),e._uU(7),e.qZA(),e.TgZ(8,"td",16)(9,"div",17)(10,"nz-space"),e.YNc(11,Xe,2,0,"button",18),e.qZA()()()()),2&i){const s=a.$implicit;e.xp6(2),e.Oqu(s.name),e.xp6(2),e.Oqu(e.lcZ(5,4,s.created_at)),e.xp6(3),e.Oqu(s.owns_by),e.xp6(4),e.Q6J("ngIf",s.owner)}}function tt(i,a){if(1&i&&e._UZ(0,"worklenz-toggle-menu-button",1),2&i){const s=e.oxw();e.Q6J("key",s.menu.TEAMS_MENU)}}function nt(i,a){1&i&&(e.TgZ(0,"div",21)(1,"div",22),e._UZ(2,"img",23),e.qZA(),e.TgZ(3,"span",24),e._uU(4,"No teams found."),e.qZA()())}function it(i,a){}function st(i,a){if(1&i&&(e.ynx(0),e.TgZ(1,"form",25)(2,"nz-form-item")(3,"nz-form-label",26),e._uU(4,"Name"),e.qZA(),e.TgZ(5,"nz-form-control",27),e._UZ(6,"input",28),e.qZA()()(),e.BQk()),2&i){const s=e.oxw();e.xp6(1),e.Q6J("formGroup",s.form)("nzLayout","vertical"),e.xp6(2),e.Q6J("nzSpan",null),e.xp6(2),e.Q6J("nzSpan",null),e.xp6(1),e.Q6J("formControlName","name")}}const ot=function(){return{rows:5}};let at=(()=>{var i;class a{constructor(t,n,o,r,l,d){this.teamsApi=t,this.settingsApi=n,this.fb=o,this.app=r,this.menu=l,this.auth=d,this.teams=[],this.currentTeam=null,this.loading=!1,this.updating=!1,this.showTeamEditModal=!1,this.editingTeamId=null,this.app.setTitle("Teams"),A.s.track(I.jD),this.form=this.fb.group({name:[null,p.kI.required]})}ngOnInit(){this.getTeams()}getTeams(){var t=this;return(0,m.Z)(function*(){try{t.loading=!0;const n=yield t.teamsApi.get();n.done&&(t.teams=n.body.filter(o=>o.id!==t.auth.getCurrentSession()?.team_id),t.currentTeam=n.body.filter(o=>o.id===t.auth.getCurrentSession()?.team_id)[0]),t.loading=!1}catch(n){t.loading=!1,(0,b.tu)(n)}})()}updateTeamName(t,n){var o=this;return(0,m.Z)(function*(){try{if(!t||!n)return;o.updating=!0,(yield o.settingsApi.updateTeamName(t,{name:n})).done?window.location.reload():o.updating=!1}catch(r){o.updating=!1,(0,b.tu)(r)}})()}closeModal(){this.showTeamEditModal=!1,this.editingTeamId=null,this.form.reset()}handleOk(){var t=this;return(0,m.Z)(function*(){t.form.valid&&t.editingTeamId&&(yield t.updateTeamName(t.editingTeamId,t.form.controls.name.value))})()}editTeam(t){!t||!t.id||(this.showTeamEditModal=!0,this.editingTeamId=t.id,this.form.controls.name.setValue(t.name))}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(pe.S),e.Y36(he.G),e.Y36(p.qu),e.Y36(U.z),e.Y36(te.h),e.Y36(w.e))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-teams"]],decls:28,vars:14,consts:[[1,"px-0",3,"nzGhost"],[3,"key"],[3,"nzActive","nzLoading","nzParagraph"],["nzPaginationType","small","nzSize","small",1,"custom-table",3,"nzData","nzLoading","nzNoResult"],["table",""],["scope","col"],["colspan","2","nzAlign","left","scope","col"],["class","actions-row",4,"ngIf"],["class","actions-row",4,"ngFor","ngForOf"],["actionsTemplate",""],["noDataTemplate",""],["noDataTemplate1",""],["nzOkText","Update Name","nzTitle","Edit Team Name",3,"nzVisible","nzOkLoading","nzOnCancel","nzOnOk","nzVisibleChange"],[4,"nzModalContent"],[1,"actions-row"],["nzColor","#52c41a",3,"nzText"],["nzAlign","center",1,"actions-col"],[1,"actions"],["nz-button","","nz-tooltip","","nzSize","small","nzTooltipPlacement","top","nzTooltipTitle","Edit","nzType","default",3,"click",4,"ngIf"],["nz-button","","nz-tooltip","","nzSize","small","nzTooltipPlacement","top","nzTooltipTitle","Edit","nzType","default",3,"click"],["nz-icon","","nzType","edit"],[1,"pt-4","pb-5"],[1,"no-data-img-holder","mx-auto","mb-3"],["src","/assets/images/empty-box.webp","alt","",1,"img-fluid"],["nz-typography","",1,"no-data-text"],["nz-form","",3,"formGroup","nzLayout"],["nzRequired","",3,"nzSpan"],["nzErrorTip","Please enter a name!",3,"nzSpan"],["nz-input","","placeholder","Name",3,"formControlName"]],template:function(t,n){if(1&t&&(e.TgZ(0,"nz-page-header",0)(1,"nz-page-header-title"),e._uU(2),e.qZA(),e.TgZ(3,"nz-page-header-extra"),e._UZ(4,"worklenz-toggle-menu-button",1),e.qZA()(),e.TgZ(5,"nz-card")(6,"nz-skeleton",2)(7,"nz-table",3,4)(9,"thead")(10,"tr")(11,"th",5),e._uU(12,"Name"),e.qZA(),e.TgZ(13,"th",5),e._uU(14,"Created"),e.qZA(),e.TgZ(15,"th",6),e._uU(16,"Owns By"),e.qZA()()(),e.TgZ(17,"tbody"),e.YNc(18,We,12,6,"tr",7),e.YNc(19,et,12,6,"tr",8),e.qZA()()()(),e.YNc(20,tt,1,1,"ng-template",null,9,e.W1O),e.YNc(22,nt,5,0,"ng-template",null,10,e.W1O),e.YNc(24,it,0,0,"ng-template",null,11,e.W1O),e.TgZ(26,"nz-modal",12),e.NdJ("nzOnCancel",function(){return n.closeModal()})("nzOnOk",function(){return n.handleOk()})("nzVisibleChange",function(r){return n.showTeamEditModal=r}),e.YNc(27,st,7,5,"ng-container",13),e.qZA()),2&t){const o=e.MAs(8),r=e.MAs(23),l=e.MAs(25);e.Q6J("nzGhost",!1),e.xp6(2),e.hij("",n.teams.length?n.teams.length+1:0," Teams"),e.xp6(2),e.Q6J("key",n.menu.TEAMS_MENU),e.xp6(2),e.Q6J("nzActive",!0)("nzLoading",n.loading)("nzParagraph",e.DdM(13,ot)),e.xp6(1),e.Q6J("nzData",n.teams)("nzLoading",n.loading)("nzNoResult",n.currentTeam?l:r),e.xp6(11),e.Q6J("ngIf",n.currentTeam),e.xp6(1),e.Q6J("ngForOf",o.data),e.xp6(7),e.Q6J("nzVisible",n.showTeamEditModal)("nzOkLoading",n.updating)}},dependencies:[h.sg,h.O5,V.bd,Y.$O,Y.u9,Y.Jp,M.Ls,p._Y,p.Fj,p.JJ,p.JL,p.sg,p.u,Z.t3,Z.SK,x.Lr,x.Nx,x.iK,x.Fd,P.Zp,O.ix,v.w,L.dQ,z.N8,z.Uo,z._C,z.Om,z.p0,z.$Z,z.UX,fe.du,fe.Hf,Q.ZU,H.NU,J.SY,$.ng,Me.l,ie.x7,ge.d],styles:[".no-data-img-holder[_ngcontent-%COMP%]{width:100px;margin-top:42px}"]}),a})();var j=c(21406),le=c(26857),ce=c(69862);let rt=(()=>{var i;class a extends le.P{constructor(t){super(),this.http=t}changePassword(t){return this._post(this.http,`${this.API_BASE_URL}/change-password`,t)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.LFG(ce.eN))},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})();function lt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"span",15),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.oldPasswordVisible=!n.oldPasswordVisible)}),e.qZA()}if(2&i){const s=e.oxw();e.Q6J("nzType",s.oldPasswordVisible?"eye-invisible":"eye")}}function ct(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"span",15),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.newPasswordVisible=!n.newPasswordVisible)}),e.qZA()}if(2&i){const s=e.oxw();e.Q6J("nzType",s.newPasswordVisible?"eye-invisible":"eye")}}function pt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"span",15),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.confirmPasswordVisible=!n.confirmPasswordVisible)}),e.qZA()}if(2&i){const s=e.oxw();e.Q6J("nzType",s.confirmPasswordVisible?"eye-invisible":"eye")}}let dt=(()=>{var i;class a{constructor(t,n,o){this.usersService=t,this.fb=n,this.app=o,this.oldPasswordVisible=!1,this.newPasswordVisible=!1,this.confirmPasswordVisible=!1,this.showTaskModal=!1,this.loading=!1,this.passwordPolicy=j.QW,this.app.setTitle("Change Password"),A.s.track(I.yv),this.passwordChangeForm=this.fb.group({password:[null,[p.kI.required]],new_password:[null,[p.kI.required]],confirm_password:[null,[p.kI.required]]},{validators:[this.confirmPasswordsValidator]})}confirmPasswordsValidator(t){return(0,m.Z)(function*(){return t.controls.confirm_password.value!==t.controls.new_password.value?t.controls.confirm_password.setErrors({passwordMismatch:!0}):t.controls.confirm_password})()}submitForm(){var t=this;return(0,m.Z)(function*(){const n={password:t.passwordChangeForm.value.password,new_password:t.passwordChangeForm.value.new_password,confirm_password:t.passwordChangeForm.value.confirm_password};n.password&&n.new_password&&n.confirm_password&&(t.passwordChangeForm.setErrors({invalid:!1}),(yield t.usersService.changePassword(n)).done&&(t.passwordChangeForm.reset(),t.showTaskModal=!1))})()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(rt),e.Y36(p.qu),e.Y36(U.z))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-change-password"]],decls:35,vars:26,consts:[["nz-form","",3,"formGroup","nzLayout","submit"],[3,"nzSm","nzXs","nzErrorTip"],["nzRequired",""],[3,"nzSuffix"],["autocomplete","off","nz-input","","placeholder","Enter your current password",3,"type","formControlName"],["suffixTemplateOld",""],["autocomplete","off","nz-input","","placeholder","New Password",3,"type","formControlName"],["suffixTemplateNew",""],[1,"mb-2"],[3,"nzErrorTip","nzSm","nzXs"],["autocomplete","off","nz-input","","placeholder","Confirm New Password",3,"type","formControlName"],["suffixTemplateConfirm",""],[3,"nzSm","nzXs"],["nz-typography","",1,"d-block",2,"font-size","12px",3,"nzType"],["nz-button","","nzType","primary","type","submit"],["nz-icon","",3,"nzType","click"]],template:function(t,n){if(1&t&&(e.TgZ(0,"nz-card")(1,"form",0),e.NdJ("submit",function(){return n.submitForm()}),e.TgZ(2,"nz-form-item")(3,"nz-form-control",1)(4,"nz-form-label",2),e._uU(5,"Current Password"),e.qZA(),e.TgZ(6,"nz-input-group",3),e._UZ(7,"input",4),e.qZA(),e.YNc(8,lt,1,1,"ng-template",null,5,e.W1O),e.qZA()(),e.TgZ(10,"nz-form-item")(11,"nz-form-control",1)(12,"nz-form-label",2),e._uU(13,"New Password"),e.qZA(),e.TgZ(14,"nz-input-group",3),e._UZ(15,"input",6),e.qZA(),e.YNc(16,ct,1,1,"ng-template",null,7,e.W1O),e.qZA()(),e.TgZ(18,"nz-form-item",8)(19,"nz-form-control",9)(20,"nz-form-label",2),e._uU(21,"Confirm New Password"),e.qZA(),e.TgZ(22,"nz-input-group",3),e._UZ(23,"input",10),e.qZA(),e.YNc(24,pt,1,1,"ng-template",null,11,e.W1O),e.qZA()(),e.TgZ(26,"nz-form-item",8)(27,"nz-form-control",12)(28,"span",13),e._uU(29),e.ALo(30,"lowercase"),e.qZA()()(),e.TgZ(31,"nz-form-item")(32,"nz-form-control")(33,"button",14),e._uU(34," Update "),e.qZA()()()()()),2&t){const o=e.MAs(9),r=e.MAs(17),l=e.MAs(25);let d;e.xp6(1),e.Q6J("formGroup",n.passwordChangeForm)("nzLayout","vertical"),e.xp6(2),e.Q6J("nzSm",14)("nzXs",24)("nzErrorTip","Please input your old password!"),e.xp6(3),e.Q6J("nzSuffix",o),e.xp6(1),e.Q6J("type",n.oldPasswordVisible?"text":"password")("formControlName","password"),e.xp6(4),e.Q6J("nzSm",14)("nzXs",24)("nzErrorTip","Please input your new password!"),e.xp6(3),e.Q6J("nzSuffix",r),e.xp6(1),e.Q6J("type",n.newPasswordVisible?"text":"password")("formControlName","new_password"),e.xp6(4),e.Q6J("nzErrorTip",null!=(d=n.passwordChangeForm.get("confirm_password"))&&d.hasError("passwordMismatch")?"Passwords do not match!":"Please confirm your new password!")("nzSm",14)("nzXs",24),e.xp6(3),e.Q6J("nzSuffix",l),e.xp6(1),e.Q6J("type",n.confirmPasswordVisible?"text":"password")("formControlName","confirm_password"),e.xp6(4),e.Q6J("nzSm",14)("nzXs",24),e.xp6(1),e.Q6J("nzType","secondary"),e.xp6(1),e.hij("New password should be ",e.lcZ(30,24,n.passwordPolicy),"")}},dependencies:[V.bd,M.Ls,p._Y,p.Fj,p.JJ,p.JL,p.sg,p.u,Z.t3,Z.SK,x.Lr,x.Nx,x.iK,x.Fd,P.Zp,P.gB,P.ke,O.ix,v.w,L.dQ,Q.ZU,h.i8],styles:["[nz-form][_ngcontent-%COMP%]{max-width:600px}"]}),a})();var oe=c(59780);let ut=(()=>{var i;class a{constructor(t,n){this.auth=t,this.router=n}canActivate(t,n){return!this.auth.getCurrentSession()?.is_google||this.router.navigate(["/worklenz"])}}return(i=a).\u0275fac=function(t){return new(t||i)(e.LFG(w.e),e.LFG(N.F0))},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})(),mt=(()=>{var i;class a extends le.P{constructor(t){super(),this.http=t,this.root=`${this.API_BASE_URL}/timezones`}update(t){return this._put(this.http,this.root,t)}get(){return this._get(this.http,this.root)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.LFG(ce.eN))},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})();var K=c(9691);function ht(i,a){if(1&i&&(e.TgZ(0,"nz-option",9),e._uU(1),e.qZA()),2&i){const s=a.$implicit;e.Q6J("nzLabel",s)("nzValue",s),e.xp6(1),e.hij(" ",s," ")}}function gt(i,a){if(1&i&&(e.TgZ(0,"nz-option",9)(1,"div",10)(2,"span"),e._uU(3),e.qZA(),e.TgZ(4,"span",11),e._uU(5),e.qZA()()()),2&i){const s=a.$implicit;e.s9C("nzLabel",s.name),e.Q6J("nzValue",s.id),e.xp6(3),e.Oqu(s.name),e.xp6(2),e.Oqu(s.abbrev)}}let ft=(()=>{var i;class a{constructor(t,n,o,r){this.app=t,this.api=n,this.fb=o,this.auth=r,this.loading=!1,this.updating=!1,this.options=["English"],this.timezones=[],this.app.setTitle("Language & Region"),A.s.track(I.xH);const l=this.auth.getCurrentSession();this.form=this.fb.group({language:["English",p.kI.required],timezone:[l?.timezone||null,p.kI.required]})}ngOnInit(){this.get()}submit(){var t=this;return(0,m.Z)(function*(){if(t.form.invalid)return t.app.displayErrorsOf(t.form);try{t.updating=!0,(yield t.api.update(t.form.value)).done&&(yield t.auth.authorize()),t.updating=!1}catch{t.updating=!1}})()}get(){var t=this;return(0,m.Z)(function*(){try{t.loading=!0;const n=yield t.api.get();n.done&&(t.timezones=n.body),t.loading=!1}catch{t.loading=!1}})()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(U.z),e.Y36(mt),e.Y36(p.qu),e.Y36(w.e))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-language-and-region"]],decls:19,vars:13,consts:[[3,"nzActive","nzLoading"],["nz-form","","nzLayout","vertical",3,"formGroup","ngSubmit"],["nzFor","name","nzRequired",""],[3,"nzSm","nzXs"],[3,"formControlName"],["nzCustomContent","",3,"nzLabel","nzValue",4,"ngFor","ngForOf"],["nzShowSearch","",3,"formControlName"],["nz-row","",1,"register-area"],["nz-button","","nzType","primary",3,"disabled","nzLoading"],["nzCustomContent","",3,"nzLabel","nzValue"],[1,"d-flex","justify-content-between"],["nz-typography","","nzType","secondary"]],template:function(t,n){1&t&&(e.TgZ(0,"nz-card")(1,"nz-skeleton",0)(2,"form",1),e.NdJ("ngSubmit",function(){return n.submit()}),e.TgZ(3,"nz-form-item")(4,"nz-form-label",2),e._uU(5,"Language"),e.qZA(),e.TgZ(6,"nz-form-control",3)(7,"nz-select",4),e.YNc(8,ht,2,3,"nz-option",5),e.qZA()()(),e.TgZ(9,"nz-form-item")(10,"nz-form-label",2),e._uU(11,"Time zone"),e.qZA(),e.TgZ(12,"nz-form-control",3)(13,"nz-select",6),e.YNc(14,gt,6,4,"nz-option",5),e.qZA()()(),e.TgZ(15,"nz-form-item",7)(16,"nz-form-control")(17,"button",8),e._uU(18," Save Changes "),e.qZA()()()()()()),2&t&&(e.xp6(1),e.Q6J("nzActive",!0)("nzLoading",n.loading||n.updating),e.xp6(1),e.Q6J("formGroup",n.form),e.xp6(4),e.Q6J("nzSm",8)("nzXs",24),e.xp6(1),e.Q6J("formControlName","language"),e.xp6(1),e.Q6J("ngForOf",n.options),e.xp6(4),e.Q6J("nzSm",8)("nzXs",24),e.xp6(1),e.Q6J("formControlName","timezone"),e.xp6(1),e.Q6J("ngForOf",n.timezones),e.xp6(3),e.Q6J("disabled",n.loading)("nzLoading",n.updating))},dependencies:[h.sg,V.bd,p._Y,p.JJ,p.JL,p.sg,p.u,Z.t3,Z.SK,x.Lr,x.Nx,x.iK,x.Fd,O.ix,v.w,L.dQ,Q.ZU,$.ng,K.Ip,K.Vq]}),a})();var zt=c(8725),Oe=c(9172),E=c(62787),ze=c(55695),Te=c(19035);function Tt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"li",17),e.NdJ("click",function(){const o=e.CHM(s).$implicit,r=e.oxw().$implicit,l=e.oxw();return e.KtG(l.updateColorCode(r.id,o))}),e.TgZ(1,"nz-tag",18),e._uU(2),e.qZA()()}if(2&i){const s=a.$implicit,t=e.oxw().$implicit,n=e.oxw();e.xp6(1),e.Q6J("nzColor",s+n.alpha),e.xp6(1),e.Oqu(t.name)}}function Ct(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"tr",8)(1,"td")(2,"nz-tag",9),e._uU(3),e.qZA(),e.TgZ(4,"nz-dropdown-menu",null,10)(6,"ul",11),e.YNc(7,Tt,3,2,"li",12),e.qZA()()(),e.TgZ(8,"td"),e._uU(9),e.qZA(),e.TgZ(10,"td",13)(11,"div",14)(12,"nz-space")(13,"button",15),e.NdJ("nzOnConfirm",function(){const o=e.CHM(s).$implicit,r=e.oxw();return e.KtG(r.deleteLabel(o))}),e._UZ(14,"span",16),e.qZA()()()()()}if(2&i){const s=a.$implicit,t=e.MAs(5),n=e.oxw();e.xp6(2),e.Q6J("nzColor",s.color_code+n.alpha)("nzDropdownMenu",t)("nzTooltipTitle","Click to change color")("nzTrigger","click"),e.xp6(1),e.Oqu(s.name),e.xp6(4),e.Q6J("ngForOf",n.colorCodes),e.xp6(2),e.Oqu(s.usage),e.xp6(4),e.Q6J("nzOkText","Yes")("nzPopconfirmTitle","Are you sure?")("nzSize","small")("nzTooltipPlacement","top")("nzTooltipTitle","Delete")("nzType","default")}}function kt(i,a){1&i&&e._UZ(0,"span",25)}function xt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"form",21)(1,"nz-input-group",22)(2,"input",23),e.NdJ("ngModelChange",function(n){e.CHM(s);const o=e.oxw(2);return e.KtG(o.labelsSearch=n)})("ngModelChange",function(n){e.CHM(s);const o=e.oxw(2);return e.KtG(o.searchLabels(n))}),e.qZA()(),e.YNc(3,kt,1,0,"ng-template",null,24,e.W1O),e.qZA()}if(2&i){const s=e.MAs(4),t=e.oxw(2);e.Q6J("nzLayout","vertical"),e.xp6(1),e.Q6J("nzSuffix",s),e.xp6(1),e.Q6J("ngModel",t.labelsSearch)}}function bt(i,a){if(1&i&&e._UZ(0,"worklenz-toggle-menu-button",26),2&i){const s=e.oxw(2);e.Q6J("key",s.menu.LABELS_MENU)}}function vt(i,a){1&i&&(e.TgZ(0,"nz-space"),e.YNc(1,xt,5,3,"form",19),e.YNc(2,bt,1,1,"worklenz-toggle-menu-button",20),e.qZA())}const wt=function(){return{rows:5}};let yt=(()=>{var i;class a{constructor(t,n,o,r,l){this.app=t,this.api=n,this.searchPipe=o,this.cdr=r,this.menu=l,this.colorCodes=j.lD,this.labels=[],this.filteredLabels=[],this.loading=!1,this.alpha=j.Yj,this.labelsSearch=null,this.app.setTitle("Manage Labels"),A.s.track(I.Hw)}ngOnInit(){this.get()}trackByFn(t,n){return n.id}get(){var t=this;return(0,m.Z)(function*(){try{t.loading=!0;const n=yield t.api.get();n.done&&(t.labels=n.body,t.filteredLabels=t.labels),t.loading=!1}catch(n){t.loading=!1,(0,b.tu)(n)}t.cdr.detectChanges()})()}deleteLabel(t){var n=this;return(0,m.Z)(function*(){if(t?.id){try{n.loading=!0,(yield n.api.deleteById(t.id)).done&&(A.s.track(I.aJ),n.get()),n.loading=!1}catch(o){n.loading=!1,(0,b.tu)(o)}n.cdr.detectChanges()}})()}updateColorCode(t,n){var o=this;return(0,m.Z)(function*(){if(t&&n)try{if((yield o.api.updateColor(t,n)).done){const l=o.labels.find(d=>d.id===t);l&&(l.color_code=n),o.cdr.markForCheck()}}catch(r){(0,b.tu)(r)}})()}searchLabels(t){this.filteredLabels=this.searchPipe.transform(this.labels,t||null),this.cdr.markForCheck()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(U.z),e.Y36(zt.u),e.Y36(Oe.g),e.Y36(e.sBO),e.Y36(te.h))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-labels"]],decls:14,vars:11,consts:[[3,"nzExtra"],[3,"nzActive","nzLoading","nzParagraph"],["nzPaginationType","small","nzSize","small",1,"custom-table",3,"nzData","nzLoading","nzNoResult"],["table",""],["scope","col","nzAlign","left",3,"nzWidth"],["scope","col","nzAlign","left","colspan","2"],["class","actions-row",4,"ngFor","ngForOf","ngForTrackBy"],["actionsTemplate",""],[1,"actions-row"],["nz-dropdown","","nz-tooltip","",1,"text-dark","m-0",2,"cursor","pointer",3,"nzColor","nzDropdownMenu","nzTooltipTitle","nzTrigger"],["colorDropdown","nzDropdownMenu"],["nz-menu","","nzSelectable","",2,"max-height","200px","overflow","hidden","overflow-y","auto"],["nz-menu-item","",3,"click",4,"ngFor","ngForOf"],["nzAlign","center",1,"actions-col"],[1,"actions"],["nz-button","","nz-popconfirm","","nz-tooltip","",3,"nzOkText","nzPopconfirmTitle","nzSize","nzTooltipPlacement","nzTooltipTitle","nzType","nzOnConfirm"],["nz-icon","","nzType","delete"],["nz-menu-item","",3,"click"],[1,"text-dark","m-0","w-100",3,"nzColor"],["nz-form","",3,"nzLayout",4,"nzSpaceItem"],[3,"key",4,"nzSpaceItem"],["nz-form","",3,"nzLayout"],[3,"nzSuffix"],["nz-input","","name","search","placeholder","Search by name","type","text",3,"ngModel","ngModelChange"],["suffixIconSearch",""],["nz-icon","","nzType","search"],[3,"key"]],template:function(t,n){if(1&t&&(e.TgZ(0,"nz-card",0)(1,"nz-skeleton",1)(2,"nz-table",2,3)(4,"thead")(5,"tr")(6,"th",4),e._uU(7,"Label"),e.qZA(),e.TgZ(8,"th",5),e._uU(9,"Associated tasks"),e.qZA()()(),e.TgZ(10,"tbody"),e.YNc(11,Ct,15,13,"tr",6),e.qZA()()()(),e.YNc(12,vt,3,0,"ng-template",null,7,e.W1O)),2&t){const o=e.MAs(3),r=e.MAs(13);e.Q6J("nzExtra",r),e.xp6(1),e.Q6J("nzActive",!0)("nzLoading",n.loading)("nzParagraph",e.DdM(10,wt)),e.xp6(1),e.Q6J("nzData",n.filteredLabels)("nzLoading",n.loading)("nzNoResult","Labels can be created while updating or creating tasks."),e.xp6(4),e.Q6J("nzWidth","300px"),e.xp6(5),e.Q6J("ngForOf",o.data)("ngForTrackBy",n.trackByFn)}},dependencies:[h.sg,V.bd,M.Ls,p._Y,p.Fj,p.JJ,p.JL,x.Lr,P.Zp,P.gB,P.ke,O.ix,v.w,L.dQ,z.N8,z.Uo,z._C,z.Om,z.p0,z.$Z,z.UX,T.wO,T.r9,H.NU,H.$1,J.SY,$.ng,p.On,p.F,Me.l,E.cm,E.RR,ze.j,Te.JW],changeDetection:0}),a})();var St=c(82803),Je=c(55522);function Pt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",12),e.NdJ("click",function(){e.CHM(s);const n=e.oxw().$implicit,o=e.oxw();return e.KtG(o.editTemplate(n.id))}),e._UZ(1,"span",13),e.qZA()}}function Mt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",14),e.NdJ("nzOnConfirm",function(){e.CHM(s);const n=e.oxw().$implicit,o=e.oxw();return e.KtG(o.deleteTemplate(n.id))}),e._UZ(1,"span",15),e.qZA()}}function Ot(i,a){if(1&i&&(e.TgZ(0,"tr",7)(1,"td"),e._uU(2),e.qZA(),e.TgZ(3,"td"),e._uU(4),e.ALo(5,"fromNow"),e.qZA(),e.TgZ(6,"td",8)(7,"div",9)(8,"nz-space"),e.YNc(9,Pt,2,0,"button",10),e.YNc(10,Mt,2,0,"button",11),e.qZA()()()()),2&i){const s=a.$implicit;e.xp6(2),e.Oqu(s.name),e.xp6(2),e.Oqu(e.lcZ(5,2,s.created_at))}}const At=function(){return{rows:5}};let It=(()=>{var i;class a{constructor(t,n,o,r,l){this.api=t,this.settingsApi=n,this.fb=o,this.app=r,this.menu=l,this.taskTemplates=[],this.loading=!1,this.updating=!1,this.drawerVisible=!1,this.editingTeamId=null,this.selectedTemplateId="",this.app.setTitle("Task Templates"),A.s.track(I.x0),this.form=this.fb.group({name:[null,p.kI.required]})}ngOnInit(){this.getTaskTemplates().then(t=>t)}getTaskTemplates(){var t=this;return(0,m.Z)(function*(){try{t.loading=!0;const n=yield t.api.get();n.done&&(t.taskTemplates=n.body),t.loading=!1}catch(n){t.loading=!1,(0,b.tu)(n)}})()}closeModal(){this.drawerVisible=!1}editTemplate(t){t&&(this.selectedTemplateId=t,this.drawerVisible=!0)}taskTemplateCancel(t){this.drawerVisible=t,this.selectedTemplateId=""}onCreateOrUpdate(){this.getTaskTemplates().then(t=>t)}deleteTemplate(t=""){var n=this;return(0,m.Z)(function*(){try{(yield n.api.delete(t)).done&&(A.s.track(I.O1),n.getTaskTemplates())}catch{}})()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(St.e),e.Y36(he.G),e.Y36(p.qu),e.Y36(U.z),e.Y36(te.h))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-task-templates"]],decls:13,vars:9,consts:[[3,"nzActive","nzLoading","nzParagraph"],["nzPaginationType","small","nzSize","small",1,"custom-table",3,"nzData","nzLoading"],["table",""],["scope","col"],["colspan","2","nzAlign","left","scope","col"],["class","actions-row",4,"ngFor","ngForOf"],[3,"drawerVisible","selectedTemplateId","onCancelClick","onCreateOrUpdate"],[1,"actions-row"],["nzAlign","center",1,"actions-col"],[1,"actions"],["nz-button","","nz-tooltip","","nzSize","small","nzTooltipPlacement","top","nzTooltipTitle","Edit","nzType","default",3,"click",4,"nzSpaceItem"],["nz-button","","nz-popconfirm","","nz-tooltip","","nzOkText","Yes","nzPopconfirmTitle","Are you sure?","nzSize","small","nzTooltipPlacement","top","nzTooltipTitle","Delete","nzType","default",3,"nzOnConfirm",4,"nzSpaceItem"],["nz-button","","nz-tooltip","","nzSize","small","nzTooltipPlacement","top","nzTooltipTitle","Edit","nzType","default",3,"click"],["nz-icon","","nzType","edit"],["nz-button","","nz-popconfirm","","nz-tooltip","","nzOkText","Yes","nzPopconfirmTitle","Are you sure?","nzSize","small","nzTooltipPlacement","top","nzTooltipTitle","Delete","nzType","default",3,"nzOnConfirm"],["nz-icon","","nzType","delete"]],template:function(t,n){if(1&t&&(e.TgZ(0,"nz-card")(1,"nz-skeleton",0)(2,"nz-table",1,2)(4,"thead")(5,"tr")(6,"th",3),e._uU(7,"Name"),e.qZA(),e.TgZ(8,"th",4),e._uU(9,"Created"),e.qZA()()(),e.TgZ(10,"tbody"),e.YNc(11,Ot,11,4,"tr",5),e.qZA()()()(),e.TgZ(12,"worklenz-task-template-drawer",6),e.NdJ("onCancelClick",function(r){return n.taskTemplateCancel(r)})("onCreateOrUpdate",function(){return n.onCreateOrUpdate()}),e.qZA()),2&t){const o=e.MAs(3);e.xp6(1),e.Q6J("nzActive",!0)("nzLoading",n.loading)("nzParagraph",e.DdM(8,At)),e.xp6(1),e.Q6J("nzData",n.taskTemplates)("nzLoading",n.loading),e.xp6(9),e.Q6J("ngForOf",o.data),e.xp6(1),e.Q6J("drawerVisible",n.drawerVisible)("selectedTemplateId",n.selectedTemplateId)}},dependencies:[h.sg,V.bd,M.Ls,O.ix,v.w,L.dQ,z.N8,z.Uo,z._C,z.Om,z.p0,z.$Z,z.UX,H.NU,H.$1,J.SY,$.ng,Je.x,Te.JW,ge.d]}),a})();var G=c(71993),Zt=c(45538),Lt=c(3278),de=c(64532),Ft=c(8689),Ge=c(72095),De=c(96928);function Nt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",14),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.refresh())}),e._UZ(1,"span",15),e.qZA()}if(2&i){const s=e.oxw();e.xp6(1),e.Q6J("nzSpin",s.loading)}}function Et(i,a){1&i&&e._UZ(0,"span",20)}function Ut(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"form",16),e.NdJ("ngSubmit",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.search())}),e.TgZ(1,"nz-input-group",17),e._UZ(2,"input",18),e.qZA(),e.YNc(3,Et,1,0,"ng-template",null,19,e.W1O),e.qZA()}if(2&i){const s=e.MAs(4),t=e.oxw();e.Q6J("formGroup",t.searchForm)("nzLayout","vertical"),e.xp6(1),e.Q6J("nzSuffix",s),e.xp6(1),e.Q6J("formControlName","search")}}function Jt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"span")(1,"button",21),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.openAddMemberForm())}),e._uU(2,"Add Member"),e.qZA()()}}function Gt(i,a){if(1&i&&e._UZ(0,"nz-avatar",33),2&i){const s=e.oxw().$implicit,t=e.oxw();e.Udp("background-color",s.avatar_url?"#ececec":t.getColor(s.name)),e.Q6J("nzSize",28)("nzText",s.name.charAt(0).toUpperCase())("nzSrc",s.avatar_url)}}function Dt(i,a){1&i&&(e.TgZ(0,"span",34),e._uU(1," (Deactivated)"),e.qZA())}function Yt(i,a){if(1&i&&(e.TgZ(0,"span"),e._uU(1),e.qZA()),2&i){const s=e.oxw().$implicit;e.xp6(1),e.Oqu(s.email)}}function Qt(i,a){1&i&&(e.TgZ(0,"span"),e._uU(1,"-"),e.qZA())}function Rt(i,a){if(1&i&&(e.TgZ(0,"span"),e._uU(1),e.TgZ(2,"small",35),e._uU(3,"(Pending Invitation)"),e.qZA()()),2&i){const s=e.oxw().$implicit;e.xp6(1),e.hij(" ",s.email," ")}}function Bt(i,a){if(1&i&&(e.TgZ(0,"span",36),e._uU(1),e.qZA()),2&i){const s=e.oxw().$implicit;e.xp6(1),e.Oqu(s.role_name)}}function Ht(i,a){if(1&i&&(e.TgZ(0,"span",37),e._uU(1),e.qZA()),2&i){const s=e.oxw().$implicit;e.xp6(1),e.Oqu(s.role_name)}}function $t(i,a){if(1&i&&(e.TgZ(0,"span",38),e._uU(1),e.qZA()),2&i){const s=e.oxw().$implicit;e.xp6(1),e.Oqu(s.role_name)}}function jt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",42),e.NdJ("click",function(){e.CHM(s);const n=e.oxw(2).$implicit,o=e.oxw();return e.KtG(o.editMember(n.id))}),e._UZ(1,"span",43),e.qZA()}2&i&&e.Q6J("nzTooltipPlacement","top")("nzTooltipTitle","Edit")("nzType","default")}function qt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",45),e.NdJ("nzOnConfirm",function(){e.CHM(s);const n=e.oxw(3).$implicit,o=e.oxw();return e.KtG(o.toggleMemberActiveStatus(n))}),e._UZ(1,"span",46),e.qZA()}if(2&i){const s=e.oxw(3).$implicit;e.Q6J("nzOkText","Yes")("nzPopconfirmTitle","Are you sure?")("nzTooltipPlacement","top")("nzTooltipTitle",s.active?"Deactivate":"Activate")("nzType","default")}}function Vt(i,a){if(1&i&&(e.TgZ(0,"div"),e.YNc(1,qt,2,5,"button",44),e.qZA()),2&i){const s=e.oxw(2).$implicit,t=e.oxw();e.xp6(1),e.Q6J("ngIf",s.id!==(null==t.profile?null:t.profile.team_member_id))}}function Kt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",47),e.NdJ("nzOnConfirm",function(){e.CHM(s);const n=e.oxw(2).$implicit,o=e.oxw();return e.KtG(o.deleteTeamMember(n.id,n.email))}),e._UZ(1,"span",48),e.qZA()}2&i&&e.Q6J("nzOkText","Yes")("nzPopconfirmTitle","Are you sure?")("nzSize","small")("nzTooltipPlacement","top")("nzTooltipTitle","Delete")("nzType","default")}function Wt(i,a){1&i&&(e.TgZ(0,"div",39)(1,"nz-space"),e.YNc(2,jt,2,3,"button",40),e.YNc(3,Vt,2,1,"div",4),e.YNc(4,Kt,2,6,"button",41),e.qZA()())}function Xt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"tr",22)(1,"td",23),e.NdJ("click",function(){const o=e.CHM(s).$implicit,r=e.oxw();return e.KtG(r.selectMember(o.id))}),e.YNc(2,Gt,1,5,"nz-avatar",24),e.TgZ(3,"nz-badge",25),e._uU(4),e.qZA(),e.YNc(5,Dt,2,0,"span",26),e.qZA(),e.TgZ(6,"td",23),e.NdJ("click",function(){const o=e.CHM(s).$implicit,r=e.oxw();return e.KtG(r.selectMember(o.id))}),e._uU(7),e.qZA(),e.TgZ(8,"td",23),e.NdJ("click",function(){const o=e.CHM(s).$implicit,r=e.oxw();return e.KtG(r.selectMember(o.id))}),e.YNc(9,Yt,2,1,"span",27),e.YNc(10,Qt,2,0,"span",27),e.YNc(11,Rt,4,1,"span",27),e.qZA(),e.TgZ(12,"td",23),e.NdJ("click",function(){const o=e.CHM(s).$implicit,r=e.oxw();return e.KtG(r.selectMember(o.id))}),e.YNc(13,Bt,2,1,"span",28),e.YNc(14,Ht,2,1,"span",29),e.YNc(15,$t,2,1,"span",30),e.qZA(),e.TgZ(16,"td",31),e.YNc(17,Wt,5,0,"div",32),e.qZA()()}if(2&i){const s=a.$implicit;e.xp6(2),e.Q6J("ngIf",s.name),e.xp6(1),e.Q6J("nzColor",s.is_online?"green":""),e.xp6(1),e.hij(" ",s.name," "),e.xp6(1),e.Q6J("ngIf",!s.active),e.xp6(2),e.Oqu(s.projects_count||0),e.xp6(2),e.Q6J("ngIf",s.email&&!s.pending_invitation),e.xp6(1),e.Q6J("ngIf",!s.email&&!s.pending_invitation),e.xp6(1),e.Q6J("ngIf",s.email&&s.pending_invitation),e.xp6(2),e.Q6J("ngIf",s.is_admin),e.xp6(1),e.Q6J("ngIf",s.is_owner),e.xp6(1),e.Q6J("ngIf",!s.is_admin&&!s.is_owner),e.xp6(2),e.Q6J("ngIf",!s.is_owner)}}const en=function(){return{rows:6}},tn=function(){return[]};let nn=(()=>{var i;class a{constructor(t,n,o,r,l,d,_,y){this.auth=t,this.api=n,this.jobTitlesApi=o,this.fb=r,this.app=l,this.router=d,this.utilsService=_,this.settingsService=y,this.model={},this.showTeamMemberModal=!1,this.loading=!1,this.selectedMemberId=null,this.total=0,this.pageSize=j.L8,this.pageIndex=1,this.paginationSizes=[5,10,15,20,50,100],this.sortField=null,this.sortOrder=null,this.app.setTitle("Team Members"),A.s.track(I.nW),this.searchForm=this.fb.group({search:[]}),this.searchForm.valueChanges.subscribe(()=>{this.search()}),this.settingsService.onNewMemberCreated.pipe((0,G.sL)()).subscribe(()=>{this.getTeamMembers()})}get profile(){return this.auth.getCurrentSession()}getTeamMembers(){var t=this;return(0,m.Z)(function*(){try{t.loading=!0;const n=yield t.api.get(t.pageIndex,t.pageSize,t.sortField,t.sortOrder,t.searchForm.value.search);n.done&&(t.model=n.body,t.total=t.model.total||0,t.utilsService.handleLastIndex(t.total,t.model.data?.length||0,t.pageIndex,o=>{t.pageIndex=o,t.getTeamMembers()})),t.loading=!1}catch(n){(0,b.tu)(n),t.loading=!1}})()}editMember(t){this.selectedMemberId=t||null,this.showTeamMemberModal=!0}reset(){this.selectedMemberId=null}deleteTeamMember(t,n){var o=this;return(0,m.Z)(function*(){if(t&&n)try{(yield o.api.delete(t,n)).done&&(A.s.track(I.OA),yield o.getTeamMembers(),o.auth.getCurrentSession()?.team_member_id===t&&window.location.reload())}catch(r){(0,b.tu)(r)}})()}selectValue(t){t&&t.select()}search(){var t=this;return(0,m.Z)(function*(){t.searchForm.value.search&&A.s.track(I.Q5),t.getTeamMembers()})()}selectMember(t){t&&(A.s.track(I.uc),this.editMember(t))}getColor(t){return j.Lj[t?.charAt(0).toUpperCase()||"A"]}onQueryParamsChange(t){var n=this;return(0,m.Z)(function*(){const{pageSize:o,pageIndex:r,sort:l}=t;n.pageIndex=r,n.pageSize=o;const d=l.find(_=>null!==_.value);n.sortField=d&&d.key||null,n.sortOrder=d&&d.value||"descend",yield n.getTeamMembers()})()}openAddMemberForm(){this.showTeamMemberModal=!0,A.s.track(I.oo)}refresh(){A.s.track(I.r9),this.getTeamMembers()}handleOnCreateOrUpdate(t){1==t&&A.s.track(I.YC),this.getTeamMembers()}toggleMemberActiveStatus(t){var n=this;return(0,m.Z)(function*(){if(t.id)try{(yield n.api.toggleMemberActiveStatus(t.id,t.active,t.email)).done&&(t.active&&A.s.track(I.ZH),t.active||A.s.track(I.aY),yield n.getTeamMembers())}catch(o){(0,b.tu)(o)}})()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(w.e),e.Y36(Zt.B),e.Y36(Lt.i),e.Y36(p.qu),e.Y36(U.z),e.Y36(N.F0),e.Y36(de.F),e.Y36(Ft.g))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-team-members"]],decls:26,vars:22,consts:[[1,"container"],[1,"px-0",3,"nzGhost"],["nz-button","","nz-tooltip","","nzShape","circle","nzTooltipTitle","Refresh members","nzType","default",3,"click",4,"nzSpaceItem"],["nz-form","",3,"formGroup","nzLayout","ngSubmit",4,"nzSpaceItem"],[4,"nzSpaceItem"],[3,"nzActive","nzLoading","nzParagraph"],["nzShowSizeChanger","","nzSize","small",1,"custom-table",3,"nzData","nzFrontPagination","nzLoading","nzPageIndex","nzPageSizeOptions","nzPageSize","nzTotal","nzQueryParams"],["table",""],["nzColumnKey","name","scope","col",3,"nzSortFn"],["nzColumnKey","projects_count","scope","col",3,"nzSortFn"],["nzColumnKey","email","scope","col",3,"nzSortFn"],["colspan","2","nzAlign","left","nzColumnKey","role_name","scope","col",3,"nzSortFn"],["class","actions-row",4,"ngFor","ngForOf"],[3,"show","memberId","onCancel","onCreateOrUpdate","showChange"],["nz-button","","nz-tooltip","","nzShape","circle","nzTooltipTitle","Refresh members","nzType","default",3,"click"],["nz-icon","","nzTheme","outline","nzType","sync",3,"nzSpin"],["nz-form","",3,"formGroup","nzLayout","ngSubmit"],[3,"nzSuffix"],["nz-input","","placeholder","Search by name","type","text",3,"formControlName"],["suffixIconSearch",""],["nz-icon","","nzType","search"],["nz-button","","nzType","primary",3,"click"],[1,"actions-row"],[1,"cursor-pointer",3,"click"],["class","me-2",3,"nzSize","nzText","background-color","nzSrc",4,"ngIf"],[1,"d-inline-flex","align-items-center","flex-row-reverse",2,"color","inherit",3,"nzColor"],["nz-typography","","nzType","warning",4,"ngIf"],[4,"ngIf"],["class","admin-role","nz-typography","",4,"ngIf"],["class","owner-role","nz-typography","",4,"ngIf"],["class","member-role","nz-typography","",4,"ngIf"],[1,"actions-col"],["class","actions",4,"ngIf"],[1,"me-2",3,"nzSize","nzText","nzSrc"],["nz-typography","","nzType","warning"],["nz-typography","","nzType","secondary"],["nz-typography","",1,"admin-role"],["nz-typography","",1,"owner-role"],["nz-typography","",1,"member-role"],[1,"actions"],["nz-button","","nz-tooltip","","nzSize","small",3,"nzTooltipPlacement","nzTooltipTitle","nzType","click",4,"nzSpaceItem"],["nz-button","","nz-popconfirm","","nz-tooltip","",3,"nzOkText","nzPopconfirmTitle","nzSize","nzTooltipPlacement","nzTooltipTitle","nzType","nzOnConfirm",4,"nzSpaceItem"],["nz-button","","nz-tooltip","","nzSize","small",3,"nzTooltipPlacement","nzTooltipTitle","nzType","click"],["nz-icon","","nzType","edit"],["nz-button","","nz-tooltip","","nz-popconfirm","","nzSize","small",3,"nzOkText","nzPopconfirmTitle","nzTooltipPlacement","nzTooltipTitle","nzType","nzOnConfirm",4,"ngIf"],["nz-button","","nz-tooltip","","nz-popconfirm","","nzSize","small",3,"nzOkText","nzPopconfirmTitle","nzTooltipPlacement","nzTooltipTitle","nzType","nzOnConfirm"],["nz-icon","","nzType","user-switch","nzTheme","outline"],["nz-button","","nz-popconfirm","","nz-tooltip","",3,"nzOkText","nzPopconfirmTitle","nzSize","nzTooltipPlacement","nzTooltipTitle","nzType","nzOnConfirm"],["nz-icon","","nzType","delete"]],template:function(t,n){if(1&t&&(e.TgZ(0,"div",0)(1,"nz-page-header",1)(2,"nz-page-header-title"),e._uU(3),e.qZA(),e.TgZ(4,"nz-page-header-extra")(5,"nz-space"),e.YNc(6,Nt,2,1,"button",2),e.YNc(7,Ut,5,4,"form",3),e.YNc(8,Jt,3,0,"span",4),e.qZA()()(),e.TgZ(9,"nz-card")(10,"nz-skeleton",5)(11,"nz-table",6,7),e.NdJ("nzQueryParams",function(r){return n.onQueryParamsChange(r)}),e.TgZ(13,"thead")(14,"tr")(15,"th",8),e._uU(16,"Name"),e.qZA(),e.TgZ(17,"th",9),e._uU(18,"Projects"),e.qZA(),e.TgZ(19,"th",10),e._uU(20,"Email"),e.qZA(),e.TgZ(21,"th",11),e._uU(22,"Team Access"),e.qZA()()(),e.TgZ(23,"tbody"),e.YNc(24,Xt,18,12,"tr",12),e.qZA()()()()(),e.TgZ(25,"worklenz-team-members-form",13),e.NdJ("onCancel",function(){return n.reset()})("onCreateOrUpdate",function(r){return n.handleOnCreateOrUpdate(r)})("showChange",function(r){return n.showTeamMemberModal=r}),e.qZA()),2&t){const o=e.MAs(12);e.xp6(1),e.Q6J("nzGhost",!1),e.xp6(2),e.AsE("",n.total||0," Member",n.total>1?"s":"",""),e.xp6(7),e.Q6J("nzActive",!0)("nzLoading",n.loading)("nzParagraph",e.DdM(20,en)),e.xp6(1),e.Q6J("nzData",n.model.data||e.DdM(21,tn))("nzFrontPagination",!1)("nzLoading",n.loading)("nzPageIndex",n.pageIndex)("nzPageSizeOptions",n.paginationSizes)("nzPageSize",n.pageSize)("nzTotal",n.total),e.xp6(4),e.Q6J("nzSortFn",!0),e.xp6(2),e.Q6J("nzSortFn",!0),e.xp6(2),e.Q6J("nzSortFn",!0),e.xp6(2),e.Q6J("nzSortFn",!0),e.xp6(3),e.Q6J("ngForOf",o.data),e.xp6(1),e.Q6J("show",n.showTeamMemberModal)("memberId",n.selectedMemberId)}},dependencies:[h.sg,h.O5,V.bd,Y.$O,Y.u9,Y.Jp,M.Ls,p._Y,p.Fj,p.JJ,p.JL,p.sg,p.u,x.Lr,P.Zp,P.gB,P.ke,O.ix,v.w,L.dQ,z.N8,z.qD,z.Uo,z._C,z.Om,z.p0,z.$Z,z.UX,Q.ZU,H.NU,H.$1,J.SY,$.ng,ie.x7,Te.JW,Ge.S,De.Dz]}),a})();var sn=c(80697);function on(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",10),e.NdJ("click",function(){e.CHM(s);const n=e.oxw().$implicit,o=e.oxw();return e.KtG(o.editTemplate(n.id,n.name))}),e._UZ(1,"span",11),e.qZA()}}function an(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",12),e.NdJ("nzOnConfirm",function(){e.CHM(s);const n=e.oxw().$implicit,o=e.oxw();return e.KtG(o.deleteTemplate(n.id))}),e._UZ(1,"span",13),e.qZA()}}function rn(i,a){if(1&i&&(e.TgZ(0,"tr",5)(1,"td"),e._uU(2),e.qZA(),e.TgZ(3,"td",6)(4,"div",7)(5,"nz-space"),e.YNc(6,on,2,0,"button",8),e.YNc(7,an,2,0,"button",9),e.qZA()()()()),2&i){const s=a.$implicit;e.xp6(2),e.Oqu(s.name)}}const ln=function(){return{rows:5}};let cn=(()=>{var i;class a{constructor(t,n,o){this.router=t,this.cdr=n,this.api=o,this.loading=!1,this.projectTemplates=[]}ngOnInit(){this.get()}get(){var t=this;return(0,m.Z)(function*(){try{t.loading=!0;const n=yield t.api.getWorklenzCustomTemplates();n.done&&(t.projectTemplates=n.body,t.loading=!1,t.cdr.markForCheck()),t.loading=!1,t.cdr.markForCheck()}catch(n){(0,b.tu)(n),t.cdr.markForCheck()}})()}editTemplate(t,n){!t||!n||this.router.navigate([`/worklenz/settings/project-templates/edit/${t}/${n}`])}deleteTemplate(t){var n=this;return(0,m.Z)(function*(){if(t)try{(yield n.api.delete(t)).done&&n.get()}catch(o){(0,b.tu)(o)}})()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(N.F0),e.Y36(e.sBO),e.Y36(sn.I))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-project-templates"]],decls:11,vars:7,consts:[[3,"nzActive","nzLoading","nzParagraph"],["nzPaginationType","small","nzSize","small",1,"custom-table",3,"nzData","nzLoading"],["table",""],["scope","col"],["class","actions-row",4,"ngFor","ngForOf"],[1,"actions-row"],["nzAlign","center",1,"actions-col"],[1,"actions"],["nz-button","","nz-tooltip","","nzSize","small","nzTooltipPlacement","top","nzTooltipTitle","Edit","nzType","default",3,"click",4,"nzSpaceItem"],["nz-button","","nz-popconfirm","","nz-tooltip","","nzOkText","Yes","nzPopconfirmTitle","Are you sure?","nzSize","small","nzTooltipPlacement","top","nzTooltipTitle","Delete","nzType","default",3,"nzOnConfirm",4,"nzSpaceItem"],["nz-button","","nz-tooltip","","nzSize","small","nzTooltipPlacement","top","nzTooltipTitle","Edit","nzType","default",3,"click"],["nz-icon","","nzType","edit"],["nz-button","","nz-popconfirm","","nz-tooltip","","nzOkText","Yes","nzPopconfirmTitle","Are you sure?","nzSize","small","nzTooltipPlacement","top","nzTooltipTitle","Delete","nzType","default",3,"nzOnConfirm"],["nz-icon","","nzType","delete"]],template:function(t,n){if(1&t&&(e.TgZ(0,"nz-card")(1,"nz-skeleton",0)(2,"nz-table",1,2)(4,"thead")(5,"tr")(6,"th",3),e._uU(7,"Name"),e.qZA(),e._UZ(8,"th",3),e.qZA()(),e.TgZ(9,"tbody"),e.YNc(10,rn,8,1,"tr",4),e.qZA()()()()),2&t){const o=e.MAs(3);e.xp6(1),e.Q6J("nzActive",!0)("nzLoading",n.loading)("nzParagraph",e.DdM(6,ln)),e.xp6(1),e.Q6J("nzData",n.projectTemplates)("nzLoading",n.loading),e.xp6(8),e.Q6J("ngForOf",o.data)}},dependencies:[h.sg,V.bd,M.Ls,O.ix,v.w,L.dQ,z.N8,z.Uo,z._C,z.Om,z.p0,z.$Z,z.UX,H.NU,H.$1,J.SY,$.ng,Te.JW],changeDetection:0}),a})();var Ce=c(26236),k=c(94489),R=c(78645),pn=c(65619),q=c(975);let ue=(()=>{var i;class a{constructor(){this._selectSbj$=new R.x,this._deselectSbj$=new R.x,this._deselectAllSbj$=new R.x,this._groupTaskMap=new Map,this._taskGroupIdsMap=new Map,this._selectedTasksMap=new Map,this._allTasksMap=new Map,this._subTasksMap=new Map,this._selectedCount=0}get tasks(){return this._allTasksMap}get onSelect$(){return this._selectSbj$.asObservable()}get onDeselect$(){return this._deselectSbj$.asObservable()}get onDeselectAll$(){return this._deselectAllSbj$.asObservable()}reset(){this._groupTaskMap.clear(),this._taskGroupIdsMap.clear(),this._selectedTasksMap.clear(),this._allTasksMap.clear(),this._subTasksMap.clear(),this._selectedCount=0}registerGroup(t){for(const n of t.tasks)this.add(t.id,n)}add(t,n){n.id&&(this.updateGroupTaskMap(t,n.id),this._taskGroupIdsMap.set(n.id,t),this._allTasksMap.set(n.id,n),n.parent_task_id&&this.updateSubtasksMap(n.parent_task_id,n))}addGroupTask(t,n){n.id&&this._taskGroupIdsMap.set(n.id,t)}has(t){return this._allTasksMap.has(t)}remove(t){t.id&&(this.deselectTask(t),this._taskGroupIdsMap.get(t.id),this._allTasksMap.delete(t.id))}updateGroupTaskMap(t,n,o){const r=this._groupTaskMap.get(t);r?("boolean"==typeof o?r[n]=o:delete r[n],this._groupTaskMap.set(t,r)):this._groupTaskMap.set(t,{[n]:o||!1})}updateSubtasksMap(t,n,o){const r=this._subTasksMap.get(t)||[];r.some(d=>d.id===n.id)||(r.push(n),this._subTasksMap.set(t,r))}selectTask(t){this._selectedTasksMap.get(t.id)||(this._selectedTasksMap.set(t.id,t),this._selectedCount++,this.updateGroupTaskMap(this._taskGroupIdsMap.get(t.id),t.id,!0),this._selectSbj$.next(t))}deselectTask(t){this._selectedTasksMap.has(t.id)&&(this._selectedTasksMap.delete(t.id),this._selectedCount--,this.updateGroupTaskMap(this._taskGroupIdsMap.get(t.id),t.id,!1),this._deselectSbj$.next(t))}deselectLocalGroups(){for(const[t,n]of this._groupTaskMap)for(const o in n)this.updateGroupTaskMap(t,o,!1)}deselectAll(){this._selectedTasksMap.size&&(this.deselectLocalGroups(),this._selectedTasksMap.clear(),this._selectedCount=0,this._deselectAllSbj$.next())}isAllSelected(t){const n=this._groupTaskMap.get(t);if(n){for(const o in n)if(!n[o])return!1;return!0}return!1}isAllDeselected(t){const n=this._groupTaskMap.get(t);if(n)for(const o in n)if(n[o])return!1;return!0}getSelectedCount(){return this._selectedCount}getGroupId(t){return this._taskGroupIdsMap.get(t)}getSelectedTasks(){const t=[];for(const[,n]of this._selectedTasksMap.entries())t.push(n);return t}getSelectedTaskIds(){const t=[];for(const[n]of this._selectedTasksMap.entries())t.push(n);return t}}return(i=a).\u0275fac=function(t){return new(t||i)},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})(),B=(()=>{var i;class a{get _currentGroup(){const t=localStorage.getItem("worklenz.pt-t-list.group_by");if(t){const n=this.GROUP_BY_OPTIONS.find(o=>o.value===t);if(n)return n}return this.GROUP_BY_OPTIONS[0]}set _currentGroup(t){localStorage.setItem("worklenz.pt-t-list.group_by",t.value)}set columns(t){this._cols=t,this.emitColsChange()}get columns(){return this._cols}set labels(t){this._labels=t,this.labelsSbj$.next()}get labels(){return this._labels}set priorities(t){this._priorities=t,this.prioritiesSbj$.next()}get priorities(){return this._priorities}set phases(t){this._phases=t,this.phasesSbj$.next()}get phases(){return this._phases}get onColumnsChange$(){return this.colsSbj$.asObservable()}get onLabelsChange$(){return this.labelsSbj$.asObservable()}get onStatusesChange$(){return this.statusesSbj$.asObservable()}get onPrioritiesChange$(){return this.prioritiesSbj$.asObservable()}get onContextMenu$(){return this.contextMenuSbj$.asObservable()}get onTaskAddOrDelete$(){return this.taskAddOrDeleteSbj$.asObservable()}get onGroupChange$(){return this.groupChangeSbj$.asObservable()}get onRefresh$(){return this.refreshSbj$.asObservable()}get onPhaseChange$(){return this.phasesSbj$.asObservable()}set statuses(t){this._statuses=t,this.statusesSbj$.next()}get statuses(){return this._statuses}get onRefreshSubtasksIncluded(){return this.refreshSubtasksIncludedSbj$.asObservable()}constructor(t,n){this.socket=t,this.map=n,this.colsSbj$=new R.x,this.labelsSbj$=new R.x,this.statusesSbj$=new R.x,this.prioritiesSbj$=new R.x,this.contextMenuSbj$=new R.x,this.taskAddOrDeleteSbj$=new pn.X(null),this.refreshSbj$=new R.x,this.groupChangeSbj$=new R.x,this.phasesSbj$=new R.x,this.updateGroupProgressSbj$=new R.x,this.refreshSubtasksIncludedSbj$=new R.x,this.HIGHLIGHT_COL_CLS="highlight-col",this.GROUP_BY_STATUS_VALUE="status",this.GROUP_BY_PRIORITY_VALUE="priority",this.GROUP_BY_PHASE_VALUE="phase",this.GROUP_BY_OPTIONS=[{label:"Status",value:this.GROUP_BY_STATUS_VALUE},{label:"Priority",value:this.GROUP_BY_PRIORITY_VALUE},{label:"Phase",value:this.GROUP_BY_PHASE_VALUE}],this.groups=[],this._templateId=null,this._cols=[],this._labels=[],this._statuses=[],this._priorities=[],this._phases=[],this.isSubtasksIncluded=!1}settemplateId(t){this._templateId=t}gettemplateId(){return this._templateId}setCurrentGroup(t){this._currentGroup=t}getCurrentGroup(){return this._currentGroup}emitColsChange(){this.colsSbj$.next()}emitOnContextMenu(t,n){this.contextMenuSbj$.next({event:t,task:n})}emitRefresh(){this.refreshSbj$.next()}emitGroupChange(t,n,o){this.groupChangeSbj$.next({groupId:t,taskId:n,color:o})}emitTaskAddOrDelete(t,n){this.taskAddOrDeleteSbj$.next({taskId:t,isSubTask:n})}emitRefreshSubtasksIncluded(){this.refreshSubtasksIncludedSbj$.next()}getGroupIdByGroupedColumn(t){const n=this.getCurrentGroup().value;return n===this.GROUP_BY_STATUS_VALUE?t.status:n===this.GROUP_BY_PRIORITY_VALUE?t.priority:n===this.GROUP_BY_PHASE_VALUE?t.phase_id:null}updateTaskGroup(t,n=!0){if(!t.id)return;const o=this.getGroupIdByGroupedColumn(t);o&&(this.deleteTask(t.id),this.addTask(t,o,n))}deleteTask(t,n=null){const o=this.map.getGroupId(t);if(!o||!t)return;const r=this.groups.find(d=>d.id===o);if(!r)return;const l=this.map.getSelectedTasks().find(d=>d.id===t);if(l?.is_sub_task){const d=r.tasks.find(_=>_.id===l.parent_task_id);if(d){const _=d.sub_tasks?.findIndex(y=>y.id===l.id);typeof _<"u"&&-1!==_&&(d.sub_tasks_count||(d.sub_tasks_count=0),d.sub_tasks_count=Math.max(+d.sub_tasks_count-1,0),d.sub_tasks?.splice(_,1),this.emitTaskAddOrDelete(d.id,!0))}this.map.remove(l)}else{const d=n??r.tasks.findIndex(_=>_.id===t);-1!==d&&(this.map.remove(r.tasks[d]),r.tasks.splice(d,1),this.emitTaskAddOrDelete(t,!1))}this.map.deselectAll()}removeSubtask(t,n=null){const o=this.map.getGroupId(t);if(!o||!t)return;const r=this.groups.find(d=>d.id===o);if(!r)return;const l=n??r.tasks.findIndex(d=>d.id===t);-1!==l&&(this.map.remove(r.tasks[l]),r.tasks.splice(l,1)),this.map.deselectAll()}addTask(t,n,o=!1){const r=this.groups.find(l=>l.id===n);if(r&&t.id){if(t.parent_task_id){const l=r.tasks.find(d=>d.id===t.parent_task_id);l&&(l.sub_tasks_count||(l.sub_tasks_count=0),l.sub_tasks_count=+l.sub_tasks_count+1,l.sub_tasks?.push(t))}else o?r.tasks.unshift(t):r.tasks.push(t);this.map.add(n,t),this.emitTaskAddOrDelete(t.parent_task_id,!!t.parent_task_id)}}reset(){this._cols=[],this._labels=[],this._statuses=[],this._priorities=[],this._templateId=null,this.groups=[],this.isSubtasksIncluded=!1}}return(i=a).\u0275fac=function(t){return new(t||i)(e.LFG(q.s),e.LFG(ue))},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})(),Ye=(()=>{var i;class a extends le.P{constructor(t){super(),this.http=t,this.root=`${this.API_BASE_URL}/pt-tasks`}getTaskList(t){const n=(0,b.UK)(t);return this._get(this.http,`${this.root}/list/${t.id}${n}`)}bulkDelete(t,n){return this._put(this.http,`${this.root}/bulk/delete`,t)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.LFG(ce.eN))},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})(),dn=(()=>{var i;class a extends le.P{constructor(t){super(),this.http=t,this.root=`${this.API_BASE_URL}/labels`}get(t=null){const n=(0,b.UK)({project:t});return this._get(this.http,`${this.root}${n}`)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.LFG(ce.eN))},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})(),Ae=(()=>{var i;class a extends le.P{constructor(t){super(),this.http=t,this.root=`${this.API_BASE_URL}/pt-statuses`}create(t){return this._post(this.http,this.root,t)}get(t){return this._get(this.http,`${this.root}?template_id=${t}`)}getById(t){return this._get(this.http,`${this.root}/${t}`)}getCategories(){return this._get(this.http,`${this.root}/categories`)}update(t,n){return this._put(this.http,`${this.root}/${t}`,n)}updateName(t,n){return this._put(this.http,`${this.root}/name/${t}`,n)}delete(t,n,o){return this._delete(this.http,`${this.root}/${t}?project=${n}&replace=${o||null}`)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.LFG(ce.eN))},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})(),un=(()=>{var i;class a extends le.P{constructor(t){super(),this.http=t,this.root=`${this.API_BASE_URL}/task-priorities`}get(){return this._get(this.http,this.root)}getById(t){return this._get(this.http,`${this.root}/${t}`)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.LFG(ce.eN))},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})(),Ie=(()=>{var i;class a extends le.P{constructor(t){super(),this.http=t,this.root=`${this.API_BASE_URL}/pt-task-phases`}create(t){const n=(0,b.UK)({id:t});return this._post(this.http,`${this.root}${n}`,{})}get(t){const n=(0,b.UK)({id:t});return this._get(this.http,`${this.root}${n}`)}getById(t,n){const o=(0,b.UK)({id:n});return this._get(this.http,`${this.root}/${t}${o}`)}updateLabel(t,n){return this._put(this.http,`${this.root}/label/${t}`,{name:n})}update(t,n){const o=(0,b.UK)({id:t});return this._put(this.http,`${this.root}/${n.id}${o}`,n)}updateColor(t,n){const o=(0,b.UK)({id:t,current_project_id:t});return this._put(this.http,`${this.root}/change-color/${n.id}${o}`,n)}delete(t,n){const o=(0,b.UK)({id:n});return this._delete(this.http,`${this.root}/${t}${o}`)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.LFG(ce.eN))},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})();const _n=["taskInput"];function mn(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"form",3)(1,"input",4,5),e.NdJ("blur",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.onInputBlur())})("keyup.enter",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.addInstantTask())}),e.qZA()()}if(2&i){const s=e.oxw();e.Q6J("formGroup",s.form),e.xp6(1),e.Q6J("nzBorderless",!0)("formControlName","name")("readOnly",s.creating)}}function hn(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"div",6),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.focusTaskInput())}),e._UZ(1,"span",7),e.TgZ(2,"span"),e._uU(3),e.qZA()()}if(2&i){const s=e.oxw();e.uIk("id",s.id),e.xp6(1),e.Q6J("nzType",s.creating?"loading":"plus")("nzTheme","outline"),e.xp6(2),e.hij(" ",s.label,"")}}let Qe=(()=>{var i;class a{constructor(t,n,o,r,l,d,_){this.socket=t,this.auth=n,this.fb=o,this.service=r,this.ngZone=l,this.cdr=d,this.map=_,this.subTaskInput=!1,this.templateId=null,this.parentTask=null,this.groupId=null,this.label="Add Task",this.focusChange=new e.vpe,this.taskInputVisible=!1,this.creating=!1,this.id=(0,b.q$)(4),this._session=null,this.form=this.fb.group({name:[null,[p.kI.required,p.kI.pattern(/^(\s+\S+\s*)*(?!\s).*$/)]]}),this._session=this.auth.getCurrentSession()}focusTaskInput(){this.taskInputVisible=!0,this.focusChange.emit(this.taskInputVisible),this.ngZone.runOutsideAngular(()=>{setTimeout(()=>{this.taskInput?.nativeElement.focus(),this.taskInput?.nativeElement.select()},100)})}addTaskInputBlur(){this.taskInputVisible=!1,this.focusChange.emit(this.taskInputVisible)}onInputBlur(){var t=this;return(0,m.Z)(function*(){t.isValidInput()?yield t.addInstantTask():t.addTaskInputBlur()})()}createRequest(){if(!this.templateId||!this._session)return null;const t=this._session,n={name:this.form.value.name,template_id:this.templateId,reporter_id:t.id,team_id:t.team_id},o=this.service.getCurrentGroup();return o.value===this.service.GROUP_BY_STATUS_VALUE?n.status_id=this.groupId||void 0:o.value===this.service.GROUP_BY_PRIORITY_VALUE?n.priority_id=this.groupId||void 0:o.value===this.service.GROUP_BY_PHASE_VALUE&&(n.phase_id=this.groupId||void 0),this.parentTask&&(n.parent_task_id=this.parentTask),n}isValidInput(){return this.form.valid&&this.form.value.name.trim().length}addInstantTask(){var t=this;return(0,m.Z)(function*(){if(!t.creating&&t.templateId&&t._session&&t.isValidInput()){try{const n=t.createRequest();if(!n)return;t.creating=!0,t.socket.emit(k.C.PT_QUICK_TASK.toString(),JSON.stringify(n)),t.socket.once(k.C.PT_QUICK_TASK.toString(),o=>{t.creating=!1,t.onNewTaskReceived(o)})}catch{t.creating=!1}t.cdr.markForCheck()}})()}reset(t=!0){this.creating=!1,this.form.controls.name.setValue(null),this.taskInputVisible=!0,this.ngZone.runOutsideAngular(()=>{setTimeout(()=>{this.taskInput?.nativeElement.focus(),t&&window.scrollTo(0,document.body.scrollHeight)},j.GR)}),this.cdr.markForCheck()}onNewTaskReceived(t){if(this.groupId&&t.id){if(this.map.has(t.id))return;this.service.addTask(t,this.groupId),this.reset(!1)}}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(q.s),e.Y36(w.e),e.Y36(p.qu),e.Y36(B),e.Y36(e.R0b),e.Y36(e.sBO),e.Y36(ue))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-add-task-input"]],viewQuery:function(t,n){if(1&t&&e.Gf(_n,5),2&t){let o;e.iGM(o=e.CRH())&&(n.taskInput=o.first)}},inputs:{subTaskInput:"subTaskInput",templateId:"templateId",parentTask:"parentTask",groupId:"groupId",label:"label"},outputs:{focusChange:"focusChange"},standalone:!0,features:[e.jDz],decls:3,vars:3,consts:[[3,"ngSwitch"],[3,"formGroup",4,"ngSwitchCase"],["class","editable-row d-block w-25 task-name",3,"click",4,"ngSwitchCase"],[3,"formGroup"],["nz-input","","type","text","tabindex","1","placeholder","Type your task and hit enter",3,"nzBorderless","formControlName","readOnly","blur","keyup.enter"],["taskInput",""],[1,"editable-row","d-block","w-25","task-name",3,"click"],["nz-icon","",1,"input-icon",3,"nzType","nzTheme"]],template:function(t,n){1&t&&(e.ynx(0,0),e.YNc(1,mn,3,4,"form",1),e.YNc(2,hn,4,4,"div",2),e.BQk()),2&t&&(e.Q6J("ngSwitch",n.taskInputVisible),e.xp6(1),e.Q6J("ngSwitchCase",!0),e.xp6(1),e.Q6J("ngSwitchCase",!1))},dependencies:[h.ez,h.RF,h.n9,p.UX,p._Y,p.Fj,p.JJ,p.JL,p.sg,p.u,P.o7,P.Zp,M.PV,M.Ls],changeDetection:0}),a})();var gn=c(49278),Ze=c(63019),_e=c(76271);function fn(i,a){1&i&&e._UZ(0,"span",12),2&i&&e.Q6J("nzType","loading")("nzTheme","outline")}function zn(i,a){if(1&i){const s=e.EpF();e.ynx(0),e.TgZ(1,"input",10),e.NdJ("ngModelChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.group.name=n)})("keydown.enter",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.updateName(n.group))})("blur",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.updateName(n.group))}),e.qZA(),e.YNc(2,fn,1,2,"span",11),e.BQk()}if(2&i){const s=e.oxw();e.xp6(1),e.ekj("bg-and-grey",!s.isEditColProgress),e.Q6J("id","group-name-"+s.group.id)("disabled",s.isEditColProgress)("ngModel",s.group.name),e.xp6(1),e.Q6J("ngIf",s.isEditColProgress)}}function Tn(i,a){if(1&i&&(e.ynx(0),e._uU(1),e.BQk()),2&i){const s=e.oxw();e.xp6(1),e.AsE(" ",s.group.name," (",s.group.tasks.length,") ")}}function Cn(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",13),e.NdJ("nzVisibleChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.showMenu=n)}),e._UZ(1,"span",14),e.qZA()}if(2&i){e.oxw();const s=e.MAs(9);e.Q6J("nzType","text")("nzTrigger","click")("nzDropdownMenu",s),e.xp6(1),e.Q6J("nzType","ellipsis")("nzTheme","outline")}}function kn(i,a){if(1&i&&(e.TgZ(0,"span"),e._uU(1),e.qZA()),2&i){const s=e.oxw().$implicit,t=e.oxw(3);e.Udp("font-weight",s.id===t.group.category_id?"bold":null),e.xp6(1),e.hij(" ",(null==s?null:s.name)||null," ")}}function xn(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"li",21),e.NdJ("click",function(){const o=e.CHM(s).$implicit,r=e.oxw(3);return e.KtG(r.changeStatusCategory(r.group,o.id))}),e.ALo(1,"safeString"),e._UZ(2,"nz-badge",22),e.ALo(3,"safeString"),e.YNc(4,kn,2,3,"ng-template",null,23,e.W1O),e.qZA()}if(2&i){const s=a.$implicit,t=e.MAs(5);e.Q6J("nzTooltipTitle",e.lcZ(1,4,s.description))("nzTooltipPlacement","right"),e.xp6(2),e.Q6J("nzColor",e.lcZ(3,6,s.color_code))("nzText",t)}}function bn(i,a){if(1&i&&(e.TgZ(0,"li",19)(1,"ul"),e.YNc(2,xn,6,8,"li",20),e.qZA()()),2&i){const s=e.oxw(2),t=e.MAs(12);e.Q6J("nzTitle",t),e.xp6(2),e.Q6J("ngForOf",s.categories)}}function vn(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"ul",15)(1,"li",16),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.editGroupName())}),e._UZ(2,"span",17),e._uU(3," Rename "),e.qZA(),e.YNc(4,bn,3,2,"li",18),e.qZA()}if(2&i){const s=e.oxw();e.xp6(4),e.Q6J("ngIf",s.isGroupByStatus)}}function wn(i,a){1&i&&(e._UZ(0,"span",24),e._uU(1," Change category\n"))}let yn=(()=>{var i;class a{constructor(t,n,o,r,l,d){this.cdr=t,this.auth=n,this.statusApi=o,this.list=r,this.ngZone=l,this.phaseApi=d,this.templateId=null,this.categories=[],this.toggle=new e.vpe,this.onCreateOrUpdate=new e.vpe,this.edit=!1,this.isEditColProgress=!1,this.showMenu=!1,this.isGroupByStatus=!1,this.isGroupByPhases=!1,this.isAdmin=!1,this.handleGroupProgressChange=()=>{this.group&&this.cdr.markForCheck()},(0,Ze.T)(this.list.onGroupChange$,this.list.onTaskAddOrDelete$).pipe((0,G.sL)()).subscribe(()=>{this.handleGroupProgressChange()})}ngOnInit(){this.isGroupByStatus=this.list.getCurrentGroup().value===this.list.GROUP_BY_STATUS_VALUE,this.isGroupByPhases=this.list.getCurrentGroup().value===this.list.GROUP_BY_PHASE_VALUE;const t=this.auth.getCurrentSession();t&&(this.isAdmin=!(!t.owner&&!t.is_admin))}canDisplayActions(){const t=this.list.getCurrentGroup().value;return t!==this.list.GROUP_BY_PRIORITY_VALUE&&(this.isAdmin||this.isGroupByStatus||t===this.list.GROUP_BY_PHASE_VALUE)&&this.group.name!==j.k9}changeStatusCategory(t,n){var o=this;return(0,m.Z)(function*(){n&&(t.category_id=n,yield o.onBlurEditColumn(t),o.list.emitRefresh())})()}editGroupName(){this.edit=!0,this.ngZone.runOutsideAngular(()=>{setTimeout(()=>{const n=document.querySelector(`#group-name-${this.group.id}`);n&&(n.focus(),n.select())})})}onToggleClick(t){this.edit||this.toggle.emit(t)}onBlurEditColumn(t){var n=this;return(0,m.Z)(function*(){if(n.templateId&&!n.isEditColProgress){try{n.isEditColProgress=!0;const o=n.list.getCurrentGroup().value;o===n.list.GROUP_BY_STATUS_VALUE?yield n.update(t):o===n.list.GROUP_BY_PHASE_VALUE&&(yield n.updatePhase(t)),n.isEditColProgress=!1,n.edit=!1}catch(o){(0,b.tu)(o),n.isEditColProgress=!1}n.cdr.markForCheck()}})()}updateName(t){var n=this;return(0,m.Z)(function*(){if(n.templateId&&!n.isEditColProgress){try{n.isEditColProgress=!0;const o=n.list.getCurrentGroup().value;o===n.list.GROUP_BY_STATUS_VALUE?yield n.updateGroupName(t):o===n.list.GROUP_BY_PHASE_VALUE&&(yield n.updatePhase(t)),n.isEditColProgress=!1,n.edit=!1}catch(o){(0,b.tu)(o),n.isEditColProgress=!1}n.cdr.markForCheck()}})()}updateGroupName(t){var n=this;return(0,m.Z)(function*(){if(t?.id&&n.templateId){try{const o={name:t.name,template_id:n.templateId,category_id:t.category_id},r=yield n.statusApi.updateName(t.id,o);if(r.done){const l=n.list.groups,d=l.find(_=>_.id===r.body.id);d&&(n.group.name=d.name=r.body.name||"",n.group.color_code=d.color_code=r.body.color_code||""),n.list.groups=l}}catch{}n.cdr.markForCheck()}})()}updatePhase(t){var n=this;return(0,m.Z)(function*(){if(t?.id&&n.templateId){try{const o={id:t.id,name:t.name},r=yield n.phaseApi.update(n.templateId,o);if(r.done){const l=n.list.phases,d=l.find(_=>_.id===r.body.id);d&&(n.group.name=d.name=r.body.name,n.group.color_code=d.color_code=r.body.color_code),n.list.phases=l}}catch{}n.cdr.markForCheck()}})()}update(t){var n=this;return(0,m.Z)(function*(){if(!n.isAdmin)return;const o={name:t.name,template_id:n.templateId,category_id:t.category_id},r=yield n.statusApi.update(t.id,o);r.done&&null!=r.body.color_code&&(t.color_code=r.body.color_code+j.Yj,n.onCreateOrUpdate.emit()),n.cdr.markForCheck()})()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(e.sBO),e.Y36(w.e),e.Y36(Ae),e.Y36(B),e.Y36(e.R0b),e.Y36(Ie))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-task-list-group-settings"]],inputs:{group:"group",templateId:"templateId",categories:"categories"},outputs:{toggle:"toggle",onCreateOrUpdate:"onCreateOrUpdate"},decls:13,vars:11,consts:[[1,"d-flex","justify-content-between","align-items-center","position-relative"],[1,"d-flex","align-items-center"],["nz-button","",1,"collapse","btn","border-0",3,"click"],["nz-icon","",1,"collapse-icon",3,"nzType","nzTheme"],[3,"ngSwitch"],[4,"ngSwitchCase"],["nz-button","","class","p-0","nz-dropdown","",3,"nzType","nzTrigger","nzDropdownMenu","nzVisibleChange",4,"ngIf"],["menu","nzDropdownMenu"],["nz-menu","",4,"ngIf"],["titleTemplate",""],["nz-input","",1,"p-0",3,"id","disabled","ngModel","ngModelChange","keydown.enter","blur"],["nz-icon","","class","ms-2",3,"nzType","nzTheme",4,"ngIf"],["nz-icon","",1,"ms-2",3,"nzType","nzTheme"],["nz-button","","nz-dropdown","",1,"p-0",3,"nzType","nzTrigger","nzDropdownMenu","nzVisibleChange"],["nz-icon","",3,"nzType","nzTheme"],["nz-menu",""],["nz-menu-item","",3,"click"],["nz-icon","","nzType","edit","nzTheme","outline",1,"me-2"],["nz-submenu","",3,"nzTitle",4,"ngIf"],["nz-submenu","",3,"nzTitle"],["class","m-0","nz-tooltip","","nz-menu-item","",3,"nzTooltipTitle","nzTooltipPlacement","click",4,"ngFor","ngForOf"],["nz-tooltip","","nz-menu-item","",1,"m-0",3,"nzTooltipTitle","nzTooltipPlacement","click"],[3,"nzColor","nzText"],["textTmpl",""],["nz-icon","","nzType","retweet","nzTheme","outline",1,"me-2"]],template:function(t,n){1&t&&(e.TgZ(0,"div",0)(1,"div",1)(2,"button",2),e.NdJ("click",function(r){return n.onToggleClick(r)}),e._UZ(3,"span",3),e.ynx(4,4),e.YNc(5,zn,3,6,"ng-container",5),e.YNc(6,Tn,2,2,"ng-container",5),e.BQk(),e.qZA(),e.YNc(7,Cn,2,5,"button",6),e.qZA()(),e.TgZ(8,"nz-dropdown-menu",null,7),e.YNc(10,vn,5,1,"ul",8),e.qZA(),e.YNc(11,wn,2,0,"ng-template",null,9,e.W1O)),2&t&&(e.xp6(2),e.Udp("background-color",n.group.color_code),e.ekj("active",n.group.tasks.length),e.xp6(1),e.Q6J("nzType","right")("nzTheme","outline"),e.xp6(1),e.Q6J("ngSwitch",n.edit),e.xp6(1),e.Q6J("ngSwitchCase",!0),e.xp6(1),e.Q6J("ngSwitchCase",!1),e.xp6(1),e.Q6J("ngIf",n.canDisplayActions()),e.xp6(3),e.Q6J("ngIf",n.showMenu))},dependencies:[h.sg,h.O5,h.RF,h.n9,M.Ls,p.Fj,p.JJ,P.Zp,O.ix,v.w,L.dQ,T.wO,T.r9,T.rY,J.SY,p.On,ie.x7,E.cm,E.RR,E.wA,_e.m],styles:[".collapse[_ngcontent-%COMP%]{color:hwb(0 0% 100%/.85);font-weight:500;padding:6px 13px 6px 12px;min-width:120px;width:auto;border:none;text-align:left;outline:none;border-top-right-radius:4px;border-top-left-radius:4px;-webkit-user-select:none;user-select:none;z-index:8;font-size:14px;height:30px;display:flex;align-items:center}.collapse.btn[_ngcontent-%COMP%] .collapse-icon[_ngcontent-%COMP%]{transition:transform .1s;transform:rotate(0)}.collapse.btn.active[_ngcontent-%COMP%] .collapse-icon[_ngcontent-%COMP%]{transition:transform .1s;transform:rotate(90deg)}.collapse.active[_ngcontent-%COMP%]{border-bottom-left-radius:0;border-bottom-right-radius:0}.collapse[_ngcontent-%COMP%]:after{color:#777;font-weight:700;float:left;margin-left:5px}"],changeDetection:0}),a})();var Re=c(48327),we=c(62612),Le=c(44889);function Sn(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",13),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.onPhaseSettingsClick())}),e._UZ(1,"span",14),e.qZA()}2&i&&(e.Q6J("nzShape","circle")("nzSize","small")("nzType","text"),e.xp6(1),e.Q6J("nzType","setting")("nzTheme","outline"))}let Pn=(()=>{var i;class a{get phaseLabel(){return this.phasesService.label}constructor(t,n,o,r,l){this.service=t,this.map=n,this.cdr=o,this.phasesService=r,this.auth=l,this.headerCls="flex-table header",this.selectChange=new e.vpe,this.phaseSettingsClick=new e.vpe,this.checked=!1,this.indeterminate=!1,this.map.onDeselectAll$.pipe((0,G.sL)()).subscribe(()=>{this.checked=!1,this.indeterminate=!1,this.cdr.markForCheck()}),this.phasesService.onLabelChange.pipe((0,G.sL)()).subscribe(d=>{this.cdr.markForCheck()}),(0,Ze.T)(this.map.onSelect$,this.map.onDeselect$).pipe((0,G.sL)()).subscribe(()=>{this.map.isAllDeselected(this.groupId)?(this.checked=!1,this.indeterminate=!1):this.map.isAllSelected(this.groupId)?(this.checked=!0,this.indeterminate=!1):this.indeterminate=!0,this.cdr.markForCheck()})}onAllChecked(t){this.selectChange?.emit(t)}onPhaseSettingsClick(){this.phaseSettingsClick.emit()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(B),e.Y36(ue),e.Y36(e.sBO),e.Y36(Re.u),e.Y36(w.e))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-task-list-header"]],hostVars:2,hostBindings:function(t,n){2&t&&e.Tol(n.headerCls)},inputs:{groupId:"groupId"},outputs:{selectChange:"selectChange",phaseSettingsClick:"phaseSettingsClick"},decls:27,vars:8,consts:[[1,"flex-row","task-drag-handler"],[1,"flex-row","task-check"],["nz-checkbox","",3,"nzChecked","nzIndeterminate","nzCheckedChange"],[1,"flex-row","task-arrow"],[1,"flex-row","task-name"],[1,"flex-row","task-description"],[1,"flex-row","task-labels"],[1,"flex-row","task-phase","d-flex","justify-content-between","align-items-center"],["nz-tooltip","",3,"nzTooltipTitle"],["nz-button","",3,"nzShape","nzSize","nzType","click",4,"ngIf"],[1,"flex-row","task-status"],[1,"flex-row","task-priority"],[1,"flex-row","task-estimation","justify-content-center"],["nz-button","",3,"nzShape","nzSize","nzType","click"],["nz-icon","",3,"nzType","nzTheme"]],template:function(t,n){1&t&&(e._UZ(0,"div",0),e.TgZ(1,"div",1)(2,"span",2),e.NdJ("nzCheckedChange",function(r){return n.checked=r})("nzCheckedChange",function(r){return n.onAllChecked(r)}),e.qZA()(),e._UZ(3,"div",3),e.TgZ(4,"div",4),e._uU(5,"Task"),e.qZA(),e.ynx(6),e.TgZ(7,"div",5),e._uU(8,"Description"),e.qZA(),e.BQk(),e.ynx(9),e.TgZ(10,"div",6),e._uU(11,"Labels"),e.qZA(),e.BQk(),e.ynx(12),e.TgZ(13,"div",7)(14,"span",8),e._uU(15),e.ALo(16,"ellipsis"),e.qZA(),e.YNc(17,Sn,2,5,"button",9),e.qZA(),e.BQk(),e.ynx(18),e.TgZ(19,"div",10),e._uU(20,"Status"),e.qZA(),e.BQk(),e.ynx(21),e.TgZ(22,"div",11),e._uU(23,"Priority"),e.qZA(),e.BQk(),e.ynx(24),e.TgZ(25,"div",12),e._uU(26,"Estimation"),e.qZA(),e.BQk()),2&t&&(e.xp6(2),e.Q6J("nzChecked",n.checked)("nzIndeterminate",n.indeterminate),e.xp6(12),e.Q6J("nzTooltipTitle",n.phaseLabel),e.xp6(1),e.Oqu(e.xi3(16,5,n.phaseLabel,10)),e.xp6(2),e.Q6J("ngIf",n.auth.isOwnerOrAdmin()))},dependencies:[h.O5,M.Ls,O.ix,v.w,L.dQ,J.SY,we.Ie,Le.p],styles:[".flex-row[_ngcontent-%COMP%]{padding:4px 11px;background-color:#fafafa;border-top:1px solid #f0f0f0;border-bottom:1px solid #f0f0f0;border-right:1px solid #f0f0f0;display:flex;align-items:center;flex-direction:row}.task-drag-handler[_ngcontent-%COMP%]{padding:0 0 0 4px!important;width:24px;border-bottom:1px solid #f0f0f0;border-right:none!important;position:sticky;left:0;z-index:1}.task-check[_ngcontent-%COMP%]{text-align:center;padding:8px 6px 8px 0!important;position:sticky;left:24px;z-index:1}.task-arrow[_ngcontent-%COMP%]{width:24px;padding:0!important;display:flex;align-items:center;position:sticky;border-right:0;left:47px;z-index:1}.task-name[_ngcontent-%COMP%]{width:450px;min-width:450px;position:sticky;left:71px;z-index:1}.task-name[_ngcontent-%COMP%] nz-filter-trigger[_ngcontent-%COMP%]{margin-left:auto}.task-name.left-0[_ngcontent-%COMP%]{left:47px}.task-description[_ngcontent-%COMP%]{width:225px}.task-labels[_ngcontent-%COMP%]{width:220px}.task-status[_ngcontent-%COMP%]{width:120px}.task-phase[_ngcontent-%COMP%]{width:150px}.task-priority[_ngcontent-%COMP%], .task-estimation[_ngcontent-%COMP%]{width:120px}.task-start-date[_ngcontent-%COMP%], .task-due-date[_ngcontent-%COMP%]{width:150px}"],changeDetection:0}),a})();var W=c(32181),Be=c(42753),He=c(6192);const Mn=["descriptionInput"],On=["descriptionEditor"];function An(i,a){if(1&i&&e._UZ(0,"span",8),2&i){const s=e.oxw();e.Q6J("innerHTML",s.task.description,e.oJD)}}function In(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"div",9)(1,"nz-form-item",10)(2,"nz-form-control",11)(3,"editor",12,13),e.NdJ("ngModelChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.task.description=n)})("onBlur",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.submit())}),e.qZA()()()()}if(2&i){const s=e.oxw();e.xp6(1),e.Udp("width","550px"),e.xp6(2),e.ekj("editing",s.isEditing),e.Q6J("init",s.CONFIG)("apiKey",s.apiKey)("ngModel",s.task.description)}}let Zn=(()=>{var i;class a{constructor(t,n,o,r){this.cdr=t,this.socket=n,this.service=o,this.ngZone=r,this.task={},this.cls="flex-row task-description p-0",this.show=!1,this.loading=!1,this.apiKey="4nquevykvy1i0q0v62ksxuu3nz1muy8i5fsqpj3wp9qm2mgp",this.CONFIG={plugins:"lists link code wordcount",toolbar:"blocks bold italic underline strikethrough | checklist numlist bullist link | alignleft aligncenter alignright alignjustify",menubar:!1,content_css:"/assets/css/prebuilt-editor.css",statusbar:!0,branding:!1,height:200,min_height:100},this.isEditing=!1,this.handleResponse=l=>{this.task.id===l?.id&&(this.task.description=l.description,this.closeDropdown(),this.cdr.markForCheck())}}ngOnInit(){this.socket.on(k.C.PT_TASK_DESCRIPTION_CHANGE.toString(),this.handleResponse)}ngOnDestroy(){this.socket.removeListener(k.C.PT_TASK_DESCRIPTION_CHANGE.toString(),this.handleResponse)}handleVisibleChange(t,n){this.show=t,t?n.classList.add(this.service.HIGHLIGHT_COL_CLS):n.classList.remove(this.service.HIGHLIGHT_COL_CLS)}submit(){this.socket.emit(k.C.PT_TASK_DESCRIPTION_CHANGE.toString(),JSON.stringify({task_id:this.task.id,description:this.task.description}))}closeDropdown(){this.ngZone.runOutsideAngular(()=>{document.body.click()})}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(e.sBO),e.Y36(q.s),e.Y36(B),e.Y36(e.R0b))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-task-description"]],viewQuery:function(t,n){if(1&t&&(e.Gf(Mn,5),e.Gf(On,5)),2&t){let o;e.iGM(o=e.CRH())&&(n.descriptionInput=o.first),e.iGM(o=e.CRH())&&(n.descriptionEditor=o.first)}},hostVars:2,hostBindings:function(t,n){2&t&&e.Tol(n.cls)},inputs:{task:"task"},decls:13,vars:11,consts:[["nz-dropdown","",1,"editable","w-100","h-100","d-flex","align-items-center","ps-2","pe-0","label-tag-container",3,"nzOverlayClassName","nzTrigger","nzClickHide","nzDropdownMenu","nzVisibleChange"],["element",""],["nz-tooltip","",3,"innerHTML","nzTooltipTitle"],["descriptionTooltip",""],["descriptionDropdown","nzDropdownMenu"],["class","bg-white px-0 py-0",4,"ngIf"],[1,"bg-white","d-flex","justify-content-end","p-2","border-top"],["nz-button","","nzType","primary","nzSize","small",3,"click"],["nz-typography","",1,"text-white",3,"innerHTML"],[1,"bg-white","px-0","py-0"],["nz-row","",1,"task-description-editor-list","mb-0"],[1,"description-hover","position-relative"],[1,"description-editor-list",3,"init","apiKey","ngModel","ngModelChange","onBlur"],["descriptionEditor",""]],template:function(t,n){if(1&t){const o=e.EpF();e.TgZ(0,"div",0,1),e.NdJ("nzVisibleChange",function(l){e.CHM(o);const d=e.MAs(1);return e.KtG(n.handleVisibleChange(l,d))}),e.ynx(2),e._UZ(3,"span",2),e.ALo(4,"nzEllipsis"),e.BQk(),e.qZA(),e.YNc(5,An,1,1,"ng-template",null,3,e.W1O),e.TgZ(7,"nz-dropdown-menu",null,4),e.YNc(9,In,5,7,"div",5),e.TgZ(10,"div",6)(11,"button",7),e.NdJ("click",function(){return n.submit()}),e._uU(12,"OK"),e.qZA()()()}if(2&t){const o=e.MAs(6),r=e.MAs(8);e.Q6J("nzOverlayClassName","custom-shadow")("nzTrigger","click")("nzClickHide",!1)("nzDropdownMenu",r),e.xp6(3),e.Q6J("innerHTML",e.Dn7(4,7,n.task.description,55,"..."),e.oJD)("nzTooltipTitle",o),e.xp6(6),e.Q6J("ngIf",n.show)}},dependencies:[h.O5,p.JJ,Z.t3,Z.SK,x.Nx,x.Fd,O.ix,v.w,L.dQ,Q.ZU,J.SY,p.On,E.cm,E.RR,Be.PG,He.N7],styles:["[_nghost-%COMP%]{display:block;max-width:640px}.description-editor-placeholder[_ngcontent-%COMP%]{pointer-events:none;-webkit-user-select:none;user-select:none;position:absolute;inset:0;display:flex;align-items:center}.description-editor-preview[_ngcontent-%COMP%]{position:relative;min-height:32px}.description-editor-preview.empty[_ngcontent-%COMP%]{display:flex;align-items:center;padding:10px}.task-description-editor[_ngcontent-%COMP%]{padding-left:12px;padding-right:12px;padding-bottom:12px;margin-left:-12px;margin-right:-12px;margin-bottom:-12px}"],changeDetection:0}),a})();var Ln=c(86211);let Fn=(()=>{var i;class a{transform(t,n){return t&&t.length>n?t:""}}return(i=a).\u0275fac=function(t){return new(t||i)},i.\u0275pipe=e.Yjl({name:"ellipsisTooltipTitlePT",type:i,pure:!0}),a})(),Nn=(()=>{var i;class a{transform(t,...n){return!(!t.end||!t.names)}}return(i=a).\u0275fac=function(t){return new(t||i)},i.\u0275pipe=e.Yjl({name:"endNameCheckPT",type:i,pure:!0}),a})();const En=["labelsSearchInput"];function Un(i,a){if(1&i&&(e.TgZ(0,"nz-tag",14),e.ALo(1,"withAlpha"),e.ALo(2,"safeString"),e.TgZ(3,"span"),e._uU(4),e.ALo(5,"ellipsis"),e.qZA()()),2&i){const s=e.oxw().$implicit;e.Q6J("nzColor",e.lcZ(1,3,s.color_code))("nzTooltipTitle",e.lcZ(2,5,s.names)),e.xp6(4),e.Oqu(e.xi3(5,7,s.name,10))}}function Jn(i,a){if(1&i&&(e.TgZ(0,"nz-tag",15),e.ALo(1,"ellipsisTooltipTitlePT"),e.ALo(2,"withAlpha"),e.TgZ(3,"span"),e._uU(4),e.ALo(5,"ellipsis"),e.qZA()()),2&i){const s=e.oxw().$implicit;e.Q6J("nzTooltipTitle",e.xi3(1,3,s.name,5))("nzColor",e.lcZ(2,6,s.color_code)),e.xp6(4),e.Oqu(e.xi3(5,8,s.name,10))}}function Gn(i,a){if(1&i&&(e.ynx(0)(1,11),e.ALo(2,"endNameCheckPT"),e.YNc(3,Un,6,10,"nz-tag",12),e.YNc(4,Jn,6,11,"nz-tag",13),e.BQk()()),2&i){const s=a.$implicit;e.xp6(1),e.Q6J("ngSwitch",e.lcZ(2,3,s)),e.xp6(2),e.Q6J("ngSwitchCase",!0),e.xp6(1),e.Q6J("ngSwitchCase",!1)}}function Dn(i,a){1&i&&(e.TgZ(0,"span",20),e._uU(1," Hit enter to create! "),e.qZA())}function Yn(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"div",16)(1,"input",17,18),e.NdJ("ngModelChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.searchText=n)})("keydown.enter",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.createLabel())}),e.qZA(),e.YNc(3,Dn,2,0,"span",19),e.qZA()}if(2&i){const s=e.oxw();e.ekj("border-bottom",s.hasFilteredLabel),e.xp6(1),e.Q6J("ngModel",s.searchText),e.xp6(2),e.Q6J("ngIf",!s.hasFilteredLabel)}}function Qn(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"li",23),e.NdJ("nzCheckedChange",function(n){const r=e.CHM(s).$implicit;return e.KtG(r.selected=n)})("nzCheckedChange",function(){const o=e.CHM(s).$implicit,r=e.oxw(2);return e.KtG(r.handleLabelChange(o))}),e._UZ(1,"nz-badge",24),e.qZA()}if(2&i){const s=a.$implicit;e.Q6J("nzChecked",s.selected),e.xp6(1),e.Q6J("nzColor",s.color_code)("nzText",(null==s?null:s.name)||null)}}function Rn(i,a){if(1&i&&(e.TgZ(0,"ul",21),e.YNc(1,Qn,2,3,"li",22),e.qZA()),2&i){const s=e.oxw();e.xp6(1),e.Q6J("ngForOf",s.filteredLabels)("ngForTrackBy",s.trackById)}}let Bn=(()=>{var i;class a{get hasFilteredLabel(){return!!this.filteredLabels.length}get filteredLabels(){return this.searchPipe.transform(this.labels,this.searchText)}constructor(t,n,o,r,l,d,_){this.cdr=t,this.searchPipe=n,this.auth=o,this.socket=r,this.utils=l,this.ngZone=d,this.service=_,this.task={},this.cls="flex-row task-labels",this.alpha=j.Yj,this.searchText=null,this.labels=[],this.show=!1,this.handleLabelsChange=y=>{if(y&&y.id===this.task.id){if(this.task.labels=y.labels,this.task.all_labels=y.all_labels,y.new_label)if(y.is_new){const S=[...this.service.labels];S.push(y.new_label),this.service.labels=[...S]}else{const S=this.labels.find(be=>be.id===y.new_label.id);S&&(S.selected=!0)}this.cdr.markForCheck()}},this.service.onLabelsChange$.pipe((0,G.sL)()).subscribe(()=>{this.updateLabels(),this.cdr.markForCheck()})}ngOnInit(){this.updateLabels(),this.socket.on(k.C.PT_TASK_LABELS_CHANGE.toString(),this.handleLabelsChange),this.socket.on(k.C.PT_CREATE_LABEL.toString(),this.handleLabelsChange)}ngOnDestroy(){this.labels=[],this.socket.removeListener(k.C.PT_TASK_LABELS_CHANGE.toString(),this.handleLabelsChange),this.socket.removeListener(k.C.PT_CREATE_LABEL.toString(),this.handleLabelsChange)}updateLabels(){this.labels=this.service.labels}trackById(t,n){return n.id}sortBySelected(t){this.utils.sortBySelection(t)}handleLabelsVisibleChange(t,n){if(this.show=t,t?n.classList.add(this.service.HIGHLIGHT_COL_CLS):n.classList.remove(this.service.HIGHLIGHT_COL_CLS),t){const o=this.task.all_labels?.map(r=>r.id)??[];for(const r of this.labels)r.selected=o.includes(r.id);this.focusLabelsSearchInput()}else{this.searchText=null;for(const o of this.labels)o.selected=!1}this.sortBySelected(this.labels)}focusLabelsSearchInput(){this.ngZone.runOutsideAngular(()=>{setTimeout(()=>{this.labelsSearchInput?.nativeElement?.focus()},100)})}handleLabelChange(t){this.socket.emit(k.C.PT_TASK_LABELS_CHANGE.toString(),JSON.stringify({task_id:this.task.id,label_id:t.id,parent_task:this.task.parent_task_id})),this.sortBySelected(this.labels)}createLabel(){if(this.hasFilteredLabel||!this.searchText)return;const t=this.auth.getCurrentSession();this.socket.emit(k.C.PT_CREATE_LABEL.toString(),JSON.stringify({task_id:this.task.id,label:this.searchText.trim(),team_id:t?.team_id,parent_task:this.task.parent_task_id})),this.searchText=null,this.cdr.detectChanges()}closeDropdown(){this.ngZone.runOutsideAngular(()=>{document.body.click()})}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(e.sBO),e.Y36(Oe.g),e.Y36(w.e),e.Y36(q.s),e.Y36(de.F),e.Y36(e.R0b),e.Y36(B))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-task-labels"]],viewQuery:function(t,n){if(1&t&&e.Gf(En,5),2&t){let o;e.iGM(o=e.CRH())&&(n.labelsSearchInput=o.first)}},hostVars:2,hostBindings:function(t,n){2&t&&e.Tol(n.cls)},inputs:{task:"task"},decls:13,vars:11,consts:[["nz-dropdown","",1,"editable","w-100","h-100","d-flex","align-items-center","ps-2","pe-0","label-tag-container",3,"nzOverlayClassName","nzTrigger","nzClickHide","nzDropdownMenu","nzVisibleChange"],["element",""],[4,"ngFor","ngForOf","ngForTrackBy"],[1,"text-dark","avatar-dashed","empty-label","task-list-label"],["nz-typography","",3,"nzType"],["nz-icon","",3,"nzType","nzTheme"],["labelsDropdown","nzDropdownMenu"],["class","bg-white px-3 py-2",3,"border-bottom",4,"ngIf"],["style","box-shadow: none;","class","dropdown-ul","nz-menu","",4,"ngIf"],[1,"bg-white","d-flex","justify-content-end","p-2","border-top"],["nz-button","","nzType","primary","nzSize","small",3,"click"],[3,"ngSwitch"],["nz-tooltip","","class","text-dark task-list-label",3,"nzColor","nzTooltipTitle",4,"ngSwitchCase"],["class","text-dark task-list-label","nz-tooltip","",3,"nzTooltipTitle","nzColor",4,"ngSwitchCase"],["nz-tooltip","",1,"text-dark","task-list-label",3,"nzColor","nzTooltipTitle"],["nz-tooltip","",1,"text-dark","task-list-label",3,"nzTooltipTitle","nzColor"],[1,"bg-white","px-3","py-2"],["type","text","placeholder","Search or create","nz-input","",3,"ngModel","ngModelChange","keydown.enter"],["labelsSearchInput",""],["nz-typography","","nzType","secondary",4,"ngIf"],["nz-typography","","nzType","secondary"],["nz-menu","",1,"dropdown-ul",2,"box-shadow","none"],["nz-checkbox","","nz-menu-item","","class","m-0",3,"nzChecked","nzCheckedChange",4,"ngFor","ngForOf","ngForTrackBy"],["nz-checkbox","","nz-menu-item","",1,"m-0",3,"nzChecked","nzCheckedChange"],[3,"nzColor","nzText"]],template:function(t,n){if(1&t){const o=e.EpF();e.TgZ(0,"div",0,1),e.NdJ("nzVisibleChange",function(l){e.CHM(o);const d=e.MAs(1);return e.KtG(n.handleLabelsVisibleChange(l,d))}),e.YNc(2,Gn,5,5,"ng-container",2),e.TgZ(3,"nz-tag",3)(4,"span",4),e._UZ(5,"span",5),e.qZA()()(),e.TgZ(6,"nz-dropdown-menu",null,6),e.YNc(8,Yn,4,4,"div",7),e.YNc(9,Rn,2,2,"ul",8),e.TgZ(10,"div",9)(11,"button",10),e.NdJ("click",function(){return n.closeDropdown()}),e._uU(12,"OK"),e.qZA()()()}if(2&t){const o=e.MAs(7);e.Q6J("nzOverlayClassName","custom-shadow")("nzTrigger","click")("nzClickHide",!1)("nzDropdownMenu",o),e.xp6(2),e.Q6J("ngForOf",n.task.labels)("ngForTrackBy",n.trackById),e.xp6(2),e.Q6J("nzType","secondary"),e.xp6(1),e.Q6J("nzType","plus")("nzTheme","outline"),e.xp6(3),e.Q6J("ngIf",n.show),e.xp6(1),e.Q6J("ngIf",n.show)}},dependencies:[h.sg,h.O5,h.RF,h.n9,M.Ls,p.Fj,p.JJ,P.Zp,O.ix,v.w,L.dQ,Q.ZU,T.wO,T.r9,J.SY,p.On,ie.x7,E.cm,E.RR,ze.j,we.Ie,_e.m,Le.p,Ln.M,Fn,Nn],styles:[".dropdown-ul[_ngcontent-%COMP%]{max-height:250px;overflow:hidden;overflow-y:auto}.label-tag-container[_ngcontent-%COMP%]{max-width:220px;overflow:hidden;flex-wrap:wrap;padding-top:8px;padding-bottom:8px;padding-right:0}nz-tag[_ngcontent-%COMP%]{display:block;overflow:hidden;white-space:break-spaces;margin-left:3px;margin-right:3px;line-height:16px;padding-left:3px;padding-right:3px}.empty-label[_ngcontent-%COMP%]{padding:2px 8px 3px}.empty-label[_ngcontent-%COMP%] .ant-typography[_ngcontent-%COMP%]{display:flex}"],changeDetection:0}),a})(),Hn=(()=>{var i;class a{transform(t,n=0){return t?t.length>n?`${t.slice(0,n)}...`:t:""}}return(i=a).\u0275fac=function(t){return new(t||i)},i.\u0275pipe=e.Yjl({name:"truncateIfLongPT",type:i,pure:!0}),a})();function $n(i,a){if(1&i&&(e.TgZ(0,"nz-option",3),e.ALo(1,"truncateIfLongPT"),e._UZ(2,"nz-badge",4),e.ALo(3,"safeString"),e.qZA()),2&i){const s=a.$implicit;e.Q6J("nzValue",s.id)("nzLabel",e.xi3(1,4,s.name,12)),e.xp6(2),e.Q6J("nzColor",e.lcZ(3,7,s.color_code))("nzText",(null==s?null:s.name)||null)}}let jn=(()=>{var i;class a{constructor(t,n,o,r,l,d){this.service=t,this.socket=n,this.cdr=o,this.ngZone=r,this.element=l,this.renderer=d,this.task={},this.cls="flex-row task-phase",this.PHASE_COLOR="#a9a9a9"+j.Yj,this.PLACEHOLDER_COLOR="rgba(0, 0, 0, 0.85) !important",this.phases=[],this.loading=!1,this.handleResponse=_=>{_&&_.task_id===this.task.id&&(this.task.phase_color=_.color_code||void 0,this.task.phase_id=_.id,this.isGroupByPhase()&&(this.task.is_sub_task||this.service.updateTaskGroup(this.task,!1),this.service.isSubtasksIncluded&&this.service.emitRefreshSubtasksIncluded()),this.cdr.markForCheck())},this.service.onPhaseChange$.pipe((0,G.sL)()).subscribe(()=>{this.updatePhases(),this.cdr.markForCheck()}),this.service.onGroupChange$.pipe((0,W.h)(_=>_.taskId===this.task.id),(0,W.h)(()=>this.isGroupByPhase()),(0,G.sL)()).subscribe(_=>{"Unmapped"===_.groupId&&(_.color=""),this.task.phase_id=_.groupId,this.task.phase_color=_.color,this.cdr.markForCheck()})}ngOnInit(){this.updatePhases(),this.socket.on(k.C.PT_TASK_PHASE_CHANGE.toString(),this.handleResponse)}ngOnDestroy(){this.socket.removeListener(k.C.PT_TASK_PHASE_CHANGE.toString(),this.handleResponse)}isGroupByPhase(){return this.service.getCurrentGroup().value===this.service.GROUP_BY_PHASE_VALUE}trackById(t,n){return n.id}handleChange(t,n){n&&this.socket.emit(k.C.PT_TASK_PHASE_CHANGE.toString(),JSON.stringify({task_id:n,phase_id:t}))}updatePhases(){this.phases=this.service.phases}toggleHighlightCls(t,n){this.ngZone.runOutsideAngular(()=>{t?this.renderer.addClass(n,this.service.HIGHLIGHT_COL_CLS):this.renderer.removeClass(n,this.service.HIGHLIGHT_COL_CLS)})}handleOpen(t){this.toggleHighlightCls(t,this.element.nativeElement)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(B),e.Y36(q.s),e.Y36(e.sBO),e.Y36(e.R0b),e.Y36(e.SBq),e.Y36(e.Qsj))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-task-phase"]],hostVars:2,hostBindings:function(t,n){2&t&&e.Tol(n.cls)},inputs:{task:"task"},decls:3,vars:9,consts:[[1,"py-0"],["nzAllowClear","",1,"rounded-pill","custom-select",3,"ngModel","nzLoading","nzDropdownMatchSelectWidth","nzSize","nzPlaceHolder","ngModelChange","nzOpenChange"],["nzCustomContent","",3,"nzValue","nzLabel",4,"ngFor","ngForOf","ngForTrackBy"],["nzCustomContent","",3,"nzValue","nzLabel"],[3,"nzColor","nzText"]],template:function(t,n){1&t&&(e.TgZ(0,"div",0)(1,"nz-select",1),e.NdJ("ngModelChange",function(r){return n.task.phase_id=r})("ngModelChange",function(r){return n.handleChange(r,n.task.id)})("nzOpenChange",function(r){return n.handleOpen(r)}),e.YNc(2,$n,4,9,"nz-option",2),e.qZA()()),2&t&&(e.xp6(1),e.Udp("background-color",n.task.phase_color),e.Q6J("ngModel",n.task.phase_id)("nzLoading",n.loading)("nzDropdownMatchSelectWidth",!1)("nzSize","small")("nzPlaceHolder","Select"),e.xp6(1),e.Q6J("ngForOf",n.phases)("ngForTrackBy",n.trackById))},dependencies:[h.sg,p.JJ,K.Ip,K.Vq,p.On,ie.x7,_e.m,Hn],changeDetection:0}),a})();function qn(i,a){if(1&i&&(e.TgZ(0,"nz-option",3),e.ALo(1,"safeString"),e._UZ(2,"nz-badge",4),e.ALo(3,"safeString"),e.qZA()),2&i){const s=a.$implicit;e.Q6J("nzValue",s.id)("nzLabel",e.lcZ(1,4,s.name)),e.xp6(2),e.Q6J("nzColor",e.lcZ(3,6,s.color_code))("nzText",(null==s?null:s.name)||null)}}let Vn=(()=>{var i;class a{constructor(t,n,o,r,l,d){this.service=t,this.socket=n,this.cdr=o,this.ngZone=r,this.element=l,this.renderer=d,this.task={},this.cls="flex-row task-status",this.statuses=[],this.loading=!1,this.handleResponse=_=>{_&&_.id===this.task.id&&(this.task.status_color=_.color_code,this.task.status=_.status_id,this.task.status_category=_.statusCategory,this.isGroupByStatus()&&(this.task.is_sub_task||this.service.updateTaskGroup(this.task,!1),this.service.isSubtasksIncluded&&this.service.emitRefreshSubtasksIncluded()),this.cdr.markForCheck())},this.service.onStatusesChange$.pipe((0,G.sL)()).subscribe(()=>{this.updateStatuses(),this.cdr.markForCheck()}),this.service.onGroupChange$.pipe((0,W.h)(_=>_.taskId===this.task.id),(0,W.h)(()=>this.isGroupByStatus()),(0,G.sL)()).subscribe(_=>{this.task.status=_.groupId,this.task.status_color=_.color,this.cdr.markForCheck()})}ngOnInit(){this.updateStatuses(),this.socket.on(k.C.PT_TASK_STATUS_CHANGE.toString(),this.handleResponse)}ngOnDestroy(){this.socket.removeListener(k.C.PT_TASK_STATUS_CHANGE.toString(),this.handleResponse)}isGroupByStatus(){return this.service.getCurrentGroup().value===this.service.GROUP_BY_STATUS_VALUE}trackById(t,n){return n.id}handleStatusChange(t,n){n&&this.socket.emit(k.C.PT_TASK_STATUS_CHANGE.toString(),JSON.stringify({task_id:n,status_id:t,parent_task:this.task.parent_task_id}))}updateStatuses(){this.statuses=this.service.statuses}toggleHighlightCls(t,n){this.ngZone.runOutsideAngular(()=>{t?this.renderer.addClass(n,this.service.HIGHLIGHT_COL_CLS):this.renderer.removeClass(n,this.service.HIGHLIGHT_COL_CLS)})}handleOpen(t){this.toggleHighlightCls(t,this.element.nativeElement)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(B),e.Y36(q.s),e.Y36(e.sBO),e.Y36(e.R0b),e.Y36(e.SBq),e.Y36(e.Qsj))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-task-status"]],hostVars:2,hostBindings:function(t,n){2&t&&e.Tol(n.cls)},inputs:{task:"task"},decls:3,vars:8,consts:[[1,"py-0"],[1,"rounded-pill","custom-select",3,"ngModel","nzLoading","nzDropdownMatchSelectWidth","nzSize","ngModelChange","nzOpenChange"],["nzCustomContent","",3,"nzValue","nzLabel",4,"ngFor","ngForOf","ngForTrackBy"],["nzCustomContent","",3,"nzValue","nzLabel"],[3,"nzColor","nzText"]],template:function(t,n){1&t&&(e.TgZ(0,"div",0)(1,"nz-select",1),e.NdJ("ngModelChange",function(r){return n.task.status=r})("ngModelChange",function(r){return n.handleStatusChange(r,n.task.id)})("nzOpenChange",function(r){return n.handleOpen(r)}),e.YNc(2,qn,4,8,"nz-option",2),e.qZA()()),2&t&&(e.xp6(1),e.Udp("background-color",n.task.status_color),e.Q6J("ngModel",n.task.status)("nzLoading",n.loading)("nzDropdownMatchSelectWidth",!1)("nzSize","small"),e.xp6(1),e.Q6J("ngForOf",n.statuses)("ngForTrackBy",n.trackById))},dependencies:[h.sg,p.JJ,K.Ip,K.Vq,p.On,ie.x7,_e.m],styles:["nz-select[_ngcontent-%COMP%]{max-width:100px}"],changeDetection:0}),a})();var $e=c(8660);function Kn(i,a){if(1&i&&(e.TgZ(0,"nz-option",3),e._UZ(1,"worklenz-task-priority-label",4),e.qZA()),2&i){const s=a.$implicit;e.Q6J("nzValue",s.id)("nzLabel",s.name)("nzCustomContent",!0),e.xp6(1),e.Q6J("name",s.name)}}let Wn=(()=>{var i;class a{constructor(t,n,o,r,l,d){this.service=t,this.socket=n,this.cdr=o,this.ngZone=r,this.element=l,this.renderer=d,this.task={},this.cls="flex-row task-priority",this.priorities=[],this.loading=!1,this.handleResponse=_=>{_&&_.id===this.task.id&&(this.task.priority_color=_.color_code,this.task.priority=_.priority_id,this.isGroupByPriority()&&(this.task.is_sub_task||this.service.updateTaskGroup(this.task,!1),this.service.isSubtasksIncluded&&this.service.emitRefreshSubtasksIncluded()),this.cdr.markForCheck())},this.service.onPrioritiesChange$.pipe((0,G.sL)()).subscribe(()=>{this.updatePriorities(),this.cdr.markForCheck()}),this.service.onGroupChange$.pipe((0,W.h)(_=>_.taskId===this.task.id),(0,W.h)(()=>this.isGroupByPriority()),(0,G.sL)()).subscribe(_=>{this.task.priority=_.groupId,this.task.priority_color=_.color,this.cdr.markForCheck()})}ngOnInit(){this.updatePriorities(),this.socket.on(k.C.PT_TASK_PRIORITY_CHANGE.toString(),this.handleResponse)}ngOnDestroy(){this.priorities=[],this.socket.removeListener(k.C.PT_TASK_PRIORITY_CHANGE.toString(),this.handleResponse)}isGroupByPriority(){return this.service.getCurrentGroup().value===this.service.GROUP_BY_PRIORITY_VALUE}trackById(t,n){return n.id}handlePriorityChange(t,n){this.socket.emit(k.C.PT_TASK_PRIORITY_CHANGE.toString(),JSON.stringify({task_id:n.id,priority_id:t,parent_task:this.task.parent_task_id}))}updatePriorities(){this.priorities=this.service.priorities}toggleHighlightCls(t,n){this.ngZone.runOutsideAngular(()=>{t?this.renderer.addClass(n,this.service.HIGHLIGHT_COL_CLS):this.renderer.removeClass(n,this.service.HIGHLIGHT_COL_CLS)})}handleOpen(t){this.toggleHighlightCls(t,this.element.nativeElement)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(B),e.Y36(q.s),e.Y36(e.sBO),e.Y36(e.R0b),e.Y36(e.SBq),e.Y36(e.Qsj))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-task-priority"]],hostVars:2,hostBindings:function(t,n){2&t&&e.Tol(n.cls)},inputs:{task:"task"},decls:3,vars:8,consts:[[1,"py-0"],[1,"rounded-pill","custom-select",3,"ngModel","nzLoading","nzDropdownMatchSelectWidth","nzSize","ngModelChange","nzOpenChange"],[3,"nzValue","nzLabel","nzCustomContent",4,"ngFor","ngForOf","ngForTrackBy"],[3,"nzValue","nzLabel","nzCustomContent"],[3,"name"]],template:function(t,n){1&t&&(e.TgZ(0,"div",0)(1,"nz-select",1),e.NdJ("ngModelChange",function(r){return n.task.priority=r})("ngModelChange",function(r){return n.handlePriorityChange(r,n.task)})("nzOpenChange",function(r){return n.handleOpen(r)}),e.YNc(2,Kn,2,4,"nz-option",2),e.qZA()()),2&t&&(e.xp6(1),e.Udp("background",n.task.priority_color),e.Q6J("ngModel",n.task.priority)("nzLoading",n.loading)("nzDropdownMatchSelectWidth",!1)("nzSize","small"),e.xp6(1),e.Q6J("ngForOf",n.priorities)("ngForTrackBy",n.trackById))},dependencies:[h.sg,p.JJ,K.Ip,K.Vq,p.On,$e.o],changeDetection:0}),a})();var je=c(68373);const Xn=["labelsSearchInput"];function ei(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"div",7)(1,"nz-form-item",8)(2,"nz-form-control")(3,"div",9)(4,"nz-form-control",10)(5,"small",11),e._uU(6,"Hours"),e.qZA(),e.TgZ(7,"nz-input-number",12),e.NdJ("ngModelChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.task.total_hours=n)}),e.qZA()(),e.TgZ(8,"nz-form-control")(9,"small",11),e._uU(10,"Minutes"),e.qZA(),e.TgZ(11,"nz-input-number",13),e.NdJ("ngModelChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.task.total_minutes=n)}),e.qZA()()()()()()}if(2&i){const s=e.oxw();e.xp6(7),e.Q6J("ngModel",s.task.total_hours)("nzFormatter",s.utils.toRound)("nzMin",0)("nzPlaceHolder","Hours")("nzStep",1),e.xp6(4),e.Q6J("ngModel",s.task.total_minutes)("nzFormatter",s.utils.toRound)("nzMax",60)("nzMin",0)("nzPlaceHolder","Minutes")("nzStep",1)}}let ti=(()=>{var i;class a{constructor(t,n,o,r,l){this.cdr=t,this.socket=n,this.service=o,this.utils=r,this.ngZone=l,this.task={},this.cls="flex-row task-estimation p-0",this.show=!1,this.handleResponse=d=>{this.task.id===d?.id&&(this.task.total_time_string=d.total_time_string,this.closeDropdown(),this.cdr.markForCheck())}}ngOnInit(){this.socket.on(k.C.PT_TASK_TIME_ESTIMATION_CHANGE.toString(),this.handleResponse)}ngOnDestroy(){this.socket.removeListener(k.C.PT_TASK_TIME_ESTIMATION_CHANGE.toString(),this.handleResponse)}handleLabelsVisibleChange(t,n){this.show=t,t?n.classList.add(this.service.HIGHLIGHT_COL_CLS):n.classList.remove(this.service.HIGHLIGHT_COL_CLS)}submit(){this.task?.id&&this.socket.emit(k.C.PT_TASK_TIME_ESTIMATION_CHANGE.toString(),JSON.stringify({task_id:this.task.id,total_hours:this.task.total_hours||0,total_minutes:this.task.total_minutes||0,parent_task:this.task.parent_task_id}))}closeDropdown(){this.ngZone.runOutsideAngular(()=>{document.body.click()})}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(e.sBO),e.Y36(q.s),e.Y36(B),e.Y36(de.F),e.Y36(e.R0b))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-task-estimation"]],viewQuery:function(t,n){if(1&t&&e.Gf(Xn,5),2&t){let o;e.iGM(o=e.CRH())&&(n.labelsSearchInput=o.first)}},hostVars:2,hostBindings:function(t,n){2&t&&e.Tol(n.cls)},inputs:{task:"task"},decls:11,vars:6,consts:[["nz-dropdown","",1,"editable","w-100","h-100","d-flex","align-items-center","justify-content-center",3,"nzOverlayClassName","nzTrigger","nzClickHide","nzDropdownMenu","nzVisibleChange"],["element",""],["nz-typography","",1,"mb-0"],["estimationDropdown","nzDropdownMenu"],["class","bg-white pt-3 px-3",4,"ngIf"],[1,"bg-white","d-flex","justify-content-end","p-2","border-top"],["nz-button","","nzType","primary","nzSize","small",3,"click"],[1,"bg-white","pt-3","px-3"],["nz-row","",1,"w-100","mb-0"],[1,"d-inline-flex",2,"position","relative","top","-12px"],[1,"me-3"],["nz-typography","","nzType","secondary",1,"d-block"],[3,"ngModel","nzFormatter","nzMin","nzPlaceHolder","nzStep","ngModelChange"],[3,"ngModel","nzFormatter","nzMax","nzMin","nzPlaceHolder","nzStep","ngModelChange"]],template:function(t,n){if(1&t){const o=e.EpF();e.TgZ(0,"div",0,1),e.NdJ("nzVisibleChange",function(l){e.CHM(o);const d=e.MAs(1);return e.KtG(n.handleLabelsVisibleChange(l,d))}),e.ynx(2),e.TgZ(3,"p",2),e._uU(4),e.qZA(),e.BQk(),e.qZA(),e.TgZ(5,"nz-dropdown-menu",null,3),e.YNc(7,ei,12,11,"div",4),e.TgZ(8,"div",5)(9,"button",6),e.NdJ("click",function(){return n.submit()}),e._uU(10,"OK"),e.qZA()()()}if(2&t){const o=e.MAs(6);e.Q6J("nzOverlayClassName","custom-shadow")("nzTrigger","click")("nzClickHide",!1)("nzDropdownMenu",o),e.xp6(4),e.Oqu(n.task.total_time_string),e.xp6(3),e.Q6J("ngIf",n.show)}},dependencies:[h.O5,p.JJ,Z.t3,Z.SK,x.Nx,x.Fd,O.ix,v.w,L.dQ,Q.ZU,p.On,E.cm,E.RR,je._V],changeDetection:0}),a})(),ni=(()=>{var i;class a{transform(t,...n){return t.sub_tasks_count?"#191919":"rgba(0, 0, 0, 0.45)"}}return(i=a).\u0275fac=function(t){return new(t||i)},i.\u0275pipe=e.Yjl({name:"subTasksArrowColorPT",type:i,pure:!0}),a})(),ii=(()=>{var i;class a{transform(t,...n){return t?"down":"right"}}return(i=a).\u0275fac=function(t){return new(t||i)},i.\u0275pipe=e.Yjl({name:"subTasksArrowIconPT",type:i,pure:!0}),a})();function si(i,a){1&i&&e._UZ(0,"div",15)}function oi(i,a){1&i&&(e.TgZ(0,"div",16),e._UZ(1,"span",17),e.qZA()),2&i&&(e.xp6(1),e.Q6J("nzType","holder")("nzTheme","outline"))}function ai(i,a){if(1&i&&(e._UZ(0,"span",24),e.ALo(1,"subTasksArrowIconPT")),2&i){const s=e.oxw(3);e.Q6J("nzType",e.lcZ(1,2,s.task.show_sub_tasks))("nzTheme","outline")}}function ri(i,a){1&i&&e._UZ(0,"span",25),2&i&&e.Q6J("nzType","loading")}function li(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"div",20),e.NdJ("click",function(n){e.CHM(s);const o=e.oxw(2);return n.stopPropagation(),e.KtG(o.openSubTasks())}),e.ynx(1),e.TgZ(2,"span",21),e.ALo(3,"subTasksArrowColorPT"),e.YNc(4,ai,2,4,"span",22),e.YNc(5,ri,1,1,"span",23),e.qZA(),e.BQk(),e.qZA()}if(2&i){const s=e.oxw(2);e.xp6(2),e.Udp("color",e.lcZ(3,6,s.task)),e.ekj("hidden-arrow",!s.Number(s.task.sub_tasks_count)&&!s.task.show_sub_tasks),e.xp6(2),e.Q6J("ngIf",!s.task.sub_tasks_loading),e.xp6(1),e.Q6J("ngIf",s.task.sub_tasks_loading)}}function ci(i,a){if(1&i&&(e.TgZ(0,"div",18),e.YNc(1,li,6,8,"div",19),e.qZA()),2&i){const s=e.oxw();e.xp6(1),e.Q6J("ngIf",!s.task.is_sub_task)}}function pi(i,a){1&i&&(e.TgZ(0,"small",32),e._UZ(1,"span",17),e.qZA()),2&i&&(e.Q6J("nzType","secondary"),e.xp6(1),e.Q6J("nzType","double-right")("nzTheme","outline"))}function di(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"div",30),e.NdJ("click",function(n){e.CHM(s);const o=e.oxw(2),r=e.MAs(10);return e.KtG(o.onTaskNameClick(n,r,o.task))}),e.ALo(1,"safeString"),e.YNc(2,pi,2,3,"small",31),e._uU(3),e.qZA()}if(2&i){const s=e.oxw(2);e.Q6J("nzTooltipMouseEnterDelay",.5)("nzTooltipTitle",e.lcZ(1,4,s.task.name)),e.xp6(2),e.Q6J("ngIf",s.task.is_sub_task),e.xp6(1),e.hij(" ",s.task.name," \xa0 ")}}function ui(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"nz-tag",33),e.NdJ("click",function(n){e.CHM(s);const o=e.oxw(2);return n.stopPropagation(),e.KtG(o.openSubTasks())}),e.TgZ(1,"span",34),e._uU(2),e._UZ(3,"span",35),e.qZA()()}if(2&i){const s=e.oxw(2);e.Q6J("nzColor","default"),e.xp6(2),e.hij(" ",s.task.sub_tasks_count," "),e.xp6(1),e.Q6J("nzType","double-right")("nzTheme","outline")}}function _i(i,a){if(1&i&&(e.TgZ(0,"div",26)(1,"div",27)(2,"div"),e.YNc(3,di,4,6,"div",28),e.qZA(),e.YNc(4,ui,4,4,"nz-tag",29),e.qZA()()),2&i){const s=e.oxw();e.xp6(3),e.Q6J("ngIf",s.editId!==s.task.id),e.xp6(1),e.Q6J("ngIf",!s.task.is_sub_task&&!s.service.isSubtasksIncluded)}}function mi(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"input",36),e.NdJ("focus",function(){e.CHM(s);const n=e.oxw(),o=e.MAs(7),r=e.MAs(10);return n.selectCol(o),e.KtG(n.selectCol(r))})("ngModelChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.task.name=n)})("blur",function(){e.CHM(s);const n=e.oxw(),o=e.MAs(7),r=e.MAs(10);return n.handleNameChange(n.task),n.deselectCol(o),e.KtG(n.deselectCol(r))})("keydown.enter",function(){e.CHM(s);const n=e.oxw(),o=e.MAs(7),r=e.MAs(10);return n.handleNameChange(n.task),n.deselectCol(o),e.KtG(n.deselectCol(r))}),e.qZA()}if(2&i){const s=e.oxw();e.Q6J("ngModel",s.task.name)("nzBorderless",!0)}}let hi=(()=>{var i;class a{get id(){return this.task.id}constructor(t,n,o,r,l,d,_,y){this.element=t,this.renderer=n,this.cdr=o,this.service=r,this.socket=l,this.map=d,this.ngZone=_,this.utils=y,this.cls="position-relative task-row",this.onShowSubTasks=new e.vpe,this.highlight="highlight-col",this.Number=Number,this.editId=null,this.selected=!1,this.handleNameChangeResponse=S=>{S&&this.id===S.id&&this.task&&this.task.name!=S.name&&(this.task.name=S.name,this.markForCheck())},this.handleEstimationChangeResponse=S=>{S.id===this.id&&(this.task.total_time_string=S.total_time_string,this.cdr.markForCheck())},this.service.onColumnsChange$.pipe((0,G.sL)()).subscribe(()=>{this.markForCheck()}),(0,Ze.T)(this.map.onSelect$.pipe((0,W.h)(S=>S.id===this.id),(0,W.h)(()=>!this.selected)),this.map.onDeselect$.pipe((0,W.h)(S=>S.id===this.id),(0,W.h)(()=>this.selected)),this.map.onDeselectAll$.pipe((0,W.h)(()=>this.selected))).pipe((0,G.sL)()).subscribe(S=>{this.selected=!this.selected,this.toggleSelection(),this.markForCheck()})}toggleSelection(){this.ngZone.runOutsideAngular(()=>{const t="selected",n=this.element.nativeElement;this.selected?this.renderer.addClass(n,t):this.renderer.removeClass(n,t)})}ngOnInit(){this.registerSocketEvents()}ngOnDestroy(){this.unregisterSocketEvents()}registerSocketEvents(){this.socket.on(k.C.PT_TASK_NAME_CHANGE.toString(),this.handleNameChangeResponse),this.socket.on(k.C.PT_TASK_TIME_ESTIMATION_CHANGE.toString(),this.handleEstimationChangeResponse)}unregisterSocketEvents(){this.socket.removeListener(k.C.PT_TASK_NAME_CHANGE.toString(),this.handleNameChangeResponse),this.socket.removeListener(k.C.PT_TASK_TIME_ESTIMATION_CHANGE.toString(),this.handleEstimationChangeResponse)}onContextMenu(t){this.service.emitOnContextMenu(t,this.task)}focus(t){setTimeout(()=>{t.querySelector("input")?.focus()})}onCheckChange(t){t?this.map.selectTask(this.task):this.map.deselectTask(this.task),this.toggleSelection()}openSubTasks(){this.onShowSubTasks?.emit(this.task)}selectCol(t){t.classList.contains(this.highlight)||t.classList.add(this.highlight)}deselectCol(t){t.classList.remove(this.highlight),this.editId=null}handleNameChange(t){t&&(this.socket.emit(k.C.PT_TASK_NAME_CHANGE.toString(),JSON.stringify({task_id:t.id,name:t.name,parent_task:this.task.parent_task_id})),this.editId=null)}onTaskNameClick(t,n,o){t.stopPropagation(),this.focus(n),this.editId=o.id||null}markForCheck(){this.cdr.markForCheck()}detectChanges(){this.cdr.detectChanges()}onDragStart(){this.map.deselectAll(),this.detectChanges()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(e.SBq),e.Y36(e.Qsj),e.Y36(e.sBO),e.Y36(B),e.Y36(q.s),e.Y36(ue),e.Y36(e.R0b),e.Y36(de.F))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-pt-task-list-row"]],hostVars:2,hostBindings:function(t,n){1&t&&e.NdJ("contextmenu",function(r){return n.onContextMenu(r)}),2&t&&e.Tol(n.cls)},inputs:{task:"task"},outputs:{onShowSubTasks:"onShowSubTasks"},decls:26,vars:16,consts:[["cdkDrag","",1,"flex-table","rows",3,"ngClass","cdkDragData","cdkDragLockAxis","cdkDragBoundary","cdkDragStarted"],["class","task-placeholder",4,"cdkDragPlaceholder"],["cdkDragHandle","",1,"flex-row","task-drag-handler",3,"cdkDragHandleDisabled"],["class","drag-handle",4,"ngIf"],[1,"flex-row","task-check"],["nz-checkbox","",1,"p-0",3,"nzChecked","nzCheckedChange"],[1,"flex-row","task-arrow"],["tr0",""],["class","p-0 border-end-0",4,"ngIf"],[1,"flex-row","task-name"],["tr1",""],[1,"inner-task-name-container"],["class","editable-cell pointer-text ps-1 w-100",4,"ngIf"],["nz-input","","class","ps-1 name-input","style","min-width: 365px; max-width: 365px;",3,"ngModel","nzBorderless","focus","ngModelChange","blur","keydown.enter",4,"ngIf"],[3,"task"],[1,"task-placeholder"],[1,"drag-handle"],["nz-icon","",3,"nzType","nzTheme"],[1,"p-0","border-end-0"],["class","d-flex align-items-center justify-content-center sub-tasks-arrow dropdown-highlight",3,"click",4,"ngIf"],[1,"d-flex","align-items-center","justify-content-center","sub-tasks-arrow","dropdown-highlight",3,"click"],[1,"align-items-center","align-self-center","cursor-pointer","d-flex","sub-arrow"],["style","font-size: 12px","nz-icon","",3,"nzType","nzTheme",4,"ngIf"],["nz-icon","",3,"nzType",4,"ngIf"],["nz-icon","",2,"font-size","12px",3,"nzType","nzTheme"],["nz-icon","",3,"nzType"],[1,"editable-cell","pointer-text","ps-1","w-100"],[1,"d-flex","w-100"],["nz-tooltip","","class","task-name-text",3,"nzTooltipMouseEnterDelay","nzTooltipTitle","click",4,"ngIf"],["class","me-1 px-1 double-arrow",3,"nzColor","click",4,"ngIf"],["nz-tooltip","",1,"task-name-text",3,"nzTooltipMouseEnterDelay","nzTooltipTitle","click"],["nz-typography","",3,"nzType",4,"ngIf"],["nz-typography","",3,"nzType"],[1,"me-1","px-1","double-arrow",3,"nzColor","click"],[2,"color","#6d6e6f"],["nz-icon","",2,"font-size","10px",3,"nzType","nzTheme"],["nz-input","",1,"ps-1","name-input",2,"min-width","365px","max-width","365px",3,"ngModel","nzBorderless","focus","ngModelChange","blur","keydown.enter"]],template:function(t,n){1&t&&(e.TgZ(0,"div",0),e.NdJ("cdkDragStarted",function(){return n.onDragStart()}),e.YNc(1,si,1,0,"div",1),e.TgZ(2,"div",2),e.YNc(3,oi,2,2,"div",3),e.qZA(),e.TgZ(4,"div",4)(5,"span",5),e.NdJ("nzCheckedChange",function(r){return n.selected=r})("nzCheckedChange",function(r){return n.onCheckChange(r)}),e.qZA()(),e.TgZ(6,"div",6,7),e.YNc(8,ci,2,1,"div",8),e.qZA(),e.TgZ(9,"div",9,10)(11,"div",11),e.YNc(12,_i,5,2,"div",12),e.qZA(),e.YNc(13,mi,1,2,"input",13),e.qZA(),e.ynx(14),e._UZ(15,"worklenz-task-description",14),e.BQk(),e.ynx(16),e._UZ(17,"worklenz-task-labels",14),e.BQk(),e.ynx(18),e._UZ(19,"worklenz-task-phase",14),e.BQk(),e.ynx(20),e._UZ(21,"worklenz-task-status",14),e.BQk(),e.ynx(22),e._UZ(23,"worklenz-task-priority",14),e.BQk(),e.ynx(24),e._UZ(25,"worklenz-task-estimation",14),e.BQk(),e.qZA()),2&t&&(e.Q6J("ngClass",n.task.is_sub_task?"subtask":"main-task")("cdkDragData",n.task)("cdkDragLockAxis","y")("cdkDragBoundary",".tasks-wrapper"),e.xp6(2),e.Q6J("cdkDragHandleDisabled",!!n.task.parent_task_id),e.xp6(1),e.Q6J("ngIf",!n.task.parent_task_id),e.xp6(2),e.Q6J("nzChecked",n.selected),e.xp6(3),e.Q6J("ngIf",!n.service.isSubtasksIncluded),e.xp6(4),e.Q6J("ngIf",n.editId!==n.task.id),e.xp6(1),e.Q6J("ngIf",n.editId===n.task.id),e.xp6(2),e.Q6J("task",n.task),e.xp6(2),e.Q6J("task",n.task),e.xp6(2),e.Q6J("task",n.task),e.xp6(2),e.Q6J("task",n.task),e.xp6(2),e.Q6J("task",n.task),e.xp6(2),e.Q6J("task",n.task))},dependencies:[h.mk,h.O5,M.Ls,p.Fj,p.JJ,P.Zp,v.w,Q.ZU,J.SY,p.On,ze.j,we.Ie,Ce.Zt,Ce.Bh,Zn,Bn,jn,Vn,Wn,ti,_e.m,ni,ii],styles:["[_nghost-%COMP%]{position:relative;display:table-row;vertical-align:inherit;border-color:inherit;-webkit-user-select:none;user-select:none}[_nghost-%COMP%]:hover .plus-icon[_ngcontent-%COMP%]{display:block}[_nghost-%COMP%]:hover td[_ngcontent-%COMP%]{background:#ecf0f3}[_nghost-%COMP%]:hover .hidden-arrow[_ngcontent-%COMP%]{display:flex!important}.hidden-arrow[_ngcontent-%COMP%]{display:none!important}.dropdown-highlight[_ngcontent-%COMP%]:hover{background-color:#d0eefa54;border:#5587f5 1px solid;border-radius:3px}.plus-icon[_ngcontent-%COMP%]{display:none;position:absolute;right:0;z-index:1;top:0;bottom:0;height:100%}.expanded[_ngcontent-%COMP%]{transform:rotate(-90deg)}.sub-tasks-arrow[_ngcontent-%COMP%]{position:relative;cursor:pointer;left:3px;width:16px;padding:2px;border:1px solid transparent;z-index:1}.sub-tasks-arrow[_ngcontent-%COMP%] .sub-arrow[_ngcontent-%COMP%]{width:10px;height:10px;color:#191919;margin-left:-2px}.task-name-text[_ngcontent-%COMP%]{border:1px solid transparent;padding-left:2px;border-radius:4px}.task-name-text[_ngcontent-%COMP%]:hover{border:1px solid #d9d9d9}.task-name[_ngcontent-%COMP%]{border:1px solid transparent}.task-name[_ngcontent-%COMP%]:hover{cursor:text;background:#fff;border-radius:4px}.highlight-col[_ngcontent-%COMP%]{border:1px solid #1890ff!important}.highlight-col[_ngcontent-%COMP%] nz-date-picker[_ngcontent-%COMP%]{box-shadow:none}.editable[_ngcontent-%COMP%] .add-button[_ngcontent-%COMP%]{visibility:hidden}.editable[_ngcontent-%COMP%]:hover .add-button[_ngcontent-%COMP%]{visibility:visible}.ant-popover[_ngcontent-%COMP%]{width:500px}.flex-table[_ngcontent-%COMP%]{display:flex}.rows[_ngcontent-%COMP%] .flex-row[_ngcontent-%COMP%]{padding:3px 12px;border-bottom:1px solid #f0f0f0;border-right:1px solid #f0f0f0;background:white;display:flex;align-items:center;max-height:50px;border-radius:0}.rows[_ngcontent-%COMP%]:hover .flex-row[_ngcontent-%COMP%]{background:#f8f7f9}.subtask[_ngcontent-%COMP%] .flex-row[_ngcontent-%COMP%]{background:#fcfcfc}.task-check[_ngcontent-%COMP%]{text-align:center;padding:8px 6px 8px 0!important;border-left:none;position:sticky;left:24px;z-index:1}.task-arrow[_ngcontent-%COMP%]{width:24px;min-width:24px;padding:8px 11px 8px 2px!important;border-right:none!important;position:sticky;left:47px;z-index:1}.task-arrow.highlight-col[_ngcontent-%COMP%]{border-top:1px solid #188fff!important;border-left:1px solid #188fff!important;border-bottom:1px solid #188fff!important}.task-name[_ngcontent-%COMP%]{width:450px;min-width:450px;position:sticky;left:71px;z-index:1;border-radius:0;padding-right:65px!important}.task-name.highlight-col[_ngcontent-%COMP%]{border-top:1px solid #188fff!important;border-right:1px solid #188fff!important;border-bottom:1px solid #188fff!important;border-left:none!important}.task-name.left-0[_ngcontent-%COMP%]{left:47px}.task-key[_ngcontent-%COMP%]{width:85px;min-width:85px;padding-left:4px!important;padding-right:4px!important;justify-content:center}.task-key[_ngcontent-%COMP%] nz-tag[_ngcontent-%COMP%]{padding-left:4px;padding-right:4px;max-width:80px;text-overflow:ellipsis;overflow:hidden}.task-description[_ngcontent-%COMP%]{width:225px;min-width:225px;overflow:hidden;display:grid!important}.task-progress[_ngcontent-%COMP%]{width:80px;min-width:80px}.task-labels[_ngcontent-%COMP%]{padding:0!important}.task-labels[_ngcontent-%COMP%] .editable[_ngcontent-%COMP%]{padding:6px 11px;align-items:center;display:flex}.task-members[_ngcontent-%COMP%]{padding:0!important}.task-members[_ngcontent-%COMP%] .editable[_ngcontent-%COMP%]{padding:6px 11px;align-items:center;display:flex}.task-members[_ngcontent-%COMP%]{width:160px;min-width:160px}.task-labels[_ngcontent-%COMP%]{width:220px;min-width:220px}.task-status[_ngcontent-%COMP%]{width:120px;min-width:120px}.task-phase[_ngcontent-%COMP%]{width:150px;min-width:150px}.task-priority[_ngcontent-%COMP%], .task-time-tracking[_ngcontent-%COMP%], .task-estimation[_ngcontent-%COMP%]{width:120px;min-width:120px}.task-start-date[_ngcontent-%COMP%], .task-due-date[_ngcontent-%COMP%], .task-completed-date[_ngcontent-%COMP%], .task-created-date[_ngcontent-%COMP%], .task-update-date[_ngcontent-%COMP%]{width:150px;min-width:150px}.task-due-date[_ngcontent-%COMP%]{padding:0!important}.task-due-date[_ngcontent-%COMP%] .editable[_ngcontent-%COMP%]{align-items:center;display:flex}.task-drag-handler[_ngcontent-%COMP%]{padding:0 0 0 4px!important;width:24px;min-width:24px;border-right:none!important;position:sticky;left:0;z-index:1;background-color:#fff}.drag-handle[_ngcontent-%COMP%]{cursor:grab;opacity:.8}.drag-handle[_ngcontent-%COMP%]:hover span[_ngcontent-%COMP%]{color:#1890ff}.drag-handle[_ngcontent-%COMP%]:active{cursor:grabbing}.task-name-text[_ngcontent-%COMP%]{width:100%;-webkit-line-clamp:1;display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis}.inner-icon-cont[_ngcontent-%COMP%]{width:max-content;display:flex;justify-content:flex-end;align-items:center;column-gap:4px}.name-input[_ngcontent-%COMP%]{padding:5px 12px;border-left:1px solid transparent}.double-arrow[_ngcontent-%COMP%]{line-height:16px;border:none;cursor:pointer}.task-placeholder[_ngcontent-%COMP%]{width:100%;height:42px;border:1px dashed #d9d9d9;background:#fafafa}.v-line[_ngcontent-%COMP%]{background-color:#188fff!important;position:absolute;inset:0 -5px 0 0;width:1px;margin:auto}.double-arrow[_ngcontent-%COMP%]{height:16px;margin-top:4px}"]}),a})();var gi=c(59773);const fi=["contextMenuDropdown"];function zi(i,a){if(1&i&&e._uU(0),2&i){const s=e.oxw().$implicit;e.hij(" ",(null==s?null:s.name)||null," ")}}function Ti(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"li",9),e.NdJ("click",function(){const o=e.CHM(s).$implicit,r=e.oxw(2);return e.KtG(r.changeGroup(o.id))}),e._UZ(1,"nz-badge",10),e.ALo(2,"safeString"),e.YNc(3,zi,1,1,"ng-template",null,11,e.W1O),e.qZA()}if(2&i){const s=a.$implicit,t=e.MAs(4);e.xp6(1),e.Q6J("nzColor",e.lcZ(2,2,s.color_code))("nzText",t)}}function Ci(i,a){if(1&i&&(e.TgZ(0,"li",7)(1,"ul"),e.YNc(2,Ti,5,4,"li",8),e.qZA()()),2&i){const s=e.oxw(),t=e.MAs(9);e.Q6J("nzTitle",t),e.xp6(2),e.Q6J("ngForOf",s.groups)}}function ki(i,a){1&i&&(e._UZ(0,"span",12),e._uU(1," Move to\n"))}let xi=(()=>{var i;class a{constructor(t,n,o,r,l,d){this.contextMenuService=t,this.service=n,this.map=o,this.api=r,this.socket=l,this.cdr=d,this.templateId=null,this.groups=[],this.deleting=!1,this.hasSubTasks=!1,this.selectedTask=null,this.destroy$=new R.x,this.service.onContextMenu$.pipe((0,gi.R)(this.destroy$)).subscribe(_=>{this.onContextMenu(_)})}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}onContextMenu(t){this.selectedTask=t.task,this.map.deselectAll(),this.map.selectTask(t.task),this.hasSubTasks=this.isSelectionHasSubTasks(),this.cdr.detectChanges(),this.contextMenuService.create(t.event,this.contextMenuDropdown)}isSelectionHasSubTasks(){return this.map.getSelectedTasks().some(t=>t.is_sub_task)}changeGroup(t){if(!this.selectedTask)return;const n=this.service.getCurrentGroup();n.value===this.service.GROUP_BY_STATUS_VALUE?this.handleStatusChange(t,this.selectedTask.id):n.value===this.service.GROUP_BY_PRIORITY_VALUE?this.handlePriorityChange(t,this.selectedTask.id):n.value===this.service.GROUP_BY_PHASE_VALUE&&this.handlePhaseChange(t,this.selectedTask.id)}handleStatusChange(t,n){n&&this.socket.emit(k.C.PT_TASK_STATUS_CHANGE.toString(),JSON.stringify({task_id:n,status_id:t}))}handlePriorityChange(t,n){n&&this.socket.emit(k.C.PT_TASK_PRIORITY_CHANGE.toString(),JSON.stringify({task_id:n,priority_id:t}))}handlePhaseChange(t,n){n&&this.socket.emit(k.C.PT_TASK_PHASE_CHANGE.toString(),{task_id:n,phase_id:t})}delete(){var t=this;return(0,m.Z)(function*(){if(!t.deleting)try{t.deleting=!0;const n=t.map.getSelectedTaskIds(),o=yield t.api.bulkDelete({tasks:n},t.templateId);if(o.done)for(const r of o.body.deleted_tasks)t.service.deleteTask(r);t.deleting=!1}catch{t.deleting=!1}})()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(E.Iw),e.Y36(B),e.Y36(ue),e.Y36(Ye),e.Y36(q.s),e.Y36(e.sBO))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-context-menu"]],viewQuery:function(t,n){if(1&t&&e.Gf(fi,5),2&t){let o;e.iGM(o=e.CRH())&&(n.contextMenuDropdown=o.first)}},inputs:{templateId:"templateId",groups:"groups"},decls:10,vars:4,consts:[["contextMenuDropdown","nzDropdownMenu"],["nz-menu",""],[3,"ngSwitch"],["nz-submenu","",3,"nzTitle",4,"ngSwitchCase"],["nz-menu-item","",3,"click"],["nz-icon","",1,"me-2",3,"nzType","nzTheme"],["titleTemplate",""],["nz-submenu","",3,"nzTitle"],["class","m-0","nz-menu-item","",3,"click",4,"ngFor","ngForOf"],["nz-menu-item","",1,"m-0",3,"click"],[3,"nzColor","nzText"],["textTmpl",""],["nz-icon","","nzType","retweet","nzTheme","outline",1,"me-2"]],template:function(t,n){1&t&&(e.TgZ(0,"nz-dropdown-menu",null,0)(2,"ul",1),e.ynx(3,2),e.YNc(4,Ci,3,2,"li",3),e.BQk(),e.TgZ(5,"li",4),e.NdJ("click",function(){return n.delete()}),e._UZ(6,"span",5),e._uU(7," Delete "),e.qZA()()(),e.YNc(8,ki,2,0,"ng-template",null,6,e.W1O)),2&t&&(e.xp6(3),e.Q6J("ngSwitch",n.hasSubTasks),e.xp6(1),e.Q6J("ngSwitchCase",!1),e.xp6(2),e.Q6J("nzType",n.deleting?"loading":"delete")("nzTheme","outline"))},dependencies:[h.sg,h.RF,h.n9,M.Ls,v.w,T.wO,T.r9,T.rY,ie.x7,E.RR,_e.m],changeDetection:0}),a})();var bi=c(34554),Fe=c(66987);function vi(i,a){1&i&&(e.TgZ(0,"button",20),e._uU(1," Search "),e.qZA()),2&i&&e.Q6J("nzSize","small")("nzType","primary")}function wi(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",21),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.reset())}),e._uU(1," Reset "),e.qZA()}2&i&&e.Q6J("nzSize","small")}function yi(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"li",22),e.NdJ("click",function(){const o=e.CHM(s).$implicit,r=e.oxw();return e.KtG(r.changeGroup(o))}),e.TgZ(1,"span",23),e._uU(2),e.qZA()()}if(2&i){const s=a.$implicit,t=e.oxw();e.Q6J("nzSelected",s.value===t.selectedGroup.value),e.xp6(2),e.Oqu(s.label)}}function Si(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",24),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.createStatusClick())}),e._uU(1," Add Status "),e.qZA()}2&i&&e.Q6J("nzType","primary")("nzTooltipTitle","Create status")}function Pi(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",24),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.phaseSettingsClick())}),e._uU(1," Add Phase "),e.qZA()}if(2&i){const s=e.oxw();e.Q6J("nzType","primary")("nzTooltipTitle",s.phaseLabel+" settings")}}let Mi=(()=>{var i;class a{get selectedGroup(){return this.service.getCurrentGroup()}get phaseLabel(){return this.phaseService.label}constructor(t,n,o,r,l,d,_,y){this.cdr=t,this.ngZone=n,this.tasksApi=o,this.socket=r,this.utils=l,this.phaseService=d,this.service=_,this.auth=y,this.onGroupBy=new e.vpe,this.onFilterSearch=new e.vpe,this.onPhaseSettingsClick=new e.vpe,this.onCreateStatusClick=new e.vpe,this.ASCEND="ascend",this.DESCEND="descend",this.COUNTS_LABELS_STYLE={backgroundColor:"#1890ff",color:"#fff"},this.taskSearch=null}changeGroup(t){this.service.setCurrentGroup(t),this.onGroupBy.emit(t)}isGroupByStatus(){return this.selectedGroup.value===this.service.GROUP_BY_STATUS_VALUE}isGroupByPhase(){return this.selectedGroup.value===this.service.GROUP_BY_PHASE_VALUE}trackById(t,n){return n.id}toIdsMap(t){return t.map(n=>n.id).join("+")}search(){this.taskSearch&&(this.onFilterSearch.emit(encodeURIComponent(this.taskSearch)),document.body.click())}reset(){this.taskSearch&&(this.taskSearch=null,this.onFilterSearch.emit(this.taskSearch),this.ngZone.runOutsideAngular(()=>{document.body.click()}))}onSearchDropdownVisibleChange(t){t&&this.ngZone.runOutsideAngular(()=>{setTimeout(()=>{document.querySelector("#task-search-input")?.focus()},j.GR)})}phaseSettingsClick(){this.onPhaseSettingsClick?.emit()}createStatusClick(){this.onCreateStatusClick?.emit()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(e.sBO),e.Y36(e.R0b),e.Y36(bi.c),e.Y36(q.s),e.Y36(de.F),e.Y36(Re.u),e.Y36(B),e.Y36(w.e))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-group-filter"]],inputs:{templateId:"templateId"},outputs:{onGroupBy:"onGroupBy",onFilterSearch:"onFilterSearch",onPhaseSettingsClick:"onPhaseSettingsClick",onCreateStatusClick:"onCreateStatusClick"},decls:26,vars:22,consts:[["nz-form","",3,"nzLayout"],["nz-row","",3,"nzAlign","nzJustify"],["nz-col",""],["nz-button","","nz-dropdown","",1,"me-2",3,"nzDropdownMenu","nzTrigger","nzVisibleChange"],["nz-icon","","nzType","search","nzTheme","outline"],["taskSearchDropdown","nzDropdownMenu"],[1,"bg-white","shadow","rounded-4","p-2",3,"submit"],["type","text","nz-input","","name","search","id","task-search-input","placeholder","Search by name",3,"ngModel","ngModelChange"],[1,"mt-2"],["nz-button","","type","submit",3,"nzSize","nzType",4,"nzSpaceItem"],["nz-button","","type","button",3,"nzSize","click",4,"nzSpaceItem"],[3,"nzType"],[1,"ms-1"],["nz-button","","nz-dropdown","",1,"ms-1","me-2",3,"nzTrigger","nzDropdownMenu","nzClickHide"],["nz-tooltip","",3,"nzTooltipTitle"],["nz-icon","",3,"nzType"],["groupByMenu",""],["nz-menu",""],["class","m-0","nz-menu-item","",3,"nzSelected","click",4,"ngFor","ngForOf","ngForTrackBy"],["nz-button","","class","ms-0","nz-tooltip","",3,"nzType","nzTooltipTitle","click",4,"ngIf"],["nz-button","","type","submit",3,"nzSize","nzType"],["nz-button","","type","button",3,"nzSize","click"],["nz-menu-item","",1,"m-0",3,"nzSelected","click"],["nz-typography",""],["nz-button","","nz-tooltip","",1,"ms-0",3,"nzType","nzTooltipTitle","click"]],template:function(t,n){if(1&t&&(e.TgZ(0,"form",0)(1,"div",1)(2,"div",2)(3,"button",3),e.NdJ("nzVisibleChange",function(r){return n.onSearchDropdownVisibleChange(r)}),e._UZ(4,"span",4),e.qZA(),e.TgZ(5,"nz-dropdown-menu",null,5)(7,"form",6),e.NdJ("submit",function(){return n.search()}),e.TgZ(8,"input",7),e.NdJ("ngModelChange",function(r){return n.taskSearch=r}),e.qZA(),e.TgZ(9,"nz-space",8),e.YNc(10,vi,2,2,"button",9),e.YNc(11,wi,2,1,"button",10),e.qZA()()(),e._UZ(12,"nz-divider",11),e.TgZ(13,"label",12),e._uU(14,"Group by: "),e.qZA(),e.TgZ(15,"button",13)(16,"span",14),e._uU(17),e.ALo(18,"ellipsis"),e.qZA(),e._UZ(19,"span",15),e.qZA(),e.TgZ(20,"nz-dropdown-menu",null,16)(22,"ul",17),e.YNc(23,yi,3,2,"li",18),e.qZA()(),e.YNc(24,Si,2,2,"button",19),e.YNc(25,Pi,2,2,"button",19),e.qZA()()()),2&t){const o=e.MAs(6),r=e.MAs(21);e.Q6J("nzLayout","vertical"),e.xp6(1),e.Q6J("nzAlign","bottom")("nzJustify","space-between"),e.xp6(2),e.ekj("filter-active",!!n.taskSearch),e.Q6J("nzDropdownMenu",o)("nzTrigger","click"),e.xp6(5),e.Q6J("ngModel",n.taskSearch),e.xp6(4),e.Q6J("nzType","vertical"),e.xp6(3),e.Q6J("nzTrigger","click")("nzDropdownMenu",r)("nzClickHide",!0),e.xp6(1),e.Q6J("nzTooltipTitle",n.selectedGroup.label),e.xp6(1),e.hij(" ",e.xi3(18,19,n.selectedGroup.label,15)," "),e.xp6(2),e.Q6J("nzType","caret-down"),e.xp6(4),e.Q6J("ngForOf",n.service.GROUP_BY_OPTIONS)("ngForTrackBy",n.trackById),e.xp6(1),e.Q6J("ngIf",n.isGroupByStatus()&&n.auth.isOwnerOrAdmin()),e.xp6(1),e.Q6J("ngIf",n.isGroupByPhase()&&n.auth.isOwnerOrAdmin())}},dependencies:[h.sg,h.O5,M.Ls,p._Y,p.Fj,p.JJ,p.JL,Z.t3,Z.SK,x.Lr,P.Zp,O.ix,v.w,L.dQ,Q.ZU,T.wO,T.r9,H.NU,H.$1,J.SY,p.On,p.F,E.cm,E.RR,E.wA,Fe.g,Le.p]}),a})(),Oi=(()=>{var i;class a{get label(){return this._label||this.DEFAULT_LABEL}set label(t){this._label=t||this.DEFAULT_LABEL}get onLabelChange(){return this._labelChangeSbj$.asObservable()}get onPhaseOptionsChange(){return this._phaseOptionsChangeSbj$.asObservable()}constructor(t){this.list=t,this._labelChangeSbj$=new R.x,this._phaseOptionsChangeSbj$=new R.x,this.DEFAULT_LABEL="Phase",this._label=null}updateLabel(t){const n=this.list.GROUP_BY_OPTIONS.find(o=>o.value===this.list.GROUP_BY_PHASE_VALUE);n&&(this.label=t,n.label=this.label,this._labelChangeSbj$.next(this.label))}emitOptionsChange(){this._phaseOptionsChangeSbj$.next()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.LFG(B))},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})();var ke=c(43389);function Ai(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"li",23),e.NdJ("click",function(){const o=e.CHM(s).$implicit,r=e.oxw().$implicit,l=e.oxw(2);return e.KtG(l.setColorCode(r,o))}),e.TgZ(1,"nz-tag",24),e._uU(2,"\xa0 "),e.qZA()()}if(2&i){const s=a.$implicit;e.xp6(1),e.Q6J("nzColor",s+"69")}}function Ii(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"nz-form-item",14)(1,"nz-form-control")(2,"input",15,16),e.NdJ("ngModelChange",function(n){const r=e.CHM(s).$implicit;return e.KtG(r.name=n)})("focus",function(){const o=e.CHM(s).$implicit,r=e.MAs(3),l=e.oxw(2);return r.select(),e.KtG(l.setNameCache(o.id,o.name))})("blur",function(){const o=e.CHM(s).$implicit,r=e.oxw(2);return e.KtG(r.updateOption(o))})("keyup.enter",function(){e.CHM(s);const n=e.MAs(3);return e.KtG(n.blur())}),e.qZA()(),e.TgZ(4,"nz-tag",17),e._uU(5,"\xa0 "),e.qZA(),e.TgZ(6,"nz-dropdown-menu",null,18)(8,"ul",19),e.YNc(9,Ai,3,1,"li",20),e.qZA()(),e.TgZ(10,"nz-form-label",21)(11,"button",22),e.NdJ("click",function(){const o=e.CHM(s).$implicit,r=e.oxw(2);return e.KtG(r.removeOption(o.id))}),e._UZ(12,"span",12),e.qZA()()()}if(2&i){const s=a.$implicit,t=a.index,n=e.MAs(7),o=e.oxw(2);e.xp6(2),e.Q6J("name","opt"+t)("ngModel",s.name)("minLength",2)("maxLength",30)("disabled",o.deleting[s.id]),e.xp6(2),e.Q6J("nzColor",s.color_code)("nzDropdownMenu",n)("nzTrigger","click"),e.xp6(5),e.Q6J("ngForOf",o.COLOR_CODES),e.xp6(1),e.Q6J("nzNoColon",!0),e.xp6(1),e.Q6J("nzShape","circle")("nzType","text")("nzLoading",o.deleting[s.id]),e.xp6(1),e.Q6J("nzType","close-circle")("nzTheme","outline")}}function Zi(i,a){if(1&i){const s=e.EpF();e.ynx(0),e.TgZ(1,"nz-skeleton",2)(2,"form",3)(3,"nz-form-item")(4,"nz-form-label",4),e._uU(5,"Phase Label:"),e.qZA(),e.TgZ(6,"nz-form-control")(7,"input",5),e.NdJ("ngModelChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.phaseLabel=n)})("focus",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.oldLabel=n.phaseLabel)})("blur",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.updateLabel(n.phaseLabel))}),e.qZA()()()(),e._UZ(8,"nz-divider",6),e.TgZ(9,"form",7)(10,"div",8)(11,"div",9)(12,"nz-form-label"),e._uU(13," Phase Options "),e.qZA()(),e.TgZ(14,"div",10)(15,"button",11),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.addNewOption())}),e._UZ(16,"span",12),e._uU(17," Add Option "),e.qZA()()(),e.YNc(18,Ii,13,15,"nz-form-item",13),e.qZA()(),e.BQk()}if(2&i){const s=e.oxw();e.xp6(1),e.Q6J("nzActive",!0)("nzLoading",s.loading),e.xp6(1),e.Q6J("nzLayout","vertical"),e.xp6(2),e.Q6J("nzFor","label"),e.xp6(3),e.Q6J("ngModel",s.phaseLabel),e.xp6(8),e.Q6J("nzType","primary")("nzLoading",s.creating),e.xp6(1),e.Q6J("nzType","plus")("nzTheme","outline"),e.xp6(2),e.Q6J("ngForOf",s.options)}}let Li=(()=>{var i;class a{get options(){return this.list.phases}constructor(t,n,o,r){this.api=t,this.cdr=n,this.list=o,this.service=r,this.templateId=null,this.show=!1,this.showChange=new e.vpe,this.onCreateOrUpdate=new e.vpe,this.refresh=new e.vpe,this.COLOR_CODES=j.zW,this.loading=!1,this.creating=!1,this.updatingLabel=!1,this.updating={},this.deleting={},this.updateCache={},this.oldLabel=null,this.phaseLabel=null}close(){this.show=!1,this.showChange.emit(!1)}addNewOption(){this.create()}onVisibleChange(t){t&&(this.get(!0),this.phaseLabel=this.service.label)}removeOption(t){t&&this.delete(t)}updateOption(t){var n=this;return(0,m.Z)(function*(){yield n.update(t),delete n.updateCache[t.id]})()}setNameCache(t,n){this.updateCache[t]=n}create(){var t=this;return(0,m.Z)(function*(){if(t.templateId&&!t.creating){try{t.creating=!0,(yield t.api.create(t.templateId)).done&&(yield t.get(!1),t.service.emitOptionsChange(),t.onCreateOrUpdate.emit()),t.creating=!1}catch{t.creating=!1}t.cdr.markForCheck()}})()}get(t){var n=this;return(0,m.Z)(function*(){if(n.templateId){try{n.loading=t;const o=yield n.api.get(n.templateId);o.done&&(n.list.phases=o.body),n.loading=!1}catch{n.loading=!1}n.cdr.markForCheck()}})()}updateLabel(t){var n=this;return(0,m.Z)(function*(){if(n.templateId){if(!t?.trim())return void(n.phaseLabel=n.oldLabel);try{n.updatingLabel=!0,(yield n.api.updateLabel(n.templateId,t?.trim())).done&&n.service.updateLabel(t?.trim()),n.updatingLabel=!1}catch{n.updatingLabel=!1}n.cdr.markForCheck()}})()}update(t){var n=this;return(0,m.Z)(function*(){if(t?.id&&n.templateId&&!n.updating[t.id]&&n.updateCache[t.id]!==t.name){try{n.updating[t.id]=!0,(yield n.api.update(n.templateId,t)).done&&(yield n.get(!1),n.service.emitOptionsChange(),n.onCreateOrUpdate.emit()),n.updating[t.id]=!1}catch{t.name=n.updateCache[t.id],n.updating[t.id]=!1}n.cdr.markForCheck()}})()}delete(t){var n=this;return(0,m.Z)(function*(){if(t&&n.templateId&&!n.deleting[t]){try{if(n.deleting[t]=!0,(yield n.api.delete(t,n.templateId)).done){const r=n.list.phases.findIndex(l=>l.id===t);r>-1&&(n.list.phases.splice(r,1),n.service.emitOptionsChange(),n.onCreateOrUpdate.emit())}n.deleting[t]=!1}catch{n.deleting[t]=!1}n.cdr.markForCheck()}})()}setColorCode(t,n){var o=this;return(0,m.Z)(function*(){t.color_code=n+"69",yield o.updateColor(t)})()}updateColor(t){var n=this;return(0,m.Z)(function*(){if(t?.id&&n.templateId){try{n.updating[t.id]=!0,(yield n.api.updateColor(n.templateId,t)).done&&n.refresh.emit(),n.updating[t.id]=!1}catch{t.name=n.updateCache[t.id],n.updating[t.id]=!1}n.cdr.markForCheck()}})()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(Ie),e.Y36(e.sBO),e.Y36(B),e.Y36(Oi))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-phase-settings-drawer"]],inputs:{templateId:"templateId",show:"show"},outputs:{showChange:"showChange",onCreateOrUpdate:"onCreateOrUpdate",refresh:"refresh"},decls:2,vars:4,consts:[[3,"nzClosable","nzVisible","nzPlacement","nzTitle","nzOnClose","nzVisibleChange"],[4,"nzDrawerContent"],[3,"nzActive","nzLoading"],["nz-form","",3,"nzLayout"],[3,"nzFor"],["nz-input","","id","label","placeholder","Enter a name for label","name","label","required","",3,"ngModel","ngModelChange","focus","blur"],[1,"mb-3"],["nz-form",""],[1,"d-flex","align-items-center","justify-content-between","mb-3"],[1,"d-block"],[1,"d-block","ms-auto"],["type","button","nz-button","","nzBlock","",3,"nzType","nzLoading","click"],["nz-icon","",3,"nzType","nzTheme"],["class","d-flex align-items-center mb-3",4,"ngFor","ngForOf"],[1,"d-flex","align-items-center","mb-3"],["nz-input","",3,"name","ngModel","minLength","maxLength","disabled","ngModelChange","focus","blur","keyup.enter"],["input",""],["nz-dropdown","",1,"ms-2","rounded-circle","cursor-pointer",2,"width","20px","height","20px",3,"nzColor","nzDropdownMenu","nzTrigger"],["menu","nzDropdownMenu"],["nz-menu","","nzSelectable","",2,"max-height","200px","overflow","hidden","overflow-y","auto"],["nz-menu-item","",3,"click",4,"ngFor","ngForOf"],[3,"nzNoColon"],["type","button","nz-button","",3,"nzShape","nzType","nzLoading","click"],["nz-menu-item","",3,"click"],[1,"me-1","w-100","rounded-pill",2,"height","16px !important","width","16px !important",3,"nzColor"]],template:function(t,n){1&t&&(e.TgZ(0,"nz-drawer",0),e.NdJ("nzOnClose",function(){return n.close()})("nzVisibleChange",function(r){return n.onVisibleChange(r)}),e.YNc(1,Zi,19,10,"ng-container",1),e.qZA()),2&t&&e.Q6J("nzClosable",!0)("nzVisible",n.show)("nzPlacement","right")("nzTitle","Configure Phases")},dependencies:[h.sg,M.Ls,p._Y,p.Fj,p.JJ,p.JL,p.Q7,Z.t3,Z.SK,x.Lr,x.Nx,x.iK,x.Fd,P.Zp,O.ix,v.w,L.dQ,T.wO,T.r9,$.ng,p.On,p.F,E.cm,E.RR,ze.j,ke.Vz,ke.SQ,Fe.g],changeDetection:0}),a})();function Fi(i,a){if(1&i&&(e.TgZ(0,"nz-option",11),e._UZ(1,"nz-badge",12),e.qZA()),2&i){const s=a.$implicit;e.Q6J("nzLabel",s.name||null)("nzValue",s.id),e.xp6(1),e.Q6J("nzTooltipTitle",s.description)("nzText",(null==s?null:s.name)||null)("nzTooltipPlacement","left")("nzColor",s.color_code)}}function Ni(i,a){if(1&i){const s=e.EpF();e.ynx(0),e.TgZ(1,"nz-skeleton",2)(2,"form",3),e.NdJ("submit",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.submit())}),e.TgZ(3,"nz-form-item")(4,"nz-form-label",4),e._uU(5,"Name"),e.qZA(),e.TgZ(6,"nz-form-control",5),e._UZ(7,"input",6),e.qZA()(),e.TgZ(8,"nz-form-item")(9,"nz-form-label",4),e._uU(10,"Category"),e.qZA(),e.TgZ(11,"nz-form-control",7)(12,"nz-select",8),e.YNc(13,Fi,2,6,"nz-option",9),e.qZA()()(),e.TgZ(14,"button",10),e._uU(15),e.qZA()()(),e.BQk()}if(2&i){const s=e.oxw();e.xp6(1),e.Q6J("nzActive",!0)("nzLoading",s.loading),e.xp6(1),e.Q6J("formGroup",s.form)("nzLayout","vertical"),e.xp6(2),e.Q6J("nzSpan",null),e.xp6(2),e.Q6J("nzSpan",null),e.xp6(1),e.Q6J("formControlName","name"),e.xp6(2),e.Q6J("nzSpan",null),e.xp6(2),e.Q6J("nzSpan",null),e.xp6(1),e.Q6J("formControlName","category_id"),e.xp6(1),e.Q6J("ngForOf",s.categories),e.xp6(2),e.Oqu(s.action)}}let Ei=(()=>{var i;class a{constructor(t,n,o){this.api=t,this.fb=n,this.app=o,this.action="Create",this.show=!1,this.statusId=null,this.templateId=null,this.showChange=new e.vpe,this.onCreateOrUpdate=new e.vpe,this.loading=!0,this.loadingCategories=!1,this.colorCodes=j.lD,this.categories=[],this.taskStatus={},this.createForm()}init(){this.form.controls.template_id.setValue(this.templateId),this.getCategories(),this.statusId?this.getById(this.statusId):this.loading=!1}closeModal(){this.show=!1,this.form.reset(),this.action="Create",this.createForm(),this.showChange.emit()}submit(){var t=this;return(0,m.Z)(function*(){t.taskStatus&&t.taskStatus.id?yield t.updateStatus():yield t.addStatus()})()}getById(t){var n=this;return(0,m.Z)(function*(){try{n.loading=!0;const o=yield n.api.getById(t);o.done&&(n.taskStatus=o.body,n.form.patchValue(n.taskStatus)),n.loading=!1}catch(o){(0,b.tu)(o),n.loading=!1}})()}getCategories(){var t=this;return(0,m.Z)(function*(){try{t.loadingCategories=!0;const n=yield t.api.getCategories();n.done&&(t.categories=n.body,t.form.controls.category_id.setValue(t.categories[0].id)),t.loadingCategories=!1}catch(n){t.loadingCategories=!1,(0,b.tu)(n)}})()}addStatus(){var t=this;return(0,m.Z)(function*(){if(t.form.invalid)t.app.displayErrorsOf(t.form);else try{const n=yield t.api.create(t.form.value);n.done&&(n.body.color_code=n.body.color_code+"69",t.onCreateOrUpdate.emit(),t.closeModal())}catch(n){(0,b.tu)(n)}})()}updateStatus(){var t=this;return(0,m.Z)(function*(){if(t.taskStatus&&t.taskStatus.id){if(t.form.invalid)return void t.app.displayErrorsOf(t.form);try{(yield t.api.update(t.taskStatus.id,t.form.value)).done&&(t.onCreateOrUpdate.emit(),t.closeModal(),(0,me.mT)())}catch(n){(0,b.tu)(n)}}})()}onVisibilityChange(t){t&&setTimeout(()=>this.init(),100)}createForm(){this.form=this.fb.group({name:[null,[p.kI.required]],category_id:[null,[p.kI.required]],template_id:[this.templateId]})}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(Ae),e.Y36(p.qu),e.Y36(U.z))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-status-settings-drawer"]],inputs:{action:"action",show:"show",statusId:"statusId",templateId:"templateId"},outputs:{showChange:"showChange",onCreateOrUpdate:"onCreateOrUpdate"},decls:2,vars:3,consts:[["nzPlacement","right",3,"nzVisible","nzClosable","nzTitle","nzOnClose","nzVisibleChange"],[4,"nzDrawerContent"],[3,"nzActive","nzLoading"],["nz-form","",3,"formGroup","nzLayout","submit"],["nzRequired","",3,"nzSpan"],["nzErrorTip","Please enter a name!",3,"nzSpan"],["nz-input","","placeholder","Name",3,"formControlName"],[3,"nzSpan"],[3,"formControlName"],["nzCustomContent","",3,"nzLabel","nzValue",4,"ngFor","ngForOf"],["nz-button","","nzBlock","","nzType","primary","type","submit"],["nzCustomContent","",3,"nzLabel","nzValue"],["nz-tooltip","",1,"w-100",3,"nzTooltipTitle","nzText","nzTooltipPlacement","nzColor"]],template:function(t,n){1&t&&(e.TgZ(0,"nz-drawer",0),e.NdJ("nzOnClose",function(){return n.closeModal()})("nzVisibleChange",function(r){return n.show=r})("nzVisibleChange",function(r){return n.onVisibilityChange(r)}),e.YNc(1,Ni,16,12,"ng-container",1),e.qZA()),2&t&&(e.MGl("nzTitle","",n.action," Status"),e.Q6J("nzVisible",n.show)("nzClosable",!0))},dependencies:[h.sg,p._Y,p.Fj,p.JJ,p.JL,p.sg,p.u,Z.t3,Z.SK,x.Lr,x.Nx,x.iK,x.Fd,P.Zp,O.ix,v.w,L.dQ,J.SY,$.ng,K.Ip,K.Vq,ie.x7,ke.Vz,ke.SQ]}),a})();const Ui=["input"];function Ji(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"h4",3),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.focusInput())}),e._uU(1),e.qZA()}if(2&i){const s=e.oxw();e.xp6(1),e.Oqu(s.templateName)}}function Gi(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"input",4,5),e.NdJ("ngModelChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.templateName=n)})("ngModelChange",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.validate())})("blur",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.onBlur())}),e.qZA()}if(2&i){const s=e.oxw();e.Q6J("ngClass",s.isEmpty?"error":"")("ngModel",s.templateName)}}function Di(i,a){1&i&&(e.TgZ(0,"small",6),e._uU(1,"Name cannot be empty!"),e.qZA())}let Yi=(()=>{var i;class a{constructor(t,n,o,r){this.socket=t,this.ngZone=n,this.renderer=o,this.cdr=r,this.templateId=null,this.templateName=null,this.showInput=!1,this.isEmpty=!1,this.handleResponse=l=>{l&&(this.templateName=l.template_name,this.showInput=!1,this.cdr.markForCheck())}}ngOnInit(){this.socket.on(k.C.PT_NAME_CHANGE.toString(),this.handleResponse)}focusInput(){this.showInput=!0,this.ngZone.runOutsideAngular(()=>{setTimeout(()=>{this.input&&(this.input.nativeElement.focus(),this.cdr.markForCheck())},100)})}onBlur(){this.validate()&&this.changeName()}validate(){return""!==this.templateName?.trim()&&this.templateName?(this.isEmpty=!1,!0):(this.isEmpty=!0,!1)}changeName(){this.socket.emit(k.C.PT_NAME_CHANGE.toString(),JSON.stringify({template_id:this.templateId,template_name:this.templateName}))}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(q.s),e.Y36(e.R0b),e.Y36(e.Qsj),e.Y36(e.sBO))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-template-name"]],viewQuery:function(t,n){if(1&t&&e.Gf(Ui,5),2&t){let o;e.iGM(o=e.CRH())&&(n.input=o.first)}},inputs:{templateId:"templateId",templateName:"templateName"},decls:3,vars:3,consts:[["nz-typography","","class","mb-0",3,"click",4,"ngIf"],["nz-input","","placeholder","Template Name","type","text","class","temp-name-input",3,"ngClass","ngModel","ngModelChange","blur",4,"ngIf"],["nz-typography","","nzType","danger","class","error-text",4,"ngIf"],["nz-typography","",1,"mb-0",3,"click"],["nz-input","","placeholder","Template Name","type","text",1,"temp-name-input",3,"ngClass","ngModel","ngModelChange","blur"],["input",""],["nz-typography","","nzType","danger",1,"error-text"]],template:function(t,n){1&t&&(e.YNc(0,Ji,2,1,"h4",0),e.YNc(1,Gi,2,2,"input",1),e.YNc(2,Di,2,0,"small",2)),2&t&&(e.Q6J("ngIf",!n.showInput),e.xp6(1),e.Q6J("ngIf",n.showInput),e.xp6(1),e.Q6J("ngIf",n.isEmpty))},dependencies:[h.mk,h.O5,p.Fj,p.JJ,P.Zp,Q.ZU,p.On],styles:[".temp-name-input[_ngcontent-%COMP%]{color:#000000d9;font-weight:600;font-size:18px;line-height:1.4}.error[_ngcontent-%COMP%]{border-color:#ff4d4f;box-shadow:none}.error-text[_ngcontent-%COMP%]{position:absolute;left:0;bottom:-16px}h4[_ngcontent-%COMP%]{transition:.25s all;border:1px solid transparent}h4[_ngcontent-%COMP%]:hover{border:1px solid #d9d9d9;border-radius:4px}"],changeDetection:0}),a})();const Qi=["row"],Ri=["scrollPanel"];function Bi(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"span")(1,"button",14),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.onBack())}),e._UZ(2,"span",15),e.qZA()()}}function Hi(i,a){if(1&i&&(e.TgZ(0,"span",16),e._UZ(1,"worklenz-template-name",17),e.qZA()),2&i){const s=e.oxw();e.xp6(1),e.Q6J("templateId",s.templateId)("templateName",s.templateName)}}function $i(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"worklenz-group-filter",18),e.NdJ("onGroupBy",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.onGroupByChange(n))})("onFilterSearch",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.handleFilterSearch(n))})("onPhaseSettingsClick",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.openAddColumnDrawer())})("onCreateStatusClick",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.openStatusDrawer())}),e.qZA()}if(2&i){const s=e.oxw();e.Q6J("templateId",s.templateId)}}function ji(i,a){if(1&i&&(e.TgZ(0,"div",19)(1,"div",20),e._UZ(2,"img",21),e.qZA(),e._UZ(3,"span",22),e.qZA()),2&i){const s=e.oxw();e.xp6(3),e.Q6J("ngSwitch",s.isGroupByPhase())}}function qi(i,a){1&i&&(e.TgZ(0,"div",39),e._uU(1," No tasks available "),e.qZA())}function Vi(i,a){1&i&&e._UZ(0,"worklenz-pt-task-list-row",44),2&i&&e.Q6J("task",a.$implicit)}function Ki(i,a){if(1&i){const s=e.EpF();e.ynx(0),e.TgZ(1,"div",45,46)(3,"div",47),e._uU(4," \xa0 "),e.qZA(),e.TgZ(5,"div",48)(6,"worklenz-add-task-input",49,50),e.NdJ("focusChange",function(n){e.CHM(s);const o=e.MAs(2),r=e.MAs(7),l=e.oxw(4);return e.KtG(l.quickTaskFocusChange(n,o,r))}),e.qZA()()(),e.BQk()}if(2&i){const s=e.oxw(2).$implicit,t=e.oxw().$implicit,n=e.oxw();e.xp6(6),e.Q6J("templateId",n.templateId)("label","Add sub-task")("parentTask",s.id)("groupId",t.id)("subTaskInput",!0)}}function Wi(i,a){if(1&i&&(e.ynx(0),e.TgZ(1,"div"),e.YNc(2,Vi,1,1,"worklenz-pt-task-list-row",43),e.YNc(3,Ki,8,5,"ng-container",42),e.qZA(),e.BQk()),2&i){const s=e.oxw().$implicit,t=e.oxw(2);e.xp6(2),e.Q6J("ngForOf",s.sub_tasks)("ngForTrackBy",t.trackById),e.xp6(1),e.Q6J("ngIf",t.templateId&&s.id&&s.show_sub_tasks)}}function Xi(i,a){if(1&i){const s=e.EpF();e.ynx(0),e.TgZ(1,"worklenz-pt-task-list-row",40,41),e.NdJ("onShowSubTasks",function(n){e.CHM(s);const o=e.MAs(2),r=e.oxw().$implicit,l=e.oxw();return e.KtG(l.displaySubTasks(n,o,r.id))}),e.qZA(),e.YNc(3,Wi,4,3,"ng-container",42),e.BQk()}if(2&i){const s=a.$implicit,t=e.oxw(2);e.xp6(1),e.ekj("selected",(null==t.selectedTask?null:t.selectedTask.id)===s.id),e.Q6J("task",s),e.xp6(2),e.Q6J("ngIf",s.sub_tasks&&s.show_sub_tasks)}}function es(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"worklenz-add-task-input",51),e.NdJ("focusChange",function(n){e.CHM(s),e.oxw();const o=e.MAs(14),r=e.oxw();return e.KtG(r.handleFocusChange(n,o))}),e.qZA()}if(2&i){const s=e.oxw().$implicit,t=e.oxw();e.Q6J("templateId",t.templateId)("groupId",s.id)}}function ts(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"div",23)(1,"div",24)(2,"worklenz-task-list-group-settings",25),e.NdJ("toggle",function(n){e.CHM(s);const o=e.MAs(4),r=e.oxw();return e.KtG(r.toggleGroup(n,o))})("onCreateOrUpdate",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.refreshWithoutLoading())}),e.qZA(),e.TgZ(3,"div",26,27),e._UZ(5,"span",28),e.TgZ(6,"div",29)(7,"div",30,31),e.NdJ("cdkDropListDropped",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.onDrop(n))})("scroll",function(){e.CHM(s);const n=e.MAs(8),o=e.oxw();return e.KtG(o.onScroll(n))}),e.TgZ(9,"div",32)(10,"worklenz-task-list-header",33),e.NdJ("selectChange",function(n){const r=e.CHM(s).$implicit,l=e.oxw();return e.KtG(l.selectTasksInGroup(n,r))})("phaseSettingsClick",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.openAddColumnDrawer())}),e.qZA(),e.YNc(11,qi,2,0,"div",34),e.YNc(12,Xi,4,4,"ng-container",35),e.qZA()(),e.TgZ(13,"div",36,37),e.YNc(15,es,1,2,"worklenz-add-task-input",38),e.qZA()()()()()}if(2&i){const s=a.$implicit,t=e.oxw();e.xp6(2),e.Q6J("group",s)("templateId",t.templateId)("categories",t.categories),e.xp6(1),e.Udp("max-height",s.tasks.length?void 0:0),e.xp6(2),e.Udp("background",s.color_code),e.xp6(2),e.Q6J("id",s.id)("cdkDropListConnectedTo",t.groupIds)("cdkDropListData",s),e.xp6(3),e.Q6J("groupId",s.id),e.xp6(1),e.Q6J("ngIf",!s.tasks.length),e.xp6(1),e.Q6J("ngForOf",s.tasks)("ngForTrackBy",t.trackById),e.xp6(3),e.Q6J("ngIf",t.templateId)}}function ns(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"worklenz-phase-settings-drawer",52),e.NdJ("showChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.displayPhaseModal=n)})("onCreateOrUpdate",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.refreshWithoutLoading())})("refresh",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.refreshWithoutLoading())}),e.qZA()}if(2&i){const s=e.oxw();e.Q6J("templateId",s.templateId)("show",s.displayPhaseModal)}}function is(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"worklenz-status-settings-drawer",53),e.NdJ("onCreateOrUpdate",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.refreshWithoutLoading())})("showChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.displayStatusModal=n)}),e.qZA()}if(2&i){const s=e.oxw();e.Q6J("statusId",null)("templateId",s.templateId)("show",s.displayStatusModal)}}const os=[{path:"",component:ve,children:[{path:"",redirectTo:"profile",pathMatch:"full"},{path:"profile",component:F},{path:"language-and-region",component:ft},{path:"labels",canActivate:[oe.T],component:yt},{path:"categories",canActivate:[oe.T],loadChildren:()=>c.e(931).then(c.bind(c,82931)).then(i=>i.CategoriesModule)},{path:"clients",canActivate:[oe.T],loadChildren:()=>c.e(554).then(c.bind(c,9554)).then(i=>i.ClientsModule)},{path:"job-titles",canActivate:[oe.T],loadChildren:()=>c.e(693).then(c.bind(c,93693)).then(i=>i.JobTitlesModule)},{path:"notifications",loadChildren:()=>c.e(940).then(c.bind(c,94940)).then(i=>i.NotificationSettingsModule)},{path:"teams",component:at},{path:"team-members",canActivate:[oe.T],component:nn},{path:"password",canActivate:[(i,a)=>(0,e.f3M)(ut).canActivate(i,a)],component:dt},{path:"task-templates",canActivate:[oe.T],component:It},{path:"project-templates",canActivate:[oe.T],component:cn}]},{path:"project-templates/edit/:id/:name",canActivate:[oe.T],component:(()=>{var i;class a{get loading(){return this.loadingGroups}get defaultStatus(){return this.service.statuses.length?this.service.statuses[0].id:null}get groups(){return this.service.groups}constructor(t,n,o,r,l,d,_,y,S,be,ne,ae,fo,zo,To,Co){this.route=t,this.router=n,this.cdr=o,this.service=r,this.ngZone=l,this.map=d,this.socket=_,this.renderer=y,this.utils=S,this.api=be,this.labelsApi=ne,this.statusesApi=ae,this.prioritiesApi=fo,this.phasesApi=zo,this.app=To,this.auth=Co,this.scrollBy=0,this.templateId=null,this.templateName="",this.searchValue=null,this.templatesFilterString=null,this.selected=!1,this.loadingGroups=!1,this.groupChanging=!1,this.displayPhaseModal=!1,this.displayStatusModal=!1,this.checked=!1,this.indeterminate=!1,this.loadingFiltering=!1,this.loadingStatuses=!1,this.loadingPriorities=!1,this.loadingCategories=!1,this.loadingPhases=!1,this.loadingLabels=!1,this.taskId=null,this.selectedTask=null,this.groupIds=[],this.categories=[],this.handleSortOrderResponse=qe=>{for(const Ve of qe){const Ne=Ve.id;if(Ne){const Ee=this.map.tasks.get(Ne);Ee&&(Ee.sort_order=Ve.sort_order,this.map.tasks.set(Ne,Ee))}}},this.templateId=this.route.snapshot.paramMap.get("id"),this.templateName=this.route.snapshot.paramMap.get("name"),this.app.setTitle("Edit Template"),this.service.settemplateId(this.templateId),this.service.onTaskAddOrDelete$.pipe((0,G.sL)()).subscribe(qe=>{this.cdr.markForCheck()}),this.service.onRefresh$.pipe((0,G.sL)()).subscribe(()=>{this.cdr.markForCheck()}),this.service.onRefreshSubtasksIncluded.pipe((0,G.sL)()).subscribe(()=>{this.cdr.markForCheck()})}ngOnInit(){this.service.isSubtasksIncluded=!1,this.init(!0)}init(t){Promise.all([this.getGroups(t),this.getLabels(),this.getStatuses(),this.getPriorities(),this.getCategories(),this.getPhases()]),this.socket.on(k.C.PT_TASK_SORT_ORDER_CHANGE.toString(),this.handleSortOrderResponse)}ngOnDestroy(){this.ngZone.runOutsideAngular(()=>{this.service.reset(),this.service.groups=[],this.map.reset()}),this.socket.removeListener(k.C.PT_TASK_SORT_ORDER_CHANGE.toString(),this.handleSortOrderResponse)}isGroupByPhase(){return this.service.getCurrentGroup().value===this.service.GROUP_BY_PHASE_VALUE}getConf(t){const n={id:this.templateId,search:this.searchValue,projects:this.templatesFilterString,group:this.service.getCurrentGroup().value,isSubtasksInclude:!1};return t&&(n.parent_task=t),n}displaySubTasks(t,n,o){var r=this;return(0,m.Z)(function*(){if(t.id||!t.sub_tasks_loading){if(!t.show_sub_tasks&&0===t.sub_tasks_count)return t.show_sub_tasks=!0,void(t.sub_tasks=[]);if(t.sub_tasks_loading=!0,t.show_sub_tasks=!t.show_sub_tasks,t.show_sub_tasks){t.sub_tasks=yield r.getSubTasks(t);for(const l of t.sub_tasks)r.map.add(o,l)}else{for(const l of t.sub_tasks||[])r.map.deselectTask(l);t.sub_tasks=[]}t.sub_tasks_loading=!1,n.detectChanges(),r.cdr.markForCheck()}})()}toggleGroup(t,n){this.ngZone.runOutsideAngular(()=>{const o=t.target;o&&(o.closest(".btn")?.classList.toggle("active"),this.renderer.setStyle(n,"max-height","0px"===n.style.maxHeight?n.scrollHeight+8+"px":"0px"))})}trackById(t,n){return n.id}onDrop(t){const n=t.previousIndex,o=t.currentIndex,r=t.previousContainer.data,l=t.container.data;0==r.tasks.length&&this.ngZone.runOutsideAngular(()=>{document.getElementById(`${t.previousContainer.id}`)?.closest("div")?.parentNode?.parentNode?.parentNode?.querySelector("button.collapse.active")?.classList.remove("active")});const d=t.item.data,_=l.tasks[o]?.sort_order;this.socket.emit(k.C.PT_TASK_SORT_ORDER_CHANGE.toString(),{template_id:this.service.gettemplateId(),from_index:r.tasks[n].sort_order,to_index:_||l.tasks[l.tasks.length-1]?.sort_order||-1,to_last_index:!_,from_group:r.id,to_group:l.id,group_by:this.service.getCurrentGroup().value,task:d,team_id:this.auth.getCurrentSession()?.team_id}),r.id===l.id?(0,Ce.bA)(t.container.data.tasks,n,o):((0,Ce.EA)(t.previousContainer.data.tasks,t.container.data.tasks,t.previousIndex,t.currentIndex),this.map.remove(d),this.map.add(l.id,d),this.service.emitGroupChange(l.id,d.id,l.color_code))}bulkUpdateSuccess(){var t=this;return(0,m.Z)(function*(){yield t.getGroups(!0)})()}selectTasksInGroup(t,n){for(const o of n.tasks)t?this.map.selectTask(o):this.map.deselectTask(o)}mapTasks(t){for(const n of t){this.map.registerGroup(n);for(const o of n.tasks)o.start_date&&(o.start_date=new Date(o.start_date)),o.end_date&&(o.end_date=new Date(o.end_date))}}toggleFocusCls(t,n){t?this.renderer.addClass(n,this.service.HIGHLIGHT_COL_CLS):this.renderer.removeClass(n,this.service.HIGHLIGHT_COL_CLS)}onShowChange(t){t||(this.selectedTask=null)}handleNewTaskReceive(t){if(t.isSubTask){const n=this.rows.find(o=>o.id===t.taskId);n&&n.detectChanges()}this.cdr.markForCheck()}handleFocusChange(t,n){this.ngZone.runOutsideAngular(()=>{this.toggleFocusCls(t,n)})}quickTaskFocusChange(t,n,o){this.ngZone.runOutsideAngular(()=>{this.toggleFocusCls(t,n)})}onGroupByChange(t){var n=this;return(0,m.Z)(function*(){n.service.setCurrentGroup(t),n.groupChanging=!0,yield n.getGroups(!0),setTimeout(()=>{n.groupChanging=!1,n.cdr.markForCheck()},100)})()}handleFilterSearch(t){var n=this;return(0,m.Z)(function*(){n.loadingFiltering=!0,n.searchValue=t,yield n.getGroups(!0),n.loadingFiltering=!1,n.cdr.markForCheck()})()}getGroups(t){var n=this;return(0,m.Z)(function*(){if(n.templateId){try{n.map.deselectAll(),n.loadingGroups=t;const o=n.getConf();o.isSubtasksInclude=n.service.isSubtasksIncluded;const r=yield n.api.getTaskList(o);if(r.done){const l=(0,b.I8)(r.body);n.groupIds=l.map(d=>d.id),n.mapTasks(l),n.service.groups=l}n.loadingGroups=!1}catch{n.loadingGroups=!1}n.cdr.markForCheck()}})()}getSubTasks(t){var n=this;return(0,m.Z)(function*(){let o=[];if(t?.id)try{const r=n.getConf(t.id),l=yield n.api.getTaskList(r);l.done&&(o=l.body)}catch{}return o})()}getStatuses(){var t=this;return(0,m.Z)(function*(){if(t.templateId)try{t.loadingStatuses=!0;const n=yield t.statusesApi.get(t.templateId);n.done&&(t.service.statuses=n.body),t.loadingStatuses=!1}catch{t.loadingStatuses=!1}})()}getPriorities(){var t=this;return(0,m.Z)(function*(){try{t.loadingPriorities=!0;const n=yield t.prioritiesApi.get();n.done&&(t.service.priorities=n.body),t.loadingPriorities=!1}catch{t.loadingPriorities=!1}})()}getCategories(){var t=this;return(0,m.Z)(function*(){try{t.loadingCategories=!0;const n=yield t.statusesApi.getCategories();n.done&&(t.categories=n.body),t.loadingCategories=!1}catch{t.loadingCategories=!1}})()}getPhases(){var t=this;return(0,m.Z)(function*(){if(t.templateId)try{t.loadingPhases=!0;const n=yield t.phasesApi.get(t.templateId);n.done&&(t.service.phases=n.body),t.loadingPhases=!1}catch{t.loadingPhases=!1}})()}getLabels(){var t=this;return(0,m.Z)(function*(){try{t.loadingLabels=!0;const n=yield t.labelsApi.get(t.templateId);n.done&&(t.service.labels=n.body),t.loadingLabels=!1}catch{t.loadingLabels=!1}})()}onScroll(t){this.ngZone.runOutsideAngular(()=>{const n="scrolling-panel";this.scrollBy=t.scrollLeft,this.scrollBy>0?t.classList.add(n):t.classList.remove(n)})}debounce(t,n){let o;return(...r)=>{clearTimeout(o),o=setTimeout(()=>t.apply(this,r),n)}}openAddColumnDrawer(){this.displayPhaseModal=!0}openStatusDrawer(){this.displayStatusModal=!0}onBack(){this.router.navigate(["/worklenz/settings/project-templates"])}refreshWithoutLoading(){this.init(!1)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(N.gz),e.Y36(N.F0),e.Y36(e.sBO),e.Y36(B),e.Y36(e.R0b),e.Y36(ue),e.Y36(q.s),e.Y36(e.Qsj),e.Y36(de.F),e.Y36(Ye),e.Y36(dn),e.Y36(Ae),e.Y36(un),e.Y36(Ie),e.Y36(U.z),e.Y36(w.e))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-project-template-edit-view"]],viewQuery:function(t,n){if(1&t&&(e.Gf(Qi,5),e.Gf(Ri,5)),2&t){let o;e.iGM(o=e.CRH())&&(n.rows=o),e.iGM(o=e.CRH())&&(n.scrollPanels=o)}},hostBindings:function(t,n){1&t&&e.NdJ("scroll",function(r){return n.onScroll(r.target)})},decls:15,vars:10,consts:[[1,"container"],[1,"nz-header","mt-3","mb-4"],[1,"align-items-center"],[4,"nzSpaceItem"],["class","name-input position-relative",4,"nzSpaceItem"],[1,"top-section","mb-3","mt-3"],[3,"templateId","onGroupBy","onFilterSearch","onPhaseSettingsClick","onCreateStatusClick",4,"ngIf"],[3,"nzActive","nzLoading"],["class","pt-5 pb-5",4,"ngIf"],["cdkDropListGroup","",1,"tasks-wrapper"],["class","mb-3",4,"rxFor","rxForOf","rxForTrackBy"],[3,"templateId","show","showChange","onCreateOrUpdate","refresh",4,"ngIf"],["action","Create",3,"statusId","templateId","show","onCreateOrUpdate","showChange",4,"ngIf"],[3,"templateId","groups"],["nz-button","","nzType","text",1,"ant-page-header-back-button","d-flex","align-items-center",2,"font-size","16px",3,"click"],["nz-icon","","nzType","arrow-left","nzTheme","outline"],[1,"name-input","position-relative"],[3,"templateId","templateName"],[3,"templateId","onGroupBy","onFilterSearch","onPhaseSettingsClick","onCreateStatusClick"],[1,"pt-5","pb-5"],[1,"no-data-img-holder","mx-auto","mb-4"],["src","/assets/images/empty-box.webp","alt","",1,"img-fluid"],["nz-typography","",1,"mx-auto","d-block","text-center","no-data-text",2,"width","max-content",3,"ngSwitch"],[1,"mb-3"],[1,"container","px-0"],[3,"group","templateId","categories","toggle","onCreateOrUpdate"],[1,"panel","position-relative"],["panel",""],[1,"panel-left-border"],[1,"tasks-table","position-relative"],["cdkDropList","",1,"container","px-0","table-container","table-1",3,"id","cdkDropListConnectedTo","cdkDropListData","cdkDropListDropped","scroll"],["scrollPanel",""],[1,"border-right"],[3,"groupId","selectChange","phaseSettingsClick"],["class","tasks-empty-placeholder d-flex align-items-center mb-0 ps-3","nz-typography","","nzType","secondary",4,"ngIf"],[4,"ngFor","ngForOf","ngForTrackBy"],[1,"new-task-input"],["td1",""],[3,"templateId","groupId","focusChange",4,"ngIf"],["nz-typography","","nzType","secondary",1,"tasks-empty-placeholder","d-flex","align-items-center","mb-0","ps-3"],[3,"task","onShowSubTasks"],["row",""],[4,"ngIf"],[3,"task",4,"ngFor","ngForOf","ngForTrackBy"],[3,"task"],[1,"d-flex","inner-subtask-create","cursor-pointer","sub-task-background-color"],["td",""],[1,"new-subtask-divider"],[1,"w-100"],[3,"templateId","label","parentTask","groupId","subTaskInput","focusChange"],["subTaskInput",""],[3,"templateId","groupId","focusChange"],[3,"templateId","show","showChange","onCreateOrUpdate","refresh"],["action","Create",3,"statusId","templateId","show","onCreateOrUpdate","showChange"]],template:function(t,n){1&t&&(e.TgZ(0,"div",0)(1,"div",1)(2,"nz-space",2),e.YNc(3,Bi,3,0,"span",3),e.YNc(4,Hi,2,2,"span",4),e.qZA()(),e.TgZ(5,"nz-content")(6,"div",5),e.YNc(7,$i,1,1,"worklenz-group-filter",6),e.qZA(),e.TgZ(8,"nz-skeleton",7),e.YNc(9,ji,4,1,"div",8),e.TgZ(10,"div",9),e.YNc(11,ts,16,15,"div",10),e.qZA()()()(),e.YNc(12,ns,1,2,"worklenz-phase-settings-drawer",11),e.YNc(13,is,1,3,"worklenz-status-settings-drawer",12),e._UZ(14,"worklenz-context-menu",13)),2&t&&(e.xp6(7),e.Q6J("ngIf",n.templateId),e.xp6(1),e.Q6J("nzActive",!0)("nzLoading",n.loadingGroups),e.xp6(1),e.Q6J("ngIf",!n.groups.length),e.xp6(2),e.Q6J("rxForOf",n.groups)("rxForTrackBy",n.trackById),e.xp6(1),e.Q6J("ngIf",n.templateId),e.xp6(1),e.Q6J("ngIf",n.templateId),e.xp6(1),e.Q6J("templateId",n.templateId)("groups",n.groups))},dependencies:[h.sg,h.O5,h.RF,D.OK,M.Ls,O.ix,v.w,Q.ZU,H.NU,H.$1,$.ng,Ce.Wj,Qe,gn._,yn,Pn,hi,xi,Mi,Li,Ei,Yi],styles:['.task-status-color[_ngcontent-%COMP%]{border-width:1.4px;border-style:solid;border-radius:4px;width:1px}.task-name-color[_ngcontent-%COMP%]{height:20px;margin:auto;border-color:#a9a9a9;background-color:#a9a9a9;position:absolute;top:0;bottom:0;left:0;border-width:1.2px}.editable-cell[_ngcontent-%COMP%]{white-space:nowrap;max-width:255px;overflow:hidden;text-overflow:ellipsis}nz-date-picker[_ngcontent-%COMP%]{background:transparent}.expanded[_ngcontent-%COMP%]{transform:rotate(-90deg)}.dropdown-highlight[_ngcontent-%COMP%]{padding:1px}.highlight-col[_ngcontent-%COMP%]{border:1px solid #1890ff!important}.dropdown-highlight[_ngcontent-%COMP%]:hover{background-color:#d0eefa54;border:#5587f5 1px solid;border-radius:3px}.pointer-text[_ngcontent-%COMP%]{cursor:text}.plus-icon[_ngcontent-%COMP%]{display:none;position:absolute;right:0;z-index:9;background-color:#e2e7ea}tr[_ngcontent-%COMP%]:hover .plus-icon[_ngcontent-%COMP%]{display:block}.selected[_ngcontent-%COMP%], .selected[_ngcontent-%COMP%] .task-name[_ngcontent-%COMP%], .selected[_ngcontent-%COMP%] .task-drag-handler[_ngcontent-%COMP%]{background:rgba(24,144,255,.0784313725)!important}.sub-task-background-color[_ngcontent-%COMP%]{background-color:#f5f5f58a}.drop-down-btn[_ngcontent-%COMP%] .anticon-caret-down[_ngcontent-%COMP%]{color:#000000a6}div[_ngcontent-%COMP%]{box-sizing:border-box}.flex-table[_ngcontent-%COMP%]{display:flex;width:max-content}.table-container[_ngcontent-%COMP%]{overflow:auto;display:flex}.tasks-table[_ngcontent-%COMP%]{width:max-content;margin-left:3px;border-right:1px solid #f0f0f0}.column-trigger[_ngcontent-%COMP%]{background-color:#f4f5f7;height:40px;width:40px;justify-content:center;display:flex;align-items:center}.header[_ngcontent-%COMP%]{margin-bottom:0;position:sticky;top:0;background-color:#fff;z-index:2}.header[_ngcontent-%COMP%] .flex-row[_ngcontent-%COMP%]{padding:4px 11px;background-color:#fafafa;border-top:1px solid #f0f0f0;border-bottom:1px solid #f0f0f0;border-right:1px solid #f0f0f0;display:flex;align-items:center;flex-direction:row}.br-right[_ngcontent-%COMP%]{border-right:1px solid #f0f0f0}.task-check[_ngcontent-%COMP%]{text-align:center;padding:8px 6px 8px 0!important;position:sticky;left:24px;z-index:1}.task-key[_ngcontent-%COMP%]{width:85px}.task-arrow[_ngcontent-%COMP%]{width:24px;padding:0!important;display:flex;align-items:center;border:none!important;position:sticky;left:47px;z-index:1}.task-name[_ngcontent-%COMP%]{width:450px;min-width:450px;position:sticky;left:71px;z-index:1;background-color:#fff}.task-name[_ngcontent-%COMP%] nz-filter-trigger[_ngcontent-%COMP%]{margin-left:auto}.task-description[_ngcontent-%COMP%]{width:225px}.task-progress[_ngcontent-%COMP%]{width:80px}.task-members[_ngcontent-%COMP%]{width:160px}.task-labels[_ngcontent-%COMP%]{width:210px}.task-status[_ngcontent-%COMP%], .task-priority[_ngcontent-%COMP%], .task-time-tracking[_ngcontent-%COMP%], .task-estimation[_ngcontent-%COMP%]{width:120px}.task-start-date[_ngcontent-%COMP%], .task-due-date[_ngcontent-%COMP%], .task-completed-date[_ngcontent-%COMP%], .task-created-date[_ngcontent-%COMP%], .task-update-date[_ngcontent-%COMP%]{width:150px}.task-drag-handler[_ngcontent-%COMP%]{padding:0 0 0 4px!important;width:24px;border-bottom:1px solid #f0f0f0;border-right:none!important;position:sticky;left:0}.inner-subtask-create[_ngcontent-%COMP%]{height:42px;display:flex;align-items:center;max-width:1316px;overflow:hidden;background-color:#fafafa;position:sticky;left:0;border-bottom:1px solid #eaeaea;border-top:1px solid #eaeaea}@media (max-width: 1400px){.inner-subtask-create[_ngcontent-%COMP%]{max-width:1136px}}@media (max-width: 1200px){.inner-subtask-create[_ngcontent-%COMP%]{max-width:956px}}.inner-subtask-create.highlight-col[_ngcontent-%COMP%]{background-color:#fff}.new-subtask-divider[_ngcontent-%COMP%]{width:50px}.new-subtask-divider.divider-large[_ngcontent-%COMP%]{width:135px}worklenz-quick-task[_ngcontent-%COMP%]{display:block}.overflow-x-auto[_ngcontent-%COMP%]{overflow-x:auto;overflow-y:hidden}.panel[_ngcontent-%COMP%]{padding:0;background-color:#fff;max-height:calc(100% + 8px);overflow:hidden;transition:max-height .1s ease-out;border-right:1px solid #f0f0f0}.panel-left-border[_ngcontent-%COMP%]{position:absolute;content:"";top:0;bottom:0;width:3px;z-index:3;border-bottom-left-radius:4px}.collapse.btn[_ngcontent-%COMP%] .collapse-icon[_ngcontent-%COMP%]{transition:transform .1s;transform:rotate(0)}.collapse.btn.active[_ngcontent-%COMP%] .collapse-icon[_ngcontent-%COMP%]{transition:transform .1s;transform:rotate(90deg)}.drop-down-btn[_ngcontent-%COMP%]{padding:4px 11px}.drop-down-btn[_ngcontent-%COMP%] .anticon-caret-down[_ngcontent-%COMP%]{color:#d9d9d9}.status-color[_ngcontent-%COMP%]{width:11px;height:11px;border-radius:2px;margin-right:7px}.drop-down-btn[_ngcontent-%COMP%] nz-badge[_ngcontent-%COMP%]{margin-top:-2px}.drop-down-btn.active[_ngcontent-%COMP%]{color:#1890ff;border-color:#1890ff;background-color:#e6f7ff}.drop-down-btn.active[_ngcontent-%COMP%] .drop-down-btn[_ngcontent-%COMP%] .anticon-caret-down[_ngcontent-%COMP%]{color:#1890ff}.ant-badge-count-sm[_ngcontent-%COMP%]{font-size:11px}.tab-name-edit[_ngcontent-%COMP%]{overflow:hidden}.tab-name-edit[_ngcontent-%COMP%] span[_ngcontent-%COMP%]{margin-right:0;font-size:14px}.tab-name-edit[_ngcontent-%COMP%] svg[_ngcontent-%COMP%]{margin-right:0}.new-task-input[_ngcontent-%COMP%]{padding-left:4px;padding-top:4px;padding-bottom:4px;border-bottom:1px solid #f0f0f0;border-top:1px solid #f0f0f0;max-width:1316px;overflow-x:auto}@media (max-width: 1400px){.new-task-input[_ngcontent-%COMP%]{max-width:1136px}}@media (max-width: 1200px){.new-task-input[_ngcontent-%COMP%]{max-width:956px}}.tasks-empty-placeholder[_ngcontent-%COMP%]{width:100%;height:42px;background:#fafafa}.no-data-img-holder[_ngcontent-%COMP%]{width:100px;margin-top:42px}.add-field-button[_ngcontent-%COMP%]{position:absolute;top:46px;right:0}.name-input[_ngcontent-%COMP%]{max-width:1270px;width:1270px;display:block}@media (max-width: 1400px){.name-input[_ngcontent-%COMP%]{max-width:1090px;width:1090px}}@media (max-width: 1200px){.name-input[_ngcontent-%COMP%]{max-width:910px;width:910px}}'],changeDetection:0}),a})()}];let as=(()=>{var i;class a{}return(i=a).\u0275fac=function(t){return new(t||i)},i.\u0275mod=e.oAB({type:i}),i.\u0275inj=e.cJS({imports:[N.Bz.forChild(os),N.Bz]}),a})();var rs=c(3626),ls=c(79382),cs=c(82669),ps=c(62831),ds=c(48128),us=c(64345),_s=c(49388);let uo=(()=>{var i;class a{}return(i=a).\u0275fac=function(t){return new(t||i)},i.\u0275mod=e.oAB({type:i}),i.\u0275inj=e.cJS({imports:[_s.vT,h.ez,p.u5,ps.ud,J.cg,ds.W,us.YI,M.PV,O.sL]}),a})();var _o=c(48522),mo=c(29666),ho=c(52682);let go=(()=>{var i;class a{}return(i=a).\u0275fac=function(t){return new(t||i)},i.\u0275mod=e.oAB({type:i}),i.\u0275inj=e.cJS({providers:[Oe.g],imports:[h.ez,as,D.wm,V.vh,Y.KJ,rs.lt,M.PV,ls.we,p.UX,x.U5,P.o7,O.sL,cs.j,z.HQ,fe.Qp,Q.ZJ,T.ip,H.zf,J.cg,$.H0,K.LV,p.u5,uo,Me.l,ie.mS,E.b1,ze.X,Je.x,Te._p,Ge.S,De.Rt,_o.a,mo.V,we.Wr,Qe,ho.Hb,He.YS,$e.o,Be.PG,je.Zf,ke.BL,Fe.S]}),a})()},80697:(ye,se,c)=>{c.d(se,{I:()=>U});var h=c(26857),N=c(10708),e=c(65879),w=c(69862);let U=(()=>{var D;class Y extends h.P{constructor(T){super(),this.http=T,this.root=`${this.API_BASE_URL}/project-templates`}createCustomTemplate(T){return this._post(this.http,`${this.root}/custom-template`,T)}createFromTemplate(T){return this._post(this.http,`${this.root}/import-template`,T)}createFromCustomTemplate(T){return this._post(this.http,`${this.root}/import-custom-template`,T)}createTemplates(){return this._get(this.http,`${this.root}/create`)}getWorklenzTemplates(){return this._get(this.http,`${this.root}/worklenz-templates`)}getWorklenzTemplateById(T){return this._get(this.http,`${this.root}/worklenz-templates/${T}`)}getWorklenzCustomTemplates(){return this._get(this.http,`${this.root}/custom-templates`)}delete(T){return(0,N.n)(this.http.delete(`${this.root}/${T}`))}setupAccount(T){return this._post(this.http,`${this.root}/setup`,T)}}return(D=Y).\u0275fac=function(T){return new(T||D)(e.LFG(w.eN))},D.\u0275prov=e.Yz7({token:D,factory:D.\u0275fac,providedIn:"root"}),Y})()},26254:(ye,se,c)=>{c.d(se,{$:()=>L,$O:()=>ge,Jp:()=>O,KJ:()=>Pe,u9:()=>M,yG:()=>Z});var h=c(49388),N=c(96814),e=c(65879),w=c(8324),U=c(62595),D=c(97582),Y=c(78645),v=c(59773),T=c(37398),re=c(40874),X=c(1608),ee=c(28802);function ve(u,g){if(1&u&&(e.ynx(0),e._UZ(1,"span",9),e.BQk()),2&u){const C=g.$implicit,f=e.oxw(2);e.xp6(1),e.Q6J("nzType",C||f.getBackIcon())}}function m(u,g){if(1&u){const C=e.EpF();e.TgZ(0,"div",6),e.NdJ("click",function(){e.CHM(C);const F=e.oxw();return e.KtG(F.onBack())}),e.TgZ(1,"div",7),e.YNc(2,ve,2,1,"ng-container",8),e.qZA()()}if(2&u){const C=e.oxw();e.xp6(2),e.Q6J("nzStringTemplateOutlet",C.nzBackIcon)}}function p(u,g){if(1&u&&(e.ynx(0),e._uU(1),e.BQk()),2&u){const C=e.oxw(2);e.xp6(1),e.Oqu(C.nzTitle)}}function me(u,g){if(1&u&&(e.TgZ(0,"span",10),e.YNc(1,p,2,1,"ng-container",8),e.qZA()),2&u){const C=e.oxw();e.xp6(1),e.Q6J("nzStringTemplateOutlet",C.nzTitle)}}function b(u,g){1&u&&e.Hsn(0,6,["*ngIf","!nzTitle"])}function A(u,g){if(1&u&&(e.ynx(0),e._uU(1),e.BQk()),2&u){const C=e.oxw(2);e.xp6(1),e.Oqu(C.nzSubtitle)}}function I(u,g){if(1&u&&(e.TgZ(0,"span",11),e.YNc(1,A,2,1,"ng-container",8),e.qZA()),2&u){const C=e.oxw();e.xp6(1),e.Q6J("nzStringTemplateOutlet",C.nzSubtitle)}}function he(u,g){1&u&&e.Hsn(0,7,["*ngIf","!nzSubtitle"])}const Se=[[["nz-breadcrumb","nz-page-header-breadcrumb",""]],[["nz-avatar","nz-page-header-avatar",""]],[["nz-page-header-tags"],["","nz-page-header-tags",""]],[["nz-page-header-extra"],["","nz-page-header-extra",""]],[["nz-page-header-content"],["","nz-page-header-content",""]],[["nz-page-header-footer"],["","nz-page-header-footer",""]],[["nz-page-header-title"],["","nz-page-header-title",""]],[["nz-page-header-subtitle"],["","nz-page-header-subtitle",""]]],V=["nz-breadcrumb[nz-page-header-breadcrumb]","nz-avatar[nz-page-header-avatar]","nz-page-header-tags, [nz-page-header-tags]","nz-page-header-extra, [nz-page-header-extra]","nz-page-header-content, [nz-page-header-content]","nz-page-header-footer, [nz-page-header-footer]","nz-page-header-title, [nz-page-header-title]","nz-page-header-subtitle, [nz-page-header-subtitle]"];let M=(()=>{var u;class g{}return(u=g).\u0275fac=function(f){return new(f||u)},u.\u0275dir=e.lG2({type:u,selectors:[["nz-page-header-title"],["","nz-page-header-title",""]],hostAttrs:[1,"ant-page-header-heading-title"],exportAs:["nzPageHeaderTitle"]}),g})(),Z=(()=>{var u;class g{}return(u=g).\u0275fac=function(f){return new(f||u)},u.\u0275dir=e.lG2({type:u,selectors:[["nz-page-header-subtitle"],["","nz-page-header-subtitle",""]],hostAttrs:[1,"ant-page-header-heading-sub-title"],exportAs:["nzPageHeaderSubtitle"]}),g})(),O=(()=>{var u;class g{}return(u=g).\u0275fac=function(f){return new(f||u)},u.\u0275dir=e.lG2({type:u,selectors:[["nz-page-header-extra"],["","nz-page-header-extra",""]],hostAttrs:[1,"ant-page-header-heading-extra"],exportAs:["nzPageHeaderExtra"]}),g})(),L=(()=>{var u;class g{}return(u=g).\u0275fac=function(f){return new(f||u)},u.\u0275dir=e.lG2({type:u,selectors:[["nz-page-header-footer"],["","nz-page-header-footer",""]],hostAttrs:[1,"ant-page-header-footer"],exportAs:["nzPageHeaderFooter"]}),g})(),Q=(()=>{var u;class g{}return(u=g).\u0275fac=function(f){return new(f||u)},u.\u0275dir=e.lG2({type:u,selectors:[["nz-breadcrumb","nz-page-header-breadcrumb",""]],exportAs:["nzPageHeaderBreadcrumb"]}),g})(),ge=(()=>{var u;class g{constructor(f,F,pe,te,z,fe){this.location=f,this.nzConfigService=F,this.elementRef=pe,this.nzResizeObserver=te,this.cdr=z,this.directionality=fe,this._nzModuleName="pageHeader",this.nzBackIcon=null,this.nzGhost=!0,this.nzBack=new e.vpe,this.compact=!1,this.destroy$=new Y.x,this.dir="ltr"}ngOnInit(){this.directionality.change?.pipe((0,v.R)(this.destroy$)).subscribe(f=>{this.dir=f,this.cdr.detectChanges()}),this.dir=this.directionality.value}ngAfterViewInit(){this.nzResizeObserver.observe(this.elementRef).pipe((0,T.U)(([f])=>f.contentRect.width),(0,v.R)(this.destroy$)).subscribe(f=>{this.compact=f<768,this.cdr.markForCheck()})}onBack(){if(this.nzBack.observers.length)this.nzBack.emit();else{if(!this.location)throw new Error(`${X.Bq} you should import 'RouterModule' or register 'Location' if you want to use 'nzBack' default event!`);this.location.back()}}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}getBackIcon(){return"rtl"===this.dir?"arrow-right":"arrow-left"}}return(u=g).\u0275fac=function(f){return new(f||u)(e.Y36(N.Ye,8),e.Y36(re.jY),e.Y36(e.SBq),e.Y36(ee.D3),e.Y36(e.sBO),e.Y36(h.Is,8))},u.\u0275cmp=e.Xpm({type:u,selectors:[["nz-page-header"]],contentQueries:function(f,F,pe){if(1&f&&(e.Suo(pe,L,5),e.Suo(pe,Q,5)),2&f){let te;e.iGM(te=e.CRH())&&(F.nzPageHeaderFooter=te.first),e.iGM(te=e.CRH())&&(F.nzPageHeaderBreadcrumb=te.first)}},hostAttrs:[1,"ant-page-header"],hostVars:10,hostBindings:function(f,F){2&f&&e.ekj("has-footer",F.nzPageHeaderFooter)("ant-page-header-ghost",F.nzGhost)("has-breadcrumb",F.nzPageHeaderBreadcrumb)("ant-page-header-compact",F.compact)("ant-page-header-rtl","rtl"===F.dir)},inputs:{nzBackIcon:"nzBackIcon",nzTitle:"nzTitle",nzSubtitle:"nzSubtitle",nzGhost:"nzGhost"},outputs:{nzBack:"nzBack"},exportAs:["nzPageHeader"],ngContentSelectors:V,decls:13,vars:5,consts:[[1,"ant-page-header-heading"],[1,"ant-page-header-heading-left"],["class","ant-page-header-back",3,"click",4,"ngIf"],["class","ant-page-header-heading-title",4,"ngIf"],[4,"ngIf"],["class","ant-page-header-heading-sub-title",4,"ngIf"],[1,"ant-page-header-back",3,"click"],["role","button","tabindex","0",1,"ant-page-header-back-button"],[4,"nzStringTemplateOutlet"],["nz-icon","","nzTheme","outline",3,"nzType"],[1,"ant-page-header-heading-title"],[1,"ant-page-header-heading-sub-title"]],template:function(f,F){1&f&&(e.F$t(Se),e.Hsn(0),e.TgZ(1,"div",0)(2,"div",1),e.YNc(3,m,3,1,"div",2),e.Hsn(4,1),e.YNc(5,me,2,1,"span",3),e.YNc(6,b,1,0,"ng-content",4),e.YNc(7,I,2,1,"span",5),e.YNc(8,he,1,0,"ng-content",4),e.Hsn(9,2),e.qZA(),e.Hsn(10,3),e.qZA(),e.Hsn(11,4),e.Hsn(12,5)),2&f&&(e.xp6(3),e.Q6J("ngIf",null!==F.nzBackIcon),e.xp6(2),e.Q6J("ngIf",F.nzTitle),e.xp6(1),e.Q6J("ngIf",!F.nzTitle),e.xp6(1),e.Q6J("ngIf",F.nzSubtitle),e.xp6(1),e.Q6J("ngIf",!F.nzSubtitle))},dependencies:[N.O5,w.f,U.Ls],encapsulation:2,changeDetection:0}),(0,D.gn)([(0,re.oS)()],g.prototype,"nzGhost",void 0),g})(),Pe=(()=>{var u;class g{}return(u=g).\u0275fac=function(f){return new(f||u)},u.\u0275mod=e.oAB({type:u}),u.\u0275inj=e.cJS({imports:[h.vT,N.ez,w.T,U.PV]}),g})()}}]); \ No newline at end of file diff --git a/worklenz-backend/src/public/150.e8fd9de562cbd890.js b/worklenz-backend/src/public/150.e8fd9de562cbd890.js new file mode 100644 index 00000000..f184f885 --- /dev/null +++ b/worklenz-backend/src/public/150.e8fd9de562cbd890.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkworklenz=self.webpackChunkworklenz||[]).push([[150],{39787:(ye,se,c)=>{c.d(se,{l:()=>Y});var h=c(96109),N=c(42840),e=c(62595),w=c(65879),U=c(72042),D=c(70855);let Y=(()=>{var v;class T{constructor(X){this.menu=X}}return(v=T).\u0275fac=function(X){return new(X||v)(w.Y36(U.h))},v.\u0275cmp=w.Xpm({type:v,selectors:[["worklenz-toggle-menu-button"]],inputs:{key:"key"},standalone:!0,features:[w.jDz],decls:2,vars:2,consts:[["nz-button","","nz-tooltip","","nzShape","circle","nzType","link",3,"nzTooltipTitle","click"],["nz-icon","","nzType","pushpin",1,"pin-button",3,"nzTheme"]],template:function(X,ee){1&X&&(w.TgZ(0,"button",0),w.NdJ("click",function(){return ee.menu.toggle(ee.key)}),w._UZ(1,"span",1),w.qZA()),2&X&&(w.Q6J("nzTooltipTitle",ee.menu.isPinned(ee.key)?"Click to unpin this from the main menu":"Click to pin this into the main menu"),w.xp6(1),w.Q6J("nzTheme",ee.menu.isPinned(ee.key)?"fill":"outline"))},dependencies:[h.cg,h.SY,N.sL,N.ix,D.w,e.PV,e.Ls]}),T})()},16150:(ye,se,c)=>{c.r(se),c.d(se,{SettingsModule:()=>fo});var h=c(96814),N=c(35420),e=c(65879),w=c(32333),U=c(90586),D=c(33410),Y=c(26254),v=c(70855),T=c(73460);const re=function(i){return[i]},X=function(){return["ant-menu-item-selected"]};function ee(i,a){if(1&i&&e._UZ(0,"li",7),2&i){const s=a.$implicit;e.Q6J("routerLink",e.VKq(4,re,s.url))("nzIcon",s.icon)("nzTitle",s.label)("routerLinkActive",e.DdM(6,X))}}let ve=(()=>{var i;class a{constructor(t,n){this.auth=t,this.app=n,this.navigation=[],this.app.setTitle("Settings")}get profile(){return this.auth.getCurrentSession()}ngOnInit(){this.buildNavigation()}isOwnerOrAdmin(){return this.profile?.owner||this.profile?.is_admin}buildNavigation(){this.navigation=[],this.navigation.push({label:"Profile",icon:"user",url:"profile"}),this.navigation.push({label:"Notifications",icon:"notification",url:"notifications"}),this.profile?.is_expired||(this.isOwnerOrAdmin()&&(this.navigation.push({label:"Clients",icon:"user-switch",url:"clients"}),this.navigation.push({label:"Job Titles",icon:"idcard",url:"job-titles"}),this.navigation.push({label:"Labels",icon:"tags",url:"labels"}),this.navigation.push({label:"Categories",icon:"group",url:"categories"}),this.navigation.push({label:"Project Templates",icon:"file-zip",url:"project-templates"}),this.navigation.push({label:"Task Templates",icon:"profile",url:"task-templates"}),this.navigation.push({label:"Team Members",icon:"team",url:"team-members"})),this.navigation.push({label:"Teams",icon:"bank",url:"teams"})),this.profile?.is_google||this.navigation.push({label:"Change Password",icon:"lock",url:"password"}),this.navigation.push({label:"Language & Region",icon:"global",url:"language-and-region"})}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(w.e),e.Y36(U.z))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-settings"]],decls:10,vars:4,consts:[[1,"container"],[1,"px-0",3,"nzGhost"],[1,"inner-layout"],[3,"nzWidth"],["nz-menu","",1,"border-0",3,"nzMode"],["class","rounded-4","nz-submenu","",3,"routerLink","nzIcon","nzTitle","routerLinkActive",4,"ngFor","ngForOf"],[1,"px-4","bg-white"],["nz-submenu","",1,"rounded-4",3,"routerLink","nzIcon","nzTitle","routerLinkActive"]],template:function(t,n){1&t&&(e.TgZ(0,"div",0)(1,"nz-page-header",1)(2,"nz-page-header-title"),e._uU(3,"Settings"),e.qZA()(),e.TgZ(4,"nz-layout",2)(5,"nz-sider",3)(6,"ul",4),e.YNc(7,ee,1,7,"li",5),e.qZA()(),e.TgZ(8,"nz-content",6),e._UZ(9,"router-outlet"),e.qZA()()()),2&t&&(e.xp6(1),e.Q6J("nzGhost",!1),e.xp6(4),e.Q6J("nzWidth","240px"),e.xp6(1),e.Q6J("nzMode","vertical"),e.xp6(1),e.Q6J("ngForOf",n.navigation))},dependencies:[h.sg,N.lC,N.rH,N.Od,D.hw,D.OK,D.t7,Y.$O,Y.u9,v.w,T.wO,T.rY],styles:["[nz-submenu][_ngcontent-%COMP%]{transition:none!important}"]}),a})();var m=c(15861),p=c(60095),me=c(59556),b=c(69649),A=c(86408),I=c(27782),he=c(16849),Se=c(44568),V=c(82962),P=c(62595),Z=c(10095),x=c(3599),M=c(20824),O=c(42840),L=c(41958),Q=c(8083),J=c(96109),$=c(24139),ge=c(34302);function Ue(i,a){1&i&&e._UZ(0,"span",22)}function Me(i,a){1&i&&e._UZ(0,"span",23)}function u(i,a){if(1&i&&(e.TgZ(0,"div",18),e.YNc(1,Ue,1,0,"span",19),e.YNc(2,Me,1,0,"span",20),e.TgZ(3,"div",21),e._uU(4),e.qZA()()),2&i){const s=e.oxw();e.xp6(1),e.Q6J("ngIf",!s.uploading),e.xp6(1),e.Q6J("ngIf",s.uploading),e.xp6(2),e.Oqu(s.uploading?"Uploading...":"Upload")}}function g(i,a){if(1&i&&e._UZ(0,"img",24),2&i){const s=e.oxw();e.Q6J("alt",null==s.profile?null:s.profile.name)("src",s.avatarUrl,e.LSH)}}function C(i,a){if(1&i&&(e.TgZ(0,"p",25)(1,"small",26),e.ALo(2,"date"),e._uU(3),e.ALo(4,"fromNow"),e.qZA()()),2&i){const s=e.oxw();e.xp6(1),e.s9C("nzTooltipTitle",e.xi3(2,2,null==s.profile?null:s.profile.joined_date,"medium")),e.xp6(2),e.hij(" Joined ",e.lcZ(4,5,null==s.profile?null:s.profile.joined_date)," ")}}function f(i,a){if(1&i&&(e.TgZ(0,"p",25)(1,"small",26),e.ALo(2,"date"),e._uU(3),e.ALo(4,"fromNow"),e.qZA()()),2&i){const s=e.oxw();e.xp6(1),e.s9C("nzTooltipTitle",e.xi3(2,2,null==s.profile?null:s.profile.last_updated,"medium")),e.xp6(2),e.hij(" Last updated ",e.lcZ(4,5,null==s.profile?null:s.profile.last_updated)," ")}}let F=(()=>{var i;class a{constructor(t,n,o,r,l,d){this.fb=t,this.api=n,this.app=o,this.auth=r,this.attachmentsApi=l,this.ref=d,this.model={},this.loading=!1,this.updating=!1,this.uploading=!1,this.avatarUrl=null,this.app.setTitle("Profile Settings"),A.s.track(I.S5),this.form=this.fb.group({name:[null,p.kI.required],email:[null,p.kI.required]}),this.avatarUrl=this.profile?.avatar_url||null,this.form.controls.email.disable()}get profile(){return this.auth.getCurrentSession()}ngOnInit(){var t=this;return(0,m.Z)(function*(){yield t.get()})()}isInvalidForm(){return this.form.invalid}get(){var t=this;return(0,m.Z)(function*(){t.loading=!0;try{const n=yield t.api.get();n.done&&(t.model=n.body,t.form.controls.name.setValue(t.model.name),t.form.controls.email.setValue(t.model.email),t.profile?.is_google&&t.form.controls.email.disable()),t.loading=!1}catch(n){t.loading=!1,(0,b.tu)(n)}})()}submit(){var t=this;return(0,m.Z)(function*(){if(t.form.invalid)t.app.displayErrorsOf(t.form);else{t.updating=!0;try{const n={name:t.form.controls.name.value};(yield t.api.update(n)).done&&(A.s.track(I.AE),yield t.get(),yield t.auth.authorize(),(0,me.Bk)()),t.updating=!1}catch(n){t.updating=!1,(0,b.tu)(n)}}})()}uploadFile(t){var n=this;return(0,m.Z)(function*(){if(n.uploading)return;try{const r=t.files||[];if(!r||!r.length)return;const l=r[0];n.uploading=!0;const d=yield(0,b.y3)(l),_=yield n.attachmentsApi.createAvatarAttachment({file:d,file_name:l.name,size:l.size});_.done&&(A.s.track(I.nX),yield n.auth.authorize(),(0,me.hQ)(),n.avatarUrl=_.body.url),n.uploading=!1}catch{n.uploading=!1}const o=new DataTransfer;t.files=o.files})()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(p.qu),e.Y36(he.G),e.Y36(U.z),e.Y36(w.e),e.Y36(Se.J),e.Y36(e.z2F))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-profile"]],decls:26,vars:16,consts:[[3,"nzActive","nzLoading"],[1,"ant-upload-list","ant-upload-list-picture-card","mb-3"],[1,"ant-upload","ant-upload-select","ant-upload-select-picture-card",2,"position","relative"],["tabindex","0","role","button","nz-tooltip","",1,"ant-upload",3,"nzTooltipTitle","click"],["type","file","accept","image/png, image/jpg, image/jpeg",2,"display","none",3,"change"],["fileInput",""],["class","avatar-upload-icon",4,"ngIf"],["style","width: 100%",3,"alt","src",4,"ngIf"],["nz-form","","nzLayout","vertical",3,"formGroup","ngSubmit"],["nzFor","name","nzRequired",""],["nzErrorTip","Name is required!",3,"nzSm","nzXs"],["id","name","nz-input","",3,"formControlName"],["nzFor","email","nzRequired",""],["nzErrorTip","The input is not valid E-mail!",3,"nzSm","nzXs"],["id","email","nz-input","","readonly","",3,"formControlName"],["nz-row","",1,"register-area","mb-3"],["nz-button","","nzType","primary",3,"disabled","nzLoading"],["class","mb-0","nz-typography","","nzType","secondary",4,"ngIf"],[1,"avatar-upload-icon"],["nz-icon","","nzType","plus","nzTheme","outline",4,"ngIf"],["nz-icon","","nzType","loading","nzTheme","outline",4,"ngIf"],[2,"margin-top","8px"],["nz-icon","","nzType","plus","nzTheme","outline"],["nz-icon","","nzType","loading","nzTheme","outline"],[2,"width","100%",3,"alt","src"],["nz-typography","","nzType","secondary",1,"mb-0"],["nz-tooltip","","nzTooltipPlacement","right",3,"nzTooltipTitle"]],template:function(t,n){if(1&t){const o=e.EpF();e.TgZ(0,"nz-card")(1,"nz-skeleton",0)(2,"div",1)(3,"div",2)(4,"div",3),e.NdJ("click",function(){e.CHM(o);const l=e.MAs(6);return e.KtG(l.click())}),e.TgZ(5,"input",4,5),e.NdJ("change",function(){e.CHM(o);const l=e.MAs(6);return e.KtG(n.uploadFile(l))}),e.qZA(),e.YNc(7,u,5,3,"div",6),e.YNc(8,g,1,2,"img",7),e.qZA()()(),e.TgZ(9,"form",8),e.NdJ("ngSubmit",function(){return n.submit()}),e.TgZ(10,"nz-form-item")(11,"nz-form-label",9),e._uU(12,"Name"),e.qZA(),e.TgZ(13,"nz-form-control",10),e._UZ(14,"input",11),e.qZA()(),e.TgZ(15,"nz-form-item")(16,"nz-form-label",12),e._uU(17,"E-mail"),e.qZA(),e.TgZ(18,"nz-form-control",13),e._UZ(19,"input",14),e.qZA()(),e.TgZ(20,"nz-form-item",15)(21,"nz-form-control")(22,"button",16),e._uU(23," Save Changes "),e.qZA()()(),e.YNc(24,C,5,7,"p",17),e.YNc(25,f,5,7,"p",17),e.qZA()()()}2&t&&(e.xp6(1),e.Q6J("nzActive",!0)("nzLoading",n.loading||n.updating),e.xp6(3),e.Q6J("nzTooltipTitle",n.avatarUrl?"Click to change the avatar":"Click to upload an avatar"),e.xp6(3),e.Q6J("ngIf",!n.avatarUrl||n.uploading),e.xp6(1),e.Q6J("ngIf",n.avatarUrl),e.xp6(1),e.Q6J("formGroup",n.form),e.xp6(4),e.Q6J("nzSm",14)("nzXs",24),e.xp6(1),e.Q6J("formControlName","name"),e.xp6(4),e.Q6J("nzSm",14)("nzXs",24),e.xp6(1),e.Q6J("formControlName","email"),e.xp6(3),e.Q6J("disabled",n.isInvalidForm()||n.loading)("nzLoading",n.updating),e.xp6(2),e.Q6J("ngIf",null==n.profile?null:n.profile.joined_date),e.xp6(1),e.Q6J("ngIf",null==n.profile?null:n.profile.last_updated))},dependencies:[h.O5,V.bd,P.Ls,p._Y,p.Fj,p.JJ,p.JL,p.sg,p.u,Z.t3,Z.SK,x.Lr,x.Nx,x.iK,x.Fd,M.Zp,O.ix,v.w,L.dQ,Q.ZU,J.SY,$.ng,h.uU,ge.d],styles:["[nz-form][_ngcontent-%COMP%]{max-width:600px}.avatar-upload-icon[_ngcontent-%COMP%]{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.6196078431)}"]}),a})();var pe=c(83916),te=c(72042),z=c(13740),fe=c(81221),H=c(92574),Pe=c(39787),ie=c(33640);function We(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",19),e.NdJ("click",function(){e.CHM(s);const n=e.oxw(2);return e.KtG(n.editTeam(n.currentTeam))}),e._UZ(1,"span",20),e.qZA()}}function Xe(i,a){if(1&i&&(e.TgZ(0,"tr",14)(1,"td"),e._UZ(2,"nz-badge",15),e.qZA(),e.TgZ(3,"td"),e._uU(4),e.ALo(5,"fromNow"),e.qZA(),e.TgZ(6,"td"),e._uU(7),e.qZA(),e.TgZ(8,"td",16)(9,"div",17)(10,"nz-space"),e.YNc(11,We,2,0,"button",18),e.qZA()()()()),2&i){const s=e.oxw();e.xp6(2),e.Q6J("nzText",s.currentTeam.name),e.xp6(2),e.Oqu(e.lcZ(5,4,s.currentTeam.created_at)),e.xp6(3),e.Oqu(s.currentTeam.owns_by),e.xp6(4),e.Q6J("ngIf",s.currentTeam.owner)}}function et(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",19),e.NdJ("click",function(){e.CHM(s);const n=e.oxw().$implicit,o=e.oxw();return e.KtG(o.editTeam(n))}),e._UZ(1,"span",20),e.qZA()}}function tt(i,a){if(1&i&&(e.TgZ(0,"tr",14)(1,"td"),e._uU(2),e.qZA(),e.TgZ(3,"td"),e._uU(4),e.ALo(5,"fromNow"),e.qZA(),e.TgZ(6,"td"),e._uU(7),e.qZA(),e.TgZ(8,"td",16)(9,"div",17)(10,"nz-space"),e.YNc(11,et,2,0,"button",18),e.qZA()()()()),2&i){const s=a.$implicit;e.xp6(2),e.Oqu(s.name),e.xp6(2),e.Oqu(e.lcZ(5,4,s.created_at)),e.xp6(3),e.Oqu(s.owns_by),e.xp6(4),e.Q6J("ngIf",s.owner)}}function nt(i,a){if(1&i&&e._UZ(0,"worklenz-toggle-menu-button",1),2&i){const s=e.oxw();e.Q6J("key",s.menu.TEAMS_MENU)}}function it(i,a){1&i&&(e.TgZ(0,"div",21)(1,"div",22),e._UZ(2,"img",23),e.qZA(),e.TgZ(3,"span",24),e._uU(4,"No teams found."),e.qZA()())}function st(i,a){}function ot(i,a){if(1&i&&(e.ynx(0),e.TgZ(1,"form",25)(2,"nz-form-item")(3,"nz-form-label",26),e._uU(4,"Name"),e.qZA(),e.TgZ(5,"nz-form-control",27),e._UZ(6,"input",28),e.qZA()()(),e.BQk()),2&i){const s=e.oxw();e.xp6(1),e.Q6J("formGroup",s.form)("nzLayout","vertical"),e.xp6(2),e.Q6J("nzSpan",null),e.xp6(2),e.Q6J("nzSpan",null),e.xp6(1),e.Q6J("formControlName","name")}}const at=function(){return{rows:5}};let rt=(()=>{var i;class a{constructor(t,n,o,r,l,d){this.teamsApi=t,this.settingsApi=n,this.fb=o,this.app=r,this.menu=l,this.auth=d,this.teams=[],this.currentTeam=null,this.loading=!1,this.updating=!1,this.showTeamEditModal=!1,this.editingTeamId=null,this.app.setTitle("Teams"),A.s.track(I.jD),this.form=this.fb.group({name:[null,p.kI.required]})}ngOnInit(){this.getTeams()}getTeams(){var t=this;return(0,m.Z)(function*(){try{t.loading=!0;const n=yield t.teamsApi.get();n.done&&(t.teams=n.body.filter(o=>o.id!==t.auth.getCurrentSession()?.team_id),t.currentTeam=n.body.filter(o=>o.id===t.auth.getCurrentSession()?.team_id)[0]),t.loading=!1}catch(n){t.loading=!1,(0,b.tu)(n)}})()}updateTeamName(t,n){var o=this;return(0,m.Z)(function*(){try{if(!t||!n)return;o.updating=!0,(yield o.settingsApi.updateTeamName(t,{name:n})).done?window.location.reload():o.updating=!1}catch(r){o.updating=!1,(0,b.tu)(r)}})()}closeModal(){this.showTeamEditModal=!1,this.editingTeamId=null,this.form.reset()}handleOk(){var t=this;return(0,m.Z)(function*(){t.form.valid&&t.editingTeamId&&(yield t.updateTeamName(t.editingTeamId,t.form.controls.name.value))})()}editTeam(t){!t||!t.id||(this.showTeamEditModal=!0,this.editingTeamId=t.id,this.form.controls.name.setValue(t.name))}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(pe.S),e.Y36(he.G),e.Y36(p.qu),e.Y36(U.z),e.Y36(te.h),e.Y36(w.e))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-teams"]],decls:28,vars:14,consts:[[1,"px-0",3,"nzGhost"],[3,"key"],[3,"nzActive","nzLoading","nzParagraph"],["nzPaginationType","small","nzSize","small",1,"custom-table",3,"nzData","nzLoading","nzNoResult"],["table",""],["scope","col"],["colspan","2","nzAlign","left","scope","col"],["class","actions-row",4,"ngIf"],["class","actions-row",4,"ngFor","ngForOf"],["actionsTemplate",""],["noDataTemplate",""],["noDataTemplate1",""],["nzOkText","Update Name","nzTitle","Edit Team Name",3,"nzVisible","nzOkLoading","nzOnCancel","nzOnOk","nzVisibleChange"],[4,"nzModalContent"],[1,"actions-row"],["nzColor","#52c41a",3,"nzText"],["nzAlign","center",1,"actions-col"],[1,"actions"],["nz-button","","nz-tooltip","","nzSize","small","nzTooltipPlacement","top","nzTooltipTitle","Edit","nzType","default",3,"click",4,"ngIf"],["nz-button","","nz-tooltip","","nzSize","small","nzTooltipPlacement","top","nzTooltipTitle","Edit","nzType","default",3,"click"],["nz-icon","","nzType","edit"],[1,"pt-4","pb-5"],[1,"no-data-img-holder","mx-auto","mb-3"],["src","/assets/images/empty-box.webp","alt","",1,"img-fluid"],["nz-typography","",1,"no-data-text"],["nz-form","",3,"formGroup","nzLayout"],["nzRequired","",3,"nzSpan"],["nzErrorTip","Please enter a name!",3,"nzSpan"],["nz-input","","placeholder","Name",3,"formControlName"]],template:function(t,n){if(1&t&&(e.TgZ(0,"nz-page-header",0)(1,"nz-page-header-title"),e._uU(2),e.qZA(),e.TgZ(3,"nz-page-header-extra"),e._UZ(4,"worklenz-toggle-menu-button",1),e.qZA()(),e.TgZ(5,"nz-card")(6,"nz-skeleton",2)(7,"nz-table",3,4)(9,"thead")(10,"tr")(11,"th",5),e._uU(12,"Name"),e.qZA(),e.TgZ(13,"th",5),e._uU(14,"Created"),e.qZA(),e.TgZ(15,"th",6),e._uU(16,"Owns By"),e.qZA()()(),e.TgZ(17,"tbody"),e.YNc(18,Xe,12,6,"tr",7),e.YNc(19,tt,12,6,"tr",8),e.qZA()()()(),e.YNc(20,nt,1,1,"ng-template",null,9,e.W1O),e.YNc(22,it,5,0,"ng-template",null,10,e.W1O),e.YNc(24,st,0,0,"ng-template",null,11,e.W1O),e.TgZ(26,"nz-modal",12),e.NdJ("nzOnCancel",function(){return n.closeModal()})("nzOnOk",function(){return n.handleOk()})("nzVisibleChange",function(r){return n.showTeamEditModal=r}),e.YNc(27,ot,7,5,"ng-container",13),e.qZA()),2&t){const o=e.MAs(8),r=e.MAs(23),l=e.MAs(25);e.Q6J("nzGhost",!1),e.xp6(2),e.hij("",n.teams.length?n.teams.length+1:0," Teams"),e.xp6(2),e.Q6J("key",n.menu.TEAMS_MENU),e.xp6(2),e.Q6J("nzActive",!0)("nzLoading",n.loading)("nzParagraph",e.DdM(13,at)),e.xp6(1),e.Q6J("nzData",n.teams)("nzLoading",n.loading)("nzNoResult",n.currentTeam?l:r),e.xp6(11),e.Q6J("ngIf",n.currentTeam),e.xp6(1),e.Q6J("ngForOf",o.data),e.xp6(7),e.Q6J("nzVisible",n.showTeamEditModal)("nzOkLoading",n.updating)}},dependencies:[h.sg,h.O5,V.bd,Y.$O,Y.u9,Y.Jp,P.Ls,p._Y,p.Fj,p.JJ,p.JL,p.sg,p.u,Z.t3,Z.SK,x.Lr,x.Nx,x.iK,x.Fd,M.Zp,O.ix,v.w,L.dQ,z.N8,z.Uo,z._C,z.Om,z.p0,z.$Z,z.UX,fe.du,fe.Hf,Q.ZU,H.NU,J.SY,$.ng,Pe.l,ie.x7,ge.d],styles:[".no-data-img-holder[_ngcontent-%COMP%]{width:100px;margin-top:42px}"]}),a})();var j=c(21406),le=c(26857),ce=c(69862);let lt=(()=>{var i;class a extends le.P{constructor(t){super(),this.http=t}changePassword(t){return this._post(this.http,`${this.API_BASE_URL}/change-password`,t)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.LFG(ce.eN))},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})();function ct(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"span",15),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.oldPasswordVisible=!n.oldPasswordVisible)}),e.qZA()}if(2&i){const s=e.oxw();e.Q6J("nzType",s.oldPasswordVisible?"eye-invisible":"eye")}}function pt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"span",15),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.newPasswordVisible=!n.newPasswordVisible)}),e.qZA()}if(2&i){const s=e.oxw();e.Q6J("nzType",s.newPasswordVisible?"eye-invisible":"eye")}}function dt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"span",15),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.confirmPasswordVisible=!n.confirmPasswordVisible)}),e.qZA()}if(2&i){const s=e.oxw();e.Q6J("nzType",s.confirmPasswordVisible?"eye-invisible":"eye")}}let ut=(()=>{var i;class a{constructor(t,n,o){this.usersService=t,this.fb=n,this.app=o,this.oldPasswordVisible=!1,this.newPasswordVisible=!1,this.confirmPasswordVisible=!1,this.showTaskModal=!1,this.loading=!1,this.passwordPolicy=j.QW,this.app.setTitle("Change Password"),A.s.track(I.yv),this.passwordChangeForm=this.fb.group({password:[null,[p.kI.required]],new_password:[null,[p.kI.required]],confirm_password:[null,[p.kI.required]]},{validators:[this.confirmPasswordsValidator]})}confirmPasswordsValidator(t){return(0,m.Z)(function*(){return t.controls.confirm_password.value!==t.controls.new_password.value?t.controls.confirm_password.setErrors({passwordMismatch:!0}):t.controls.confirm_password})()}submitForm(){var t=this;return(0,m.Z)(function*(){const n={password:t.passwordChangeForm.value.password,new_password:t.passwordChangeForm.value.new_password,confirm_password:t.passwordChangeForm.value.confirm_password};n.password&&n.new_password&&n.confirm_password&&(t.passwordChangeForm.setErrors({invalid:!1}),(yield t.usersService.changePassword(n)).done&&(t.passwordChangeForm.reset(),t.showTaskModal=!1))})()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(lt),e.Y36(p.qu),e.Y36(U.z))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-change-password"]],decls:35,vars:26,consts:[["nz-form","",3,"formGroup","nzLayout","submit"],[3,"nzSm","nzXs","nzErrorTip"],["nzRequired",""],[3,"nzSuffix"],["autocomplete","off","nz-input","","placeholder","Enter your current password",3,"type","formControlName"],["suffixTemplateOld",""],["autocomplete","off","nz-input","","placeholder","New Password",3,"type","formControlName"],["suffixTemplateNew",""],[1,"mb-2"],[3,"nzErrorTip","nzSm","nzXs"],["autocomplete","off","nz-input","","placeholder","Confirm New Password",3,"type","formControlName"],["suffixTemplateConfirm",""],[3,"nzSm","nzXs"],["nz-typography","",1,"d-block",2,"font-size","12px",3,"nzType"],["nz-button","","nzType","primary","type","submit"],["nz-icon","",3,"nzType","click"]],template:function(t,n){if(1&t&&(e.TgZ(0,"nz-card")(1,"form",0),e.NdJ("submit",function(){return n.submitForm()}),e.TgZ(2,"nz-form-item")(3,"nz-form-control",1)(4,"nz-form-label",2),e._uU(5,"Current Password"),e.qZA(),e.TgZ(6,"nz-input-group",3),e._UZ(7,"input",4),e.qZA(),e.YNc(8,ct,1,1,"ng-template",null,5,e.W1O),e.qZA()(),e.TgZ(10,"nz-form-item")(11,"nz-form-control",1)(12,"nz-form-label",2),e._uU(13,"New Password"),e.qZA(),e.TgZ(14,"nz-input-group",3),e._UZ(15,"input",6),e.qZA(),e.YNc(16,pt,1,1,"ng-template",null,7,e.W1O),e.qZA()(),e.TgZ(18,"nz-form-item",8)(19,"nz-form-control",9)(20,"nz-form-label",2),e._uU(21,"Confirm New Password"),e.qZA(),e.TgZ(22,"nz-input-group",3),e._UZ(23,"input",10),e.qZA(),e.YNc(24,dt,1,1,"ng-template",null,11,e.W1O),e.qZA()(),e.TgZ(26,"nz-form-item",8)(27,"nz-form-control",12)(28,"span",13),e._uU(29),e.ALo(30,"lowercase"),e.qZA()()(),e.TgZ(31,"nz-form-item")(32,"nz-form-control")(33,"button",14),e._uU(34," Update "),e.qZA()()()()()),2&t){const o=e.MAs(9),r=e.MAs(17),l=e.MAs(25);let d;e.xp6(1),e.Q6J("formGroup",n.passwordChangeForm)("nzLayout","vertical"),e.xp6(2),e.Q6J("nzSm",14)("nzXs",24)("nzErrorTip","Please input your old password!"),e.xp6(3),e.Q6J("nzSuffix",o),e.xp6(1),e.Q6J("type",n.oldPasswordVisible?"text":"password")("formControlName","password"),e.xp6(4),e.Q6J("nzSm",14)("nzXs",24)("nzErrorTip","Please input your new password!"),e.xp6(3),e.Q6J("nzSuffix",r),e.xp6(1),e.Q6J("type",n.newPasswordVisible?"text":"password")("formControlName","new_password"),e.xp6(4),e.Q6J("nzErrorTip",null!=(d=n.passwordChangeForm.get("confirm_password"))&&d.hasError("passwordMismatch")?"Passwords do not match!":"Please confirm your new password!")("nzSm",14)("nzXs",24),e.xp6(3),e.Q6J("nzSuffix",l),e.xp6(1),e.Q6J("type",n.confirmPasswordVisible?"text":"password")("formControlName","confirm_password"),e.xp6(4),e.Q6J("nzSm",14)("nzXs",24),e.xp6(1),e.Q6J("nzType","secondary"),e.xp6(1),e.hij("New password should be ",e.lcZ(30,24,n.passwordPolicy),"")}},dependencies:[V.bd,P.Ls,p._Y,p.Fj,p.JJ,p.JL,p.sg,p.u,Z.t3,Z.SK,x.Lr,x.Nx,x.iK,x.Fd,M.Zp,M.gB,M.ke,O.ix,v.w,L.dQ,Q.ZU,h.i8],styles:["[nz-form][_ngcontent-%COMP%]{max-width:600px}"]}),a})();var oe=c(59780);let _t=(()=>{var i;class a{constructor(t,n){this.auth=t,this.router=n}canActivate(t,n){return!this.auth.getCurrentSession()?.is_google||this.router.navigate(["/worklenz"])}}return(i=a).\u0275fac=function(t){return new(t||i)(e.LFG(w.e),e.LFG(N.F0))},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})(),ht=(()=>{var i;class a extends le.P{constructor(t){super(),this.http=t,this.root=`${this.API_BASE_URL}/timezones`}update(t){return this._put(this.http,this.root,t)}get(){return this._get(this.http,this.root)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.LFG(ce.eN))},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})();var K=c(9691);function gt(i,a){if(1&i&&(e.TgZ(0,"nz-option",9),e._uU(1),e.qZA()),2&i){const s=a.$implicit;e.Q6J("nzLabel",s)("nzValue",s),e.xp6(1),e.hij(" ",s," ")}}function ft(i,a){if(1&i&&(e.TgZ(0,"nz-option",9)(1,"div",10)(2,"span"),e._uU(3),e.qZA(),e.TgZ(4,"span",11),e._uU(5),e.qZA()()()),2&i){const s=a.$implicit;e.s9C("nzLabel",s.name),e.Q6J("nzValue",s.id),e.xp6(3),e.Oqu(s.name),e.xp6(2),e.Oqu(s.abbrev)}}let zt=(()=>{var i;class a{constructor(t,n,o,r){this.app=t,this.api=n,this.fb=o,this.auth=r,this.loading=!1,this.updating=!1,this.options=["English"],this.timezones=[],this.app.setTitle("Language & Region"),A.s.track(I.xH);const l=this.auth.getCurrentSession();this.form=this.fb.group({language:["English",p.kI.required],timezone:[l?.timezone||null,p.kI.required]})}ngOnInit(){this.get()}submit(){var t=this;return(0,m.Z)(function*(){if(t.form.invalid)return t.app.displayErrorsOf(t.form);try{t.updating=!0,(yield t.api.update(t.form.value)).done&&(yield t.auth.authorize()),t.updating=!1}catch{t.updating=!1}})()}get(){var t=this;return(0,m.Z)(function*(){try{t.loading=!0;const n=yield t.api.get();n.done&&(t.timezones=n.body),t.loading=!1}catch{t.loading=!1}})()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(U.z),e.Y36(ht),e.Y36(p.qu),e.Y36(w.e))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-language-and-region"]],decls:19,vars:13,consts:[[3,"nzActive","nzLoading"],["nz-form","","nzLayout","vertical",3,"formGroup","ngSubmit"],["nzFor","name","nzRequired",""],[3,"nzSm","nzXs"],[3,"formControlName"],["nzCustomContent","",3,"nzLabel","nzValue",4,"ngFor","ngForOf"],["nzShowSearch","",3,"formControlName"],["nz-row","",1,"register-area"],["nz-button","","nzType","primary",3,"disabled","nzLoading"],["nzCustomContent","",3,"nzLabel","nzValue"],[1,"d-flex","justify-content-between"],["nz-typography","","nzType","secondary"]],template:function(t,n){1&t&&(e.TgZ(0,"nz-card")(1,"nz-skeleton",0)(2,"form",1),e.NdJ("ngSubmit",function(){return n.submit()}),e.TgZ(3,"nz-form-item")(4,"nz-form-label",2),e._uU(5,"Language"),e.qZA(),e.TgZ(6,"nz-form-control",3)(7,"nz-select",4),e.YNc(8,gt,2,3,"nz-option",5),e.qZA()()(),e.TgZ(9,"nz-form-item")(10,"nz-form-label",2),e._uU(11,"Time zone"),e.qZA(),e.TgZ(12,"nz-form-control",3)(13,"nz-select",6),e.YNc(14,ft,6,4,"nz-option",5),e.qZA()()(),e.TgZ(15,"nz-form-item",7)(16,"nz-form-control")(17,"button",8),e._uU(18," Save Changes "),e.qZA()()()()()()),2&t&&(e.xp6(1),e.Q6J("nzActive",!0)("nzLoading",n.loading||n.updating),e.xp6(1),e.Q6J("formGroup",n.form),e.xp6(4),e.Q6J("nzSm",8)("nzXs",24),e.xp6(1),e.Q6J("formControlName","language"),e.xp6(1),e.Q6J("ngForOf",n.options),e.xp6(4),e.Q6J("nzSm",8)("nzXs",24),e.xp6(1),e.Q6J("formControlName","timezone"),e.xp6(1),e.Q6J("ngForOf",n.timezones),e.xp6(3),e.Q6J("disabled",n.loading)("nzLoading",n.updating))},dependencies:[h.sg,V.bd,p._Y,p.JJ,p.JL,p.sg,p.u,Z.t3,Z.SK,x.Lr,x.Nx,x.iK,x.Fd,O.ix,v.w,L.dQ,Q.ZU,$.ng,K.Ip,K.Vq]}),a})();var Tt=c(8725),Oe=c(9172),E=c(62787),ze=c(55695),Te=c(19035);function Ct(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"li",17),e.NdJ("click",function(){const o=e.CHM(s).$implicit,r=e.oxw().$implicit,l=e.oxw();return e.KtG(l.updateColorCode(r.id,o))}),e.TgZ(1,"nz-tag",18),e._uU(2),e.qZA()()}if(2&i){const s=a.$implicit,t=e.oxw().$implicit,n=e.oxw();e.xp6(1),e.Q6J("nzColor",s+n.alpha),e.xp6(1),e.Oqu(t.name)}}function kt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"tr",8)(1,"td")(2,"nz-tag",9),e._uU(3),e.qZA(),e.TgZ(4,"nz-dropdown-menu",null,10)(6,"ul",11),e.YNc(7,Ct,3,2,"li",12),e.qZA()()(),e.TgZ(8,"td"),e._uU(9),e.qZA(),e.TgZ(10,"td",13)(11,"div",14)(12,"nz-space")(13,"button",15),e.NdJ("nzOnConfirm",function(){const o=e.CHM(s).$implicit,r=e.oxw();return e.KtG(r.deleteLabel(o))}),e._UZ(14,"span",16),e.qZA()()()()()}if(2&i){const s=a.$implicit,t=e.MAs(5),n=e.oxw();e.xp6(2),e.Q6J("nzColor",s.color_code+n.alpha)("nzDropdownMenu",t)("nzTooltipTitle","Click to change color")("nzTrigger","click"),e.xp6(1),e.Oqu(s.name),e.xp6(4),e.Q6J("ngForOf",n.colorCodes),e.xp6(2),e.Oqu(s.usage),e.xp6(4),e.Q6J("nzOkText","Yes")("nzPopconfirmTitle","Are you sure?")("nzSize","small")("nzTooltipPlacement","top")("nzTooltipTitle","Delete")("nzType","default")}}function xt(i,a){1&i&&e._UZ(0,"span",25)}function bt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"form",21)(1,"nz-input-group",22)(2,"input",23),e.NdJ("ngModelChange",function(n){e.CHM(s);const o=e.oxw(2);return e.KtG(o.labelsSearch=n)})("ngModelChange",function(n){e.CHM(s);const o=e.oxw(2);return e.KtG(o.searchLabels(n))}),e.qZA()(),e.YNc(3,xt,1,0,"ng-template",null,24,e.W1O),e.qZA()}if(2&i){const s=e.MAs(4),t=e.oxw(2);e.Q6J("nzLayout","vertical"),e.xp6(1),e.Q6J("nzSuffix",s),e.xp6(1),e.Q6J("ngModel",t.labelsSearch)}}function vt(i,a){if(1&i&&e._UZ(0,"worklenz-toggle-menu-button",26),2&i){const s=e.oxw(2);e.Q6J("key",s.menu.LABELS_MENU)}}function wt(i,a){1&i&&(e.TgZ(0,"nz-space"),e.YNc(1,bt,5,3,"form",19),e.YNc(2,vt,1,1,"worklenz-toggle-menu-button",20),e.qZA())}const yt=function(){return{rows:5}};let St=(()=>{var i;class a{constructor(t,n,o,r,l){this.app=t,this.api=n,this.searchPipe=o,this.cdr=r,this.menu=l,this.colorCodes=j.lD,this.labels=[],this.filteredLabels=[],this.loading=!1,this.alpha=j.Yj,this.labelsSearch=null,this.app.setTitle("Manage Labels"),A.s.track(I.Hw)}ngOnInit(){this.get()}trackByFn(t,n){return n.id}get(){var t=this;return(0,m.Z)(function*(){try{t.loading=!0;const n=yield t.api.get();n.done&&(t.labels=n.body,t.filteredLabels=t.labels),t.loading=!1}catch(n){t.loading=!1,(0,b.tu)(n)}t.cdr.detectChanges()})()}deleteLabel(t){var n=this;return(0,m.Z)(function*(){if(t?.id){try{n.loading=!0,(yield n.api.deleteById(t.id)).done&&(A.s.track(I.aJ),n.get()),n.loading=!1}catch(o){n.loading=!1,(0,b.tu)(o)}n.cdr.detectChanges()}})()}updateColorCode(t,n){var o=this;return(0,m.Z)(function*(){if(t&&n)try{if((yield o.api.updateColor(t,n)).done){const l=o.labels.find(d=>d.id===t);l&&(l.color_code=n),o.cdr.markForCheck()}}catch(r){(0,b.tu)(r)}})()}searchLabels(t){this.filteredLabels=this.searchPipe.transform(this.labels,t||null),this.cdr.markForCheck()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(U.z),e.Y36(Tt.u),e.Y36(Oe.g),e.Y36(e.sBO),e.Y36(te.h))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-labels"]],decls:14,vars:11,consts:[[3,"nzExtra"],[3,"nzActive","nzLoading","nzParagraph"],["nzPaginationType","small","nzSize","small",1,"custom-table",3,"nzData","nzLoading","nzNoResult"],["table",""],["scope","col","nzAlign","left",3,"nzWidth"],["scope","col","nzAlign","left","colspan","2"],["class","actions-row",4,"ngFor","ngForOf","ngForTrackBy"],["actionsTemplate",""],[1,"actions-row"],["nz-dropdown","","nz-tooltip","",1,"text-dark","m-0",2,"cursor","pointer",3,"nzColor","nzDropdownMenu","nzTooltipTitle","nzTrigger"],["colorDropdown","nzDropdownMenu"],["nz-menu","","nzSelectable","",2,"max-height","200px","overflow","hidden","overflow-y","auto"],["nz-menu-item","",3,"click",4,"ngFor","ngForOf"],["nzAlign","center",1,"actions-col"],[1,"actions"],["nz-button","","nz-popconfirm","","nz-tooltip","",3,"nzOkText","nzPopconfirmTitle","nzSize","nzTooltipPlacement","nzTooltipTitle","nzType","nzOnConfirm"],["nz-icon","","nzType","delete"],["nz-menu-item","",3,"click"],[1,"text-dark","m-0","w-100",3,"nzColor"],["nz-form","",3,"nzLayout",4,"nzSpaceItem"],[3,"key",4,"nzSpaceItem"],["nz-form","",3,"nzLayout"],[3,"nzSuffix"],["nz-input","","name","search","placeholder","Search by name","type","text",3,"ngModel","ngModelChange"],["suffixIconSearch",""],["nz-icon","","nzType","search"],[3,"key"]],template:function(t,n){if(1&t&&(e.TgZ(0,"nz-card",0)(1,"nz-skeleton",1)(2,"nz-table",2,3)(4,"thead")(5,"tr")(6,"th",4),e._uU(7,"Label"),e.qZA(),e.TgZ(8,"th",5),e._uU(9,"Associated tasks"),e.qZA()()(),e.TgZ(10,"tbody"),e.YNc(11,kt,15,13,"tr",6),e.qZA()()()(),e.YNc(12,wt,3,0,"ng-template",null,7,e.W1O)),2&t){const o=e.MAs(3),r=e.MAs(13);e.Q6J("nzExtra",r),e.xp6(1),e.Q6J("nzActive",!0)("nzLoading",n.loading)("nzParagraph",e.DdM(10,yt)),e.xp6(1),e.Q6J("nzData",n.filteredLabels)("nzLoading",n.loading)("nzNoResult","Labels can be created while updating or creating tasks."),e.xp6(4),e.Q6J("nzWidth","300px"),e.xp6(5),e.Q6J("ngForOf",o.data)("ngForTrackBy",n.trackByFn)}},dependencies:[h.sg,V.bd,P.Ls,p._Y,p.Fj,p.JJ,p.JL,x.Lr,M.Zp,M.gB,M.ke,O.ix,v.w,L.dQ,z.N8,z.Uo,z._C,z.Om,z.p0,z.$Z,z.UX,T.wO,T.r9,H.NU,H.$1,J.SY,$.ng,p.On,p.F,Pe.l,E.cm,E.RR,ze.j,Te.JW],changeDetection:0}),a})();var Mt=c(82803),Je=c(55522);function Pt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",12),e.NdJ("click",function(){e.CHM(s);const n=e.oxw().$implicit,o=e.oxw();return e.KtG(o.editTemplate(n.id))}),e._UZ(1,"span",13),e.qZA()}}function Ot(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",14),e.NdJ("nzOnConfirm",function(){e.CHM(s);const n=e.oxw().$implicit,o=e.oxw();return e.KtG(o.deleteTemplate(n.id))}),e._UZ(1,"span",15),e.qZA()}}function At(i,a){if(1&i&&(e.TgZ(0,"tr",7)(1,"td"),e._uU(2),e.qZA(),e.TgZ(3,"td"),e._uU(4),e.ALo(5,"fromNow"),e.qZA(),e.TgZ(6,"td",8)(7,"div",9)(8,"nz-space"),e.YNc(9,Pt,2,0,"button",10),e.YNc(10,Ot,2,0,"button",11),e.qZA()()()()),2&i){const s=a.$implicit;e.xp6(2),e.Oqu(s.name),e.xp6(2),e.Oqu(e.lcZ(5,2,s.created_at))}}const It=function(){return{rows:5}};let Zt=(()=>{var i;class a{constructor(t,n,o,r,l){this.api=t,this.settingsApi=n,this.fb=o,this.app=r,this.menu=l,this.taskTemplates=[],this.loading=!1,this.updating=!1,this.drawerVisible=!1,this.editingTeamId=null,this.selectedTemplateId="",this.app.setTitle("Task Templates"),A.s.track(I.x0),this.form=this.fb.group({name:[null,p.kI.required]})}ngOnInit(){this.getTaskTemplates().then(t=>t)}getTaskTemplates(){var t=this;return(0,m.Z)(function*(){try{t.loading=!0;const n=yield t.api.get();n.done&&(t.taskTemplates=n.body),t.loading=!1}catch(n){t.loading=!1,(0,b.tu)(n)}})()}closeModal(){this.drawerVisible=!1}editTemplate(t){t&&(this.selectedTemplateId=t,this.drawerVisible=!0)}taskTemplateCancel(t){this.drawerVisible=t,this.selectedTemplateId=""}onCreateOrUpdate(){this.getTaskTemplates().then(t=>t)}deleteTemplate(t=""){var n=this;return(0,m.Z)(function*(){try{(yield n.api.delete(t)).done&&(A.s.track(I.O1),n.getTaskTemplates())}catch{}})()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(Mt.e),e.Y36(he.G),e.Y36(p.qu),e.Y36(U.z),e.Y36(te.h))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-task-templates"]],decls:13,vars:9,consts:[[3,"nzActive","nzLoading","nzParagraph"],["nzPaginationType","small","nzSize","small",1,"custom-table",3,"nzData","nzLoading"],["table",""],["scope","col"],["colspan","2","nzAlign","left","scope","col"],["class","actions-row",4,"ngFor","ngForOf"],[3,"drawerVisible","selectedTemplateId","onCancelClick","onCreateOrUpdate"],[1,"actions-row"],["nzAlign","center",1,"actions-col"],[1,"actions"],["nz-button","","nz-tooltip","","nzSize","small","nzTooltipPlacement","top","nzTooltipTitle","Edit","nzType","default",3,"click",4,"nzSpaceItem"],["nz-button","","nz-popconfirm","","nz-tooltip","","nzOkText","Yes","nzPopconfirmTitle","Are you sure?","nzSize","small","nzTooltipPlacement","top","nzTooltipTitle","Delete","nzType","default",3,"nzOnConfirm",4,"nzSpaceItem"],["nz-button","","nz-tooltip","","nzSize","small","nzTooltipPlacement","top","nzTooltipTitle","Edit","nzType","default",3,"click"],["nz-icon","","nzType","edit"],["nz-button","","nz-popconfirm","","nz-tooltip","","nzOkText","Yes","nzPopconfirmTitle","Are you sure?","nzSize","small","nzTooltipPlacement","top","nzTooltipTitle","Delete","nzType","default",3,"nzOnConfirm"],["nz-icon","","nzType","delete"]],template:function(t,n){if(1&t&&(e.TgZ(0,"nz-card")(1,"nz-skeleton",0)(2,"nz-table",1,2)(4,"thead")(5,"tr")(6,"th",3),e._uU(7,"Name"),e.qZA(),e.TgZ(8,"th",4),e._uU(9,"Created"),e.qZA()()(),e.TgZ(10,"tbody"),e.YNc(11,At,11,4,"tr",5),e.qZA()()()(),e.TgZ(12,"worklenz-task-template-drawer",6),e.NdJ("onCancelClick",function(r){return n.taskTemplateCancel(r)})("onCreateOrUpdate",function(){return n.onCreateOrUpdate()}),e.qZA()),2&t){const o=e.MAs(3);e.xp6(1),e.Q6J("nzActive",!0)("nzLoading",n.loading)("nzParagraph",e.DdM(8,It)),e.xp6(1),e.Q6J("nzData",n.taskTemplates)("nzLoading",n.loading),e.xp6(9),e.Q6J("ngForOf",o.data),e.xp6(1),e.Q6J("drawerVisible",n.drawerVisible)("selectedTemplateId",n.selectedTemplateId)}},dependencies:[h.sg,V.bd,P.Ls,O.ix,v.w,L.dQ,z.N8,z.Uo,z._C,z.Om,z.p0,z.$Z,z.UX,H.NU,H.$1,J.SY,$.ng,Je.x,Te.JW,ge.d]}),a})();var G=c(71993),Lt=c(45538),Ft=c(3278),de=c(64532),Nt=c(8689),Ge=c(72095),De=c(96928),Ye=c(28677);function Et(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",15),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.refresh())}),e._UZ(1,"span",16),e.qZA()}if(2&i){const s=e.oxw();e.xp6(1),e.Q6J("nzSpin",s.loading)}}function Ut(i,a){1&i&&e._UZ(0,"span",21)}function Jt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"form",17),e.NdJ("ngSubmit",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.search())}),e.TgZ(1,"nz-input-group",18),e._UZ(2,"input",19),e.qZA(),e.YNc(3,Ut,1,0,"ng-template",null,20,e.W1O),e.qZA()}if(2&i){const s=e.MAs(4),t=e.oxw();e.Q6J("formGroup",t.searchForm)("nzLayout","vertical"),e.xp6(1),e.Q6J("nzSuffix",s),e.xp6(1),e.Q6J("formControlName","search")}}function Gt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"span")(1,"button",22),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.openAddMemberForm())}),e._uU(2,"Add Member"),e.qZA()()}}function Dt(i,a){if(1&i&&e._UZ(0,"nz-avatar",34),2&i){const s=e.oxw().$implicit,t=e.oxw();e.Udp("background-color",s.avatar_url?"#ececec":t.getColor(s.name)),e.Q6J("nzSize",28)("nzText",s.name.charAt(0).toUpperCase())("nzSrc",s.avatar_url)}}function Yt(i,a){1&i&&(e.TgZ(0,"span",35),e._uU(1," (Deactivated)"),e.qZA())}function Qt(i,a){if(1&i&&(e.TgZ(0,"span"),e._uU(1),e.qZA()),2&i){const s=e.oxw().$implicit;e.xp6(1),e.Oqu(s.email)}}function Rt(i,a){1&i&&(e.TgZ(0,"span"),e._uU(1,"-"),e.qZA())}function Bt(i,a){if(1&i&&(e.TgZ(0,"span"),e._uU(1),e.TgZ(2,"small",36),e._uU(3,"(Pending Invitation)"),e.qZA()()),2&i){const s=e.oxw().$implicit;e.xp6(1),e.hij(" ",s.email," ")}}function Ht(i,a){if(1&i&&(e.TgZ(0,"span",37),e._uU(1),e.qZA()),2&i){const s=e.oxw().$implicit;e.xp6(1),e.Oqu(s.role_name)}}function $t(i,a){if(1&i&&(e.TgZ(0,"span",38),e._uU(1),e.qZA()),2&i){const s=e.oxw().$implicit;e.xp6(1),e.Oqu(s.role_name)}}function jt(i,a){if(1&i&&(e.TgZ(0,"span",39),e._uU(1),e.qZA()),2&i){const s=e.oxw().$implicit;e.xp6(1),e.Oqu(s.role_name)}}function qt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",43),e.NdJ("click",function(){e.CHM(s);const n=e.oxw(2).$implicit,o=e.oxw();return e.KtG(o.editMember(n.id))}),e._UZ(1,"span",44),e.qZA()}2&i&&e.Q6J("nzTooltipPlacement","top")("nzTooltipTitle","Edit")("nzType","default")}function Vt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",46),e.NdJ("nzOnConfirm",function(){e.CHM(s);const n=e.oxw(3).$implicit,o=e.oxw();return e.KtG(o.toggleMemberActiveStatus(n))}),e._UZ(1,"span",47),e.qZA()}if(2&i){const s=e.oxw(3).$implicit;e.Q6J("nzOkText","Yes")("nzPopconfirmTitle","Are you sure?")("nzTooltipPlacement","top")("nzTooltipTitle",s.active?"Deactivate":"Activate")("nzType","default")}}function Kt(i,a){if(1&i&&(e.TgZ(0,"div"),e.YNc(1,Vt,2,5,"button",45),e.qZA()),2&i){const s=e.oxw(2).$implicit,t=e.oxw();e.xp6(1),e.Q6J("ngIf",s.id!==(null==t.profile?null:t.profile.team_member_id))}}function Wt(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",48),e.NdJ("nzOnConfirm",function(){e.CHM(s);const n=e.oxw(2).$implicit,o=e.oxw();return e.KtG(o.deleteTeamMember(n.id,n.email))}),e._UZ(1,"span",49),e.qZA()}2&i&&e.Q6J("nzOkText","Yes")("nzPopconfirmTitle","Are you sure?")("nzSize","small")("nzTooltipPlacement","top")("nzTooltipTitle","Delete")("nzType","default")}function Xt(i,a){1&i&&(e.TgZ(0,"div",40)(1,"nz-space"),e.YNc(2,qt,2,3,"button",41),e.YNc(3,Kt,2,1,"div",4),e.YNc(4,Wt,2,6,"button",42),e.qZA()())}function en(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"tr",23)(1,"td",24),e.NdJ("click",function(){const o=e.CHM(s).$implicit,r=e.oxw();return e.KtG(r.selectMember(o.id))}),e.YNc(2,Dt,1,5,"nz-avatar",25),e.TgZ(3,"nz-badge",26),e._uU(4),e.qZA(),e.YNc(5,Yt,2,0,"span",27),e.qZA(),e.TgZ(6,"td",24),e.NdJ("click",function(){const o=e.CHM(s).$implicit,r=e.oxw();return e.KtG(r.selectMember(o.id))}),e._uU(7),e.qZA(),e.TgZ(8,"td",24),e.NdJ("click",function(){const o=e.CHM(s).$implicit,r=e.oxw();return e.KtG(r.selectMember(o.id))}),e.YNc(9,Qt,2,1,"span",28),e.YNc(10,Rt,2,0,"span",28),e.YNc(11,Bt,4,1,"span",28),e.qZA(),e.TgZ(12,"td",24),e.NdJ("click",function(){const o=e.CHM(s).$implicit,r=e.oxw();return e.KtG(r.selectMember(o.id))}),e.YNc(13,Ht,2,1,"span",29),e.YNc(14,$t,2,1,"span",30),e.YNc(15,jt,2,1,"span",31),e.qZA(),e.TgZ(16,"td",32),e.YNc(17,Xt,5,0,"div",33),e.qZA()()}if(2&i){const s=a.$implicit;e.xp6(2),e.Q6J("ngIf",s.name),e.xp6(1),e.Q6J("nzColor",s.is_online?"green":""),e.xp6(1),e.hij(" ",s.name," "),e.xp6(1),e.Q6J("ngIf",!s.active),e.xp6(2),e.Oqu(s.projects_count||0),e.xp6(2),e.Q6J("ngIf",s.email&&!s.pending_invitation),e.xp6(1),e.Q6J("ngIf",!s.email&&!s.pending_invitation),e.xp6(1),e.Q6J("ngIf",s.email&&s.pending_invitation),e.xp6(2),e.Q6J("ngIf",s.is_admin),e.xp6(1),e.Q6J("ngIf",s.is_owner),e.xp6(1),e.Q6J("ngIf",!s.is_admin&&!s.is_owner),e.xp6(2),e.Q6J("ngIf",!s.is_owner)}}const tn=function(){return{rows:6}},nn=function(){return[]};let sn=(()=>{var i;class a{constructor(t,n,o,r,l,d,_,y){this.auth=t,this.api=n,this.jobTitlesApi=o,this.fb=r,this.app=l,this.router=d,this.utilsService=_,this.settingsService=y,this.model={},this.showTeamMemberModal=!1,this.loading=!1,this.selectedMemberId=null,this.total=0,this.pageSize=j.L8,this.pageIndex=1,this.paginationSizes=[5,10,15,20,50,100],this.sortField=null,this.sortOrder=null,this.showMoreSeatsModal=!1,this.moreSeatsData={},this.app.setTitle("Team Members"),A.s.track(I.nW),this.searchForm=this.fb.group({search:[]}),this.searchForm.valueChanges.subscribe(()=>{this.search()}),this.settingsService.onNewMemberCreated.pipe((0,G.sL)()).subscribe(()=>{this.getTeamMembers()})}get profile(){return this.auth.getCurrentSession()}getTeamMembers(){var t=this;return(0,m.Z)(function*(){try{t.loading=!0;const n=yield t.api.get(t.pageIndex,t.pageSize,t.sortField,t.sortOrder,t.searchForm.value.search);n.done&&(t.model=n.body,t.total=t.model.total||0,t.utilsService.handleLastIndex(t.total,t.model.data?.length||0,t.pageIndex,o=>{t.pageIndex=o,t.getTeamMembers()})),t.loading=!1}catch(n){(0,b.tu)(n),t.loading=!1}})()}editMember(t){this.selectedMemberId=t||null,this.showTeamMemberModal=!0}reset(){this.selectedMemberId=null}deleteTeamMember(t,n){var o=this;return(0,m.Z)(function*(){if(t&&n)try{(yield o.api.delete(t,n)).done&&(A.s.track(I.OA),yield o.getTeamMembers(),o.auth.getCurrentSession()?.team_member_id===t&&window.location.reload())}catch(r){(0,b.tu)(r)}})()}selectValue(t){t&&t.select()}search(){var t=this;return(0,m.Z)(function*(){t.searchForm.value.search&&A.s.track(I.Q5),t.getTeamMembers()})()}selectMember(t){t&&(A.s.track(I.uc),this.editMember(t))}getColor(t){return j.Lj[t?.charAt(0).toUpperCase()||"A"]}onQueryParamsChange(t){var n=this;return(0,m.Z)(function*(){const{pageSize:o,pageIndex:r,sort:l}=t;n.pageIndex=r,n.pageSize=o;const d=l.find(_=>null!==_.value);n.sortField=d&&d.key||null,n.sortOrder=d&&d.value||"descend",yield n.getTeamMembers()})()}openAddMemberForm(){this.showTeamMemberModal=!0,A.s.track(I.oo)}refresh(){A.s.track(I.r9),this.getTeamMembers()}handleOnCreateOrUpdate(t){1==t&&A.s.track(I.YC),this.getTeamMembers()}toggleMemberActiveStatus(t){var n=this;return(0,m.Z)(function*(){if(t.id)try{(yield n.api.toggleMemberActiveStatus(t.id,t.active,t.email)).done&&(t.active&&A.s.track(I.ZH),t.active||A.s.track(I.aY),yield n.getTeamMembers())}catch(o){(0,b.tu)(o)}})()}handleMoreSeatsNeeded(t){this.moreSeatsData=t,this.showMoreSeatsModal=!0}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(w.e),e.Y36(Lt.B),e.Y36(Ft.i),e.Y36(p.qu),e.Y36(U.z),e.Y36(N.F0),e.Y36(de.F),e.Y36(Nt.g))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-team-members"]],decls:27,vars:24,consts:[[1,"container"],[1,"px-0",3,"nzGhost"],["nz-button","","nz-tooltip","","nzShape","circle","nzTooltipTitle","Refresh members","nzType","default",3,"click",4,"nzSpaceItem"],["nz-form","",3,"formGroup","nzLayout","ngSubmit",4,"nzSpaceItem"],[4,"nzSpaceItem"],[3,"nzActive","nzLoading","nzParagraph"],["nzShowSizeChanger","","nzSize","small",1,"custom-table",3,"nzData","nzFrontPagination","nzLoading","nzPageIndex","nzPageSizeOptions","nzPageSize","nzTotal","nzQueryParams"],["table",""],["nzColumnKey","name","scope","col",3,"nzSortFn"],["nzColumnKey","projects_count","scope","col",3,"nzSortFn"],["nzColumnKey","email","scope","col",3,"nzSortFn"],["colspan","2","nzAlign","left","nzColumnKey","role_name","scope","col",3,"nzSortFn"],["class","actions-row",4,"ngFor","ngForOf"],[3,"show","memberId","onCancel","onCreateOrUpdate","onMoreSeatsNeeded","showChange"],[3,"isVisible","subscriptionData","modalClosed"],["nz-button","","nz-tooltip","","nzShape","circle","nzTooltipTitle","Refresh members","nzType","default",3,"click"],["nz-icon","","nzTheme","outline","nzType","sync",3,"nzSpin"],["nz-form","",3,"formGroup","nzLayout","ngSubmit"],[3,"nzSuffix"],["nz-input","","placeholder","Search by name","type","text",3,"formControlName"],["suffixIconSearch",""],["nz-icon","","nzType","search"],["nz-button","","nzType","primary",3,"click"],[1,"actions-row"],[1,"cursor-pointer",3,"click"],["class","me-2",3,"nzSize","nzText","background-color","nzSrc",4,"ngIf"],[1,"d-inline-flex","align-items-center","flex-row-reverse",2,"color","inherit",3,"nzColor"],["nz-typography","","nzType","warning",4,"ngIf"],[4,"ngIf"],["class","admin-role","nz-typography","",4,"ngIf"],["class","owner-role","nz-typography","",4,"ngIf"],["class","member-role","nz-typography","",4,"ngIf"],[1,"actions-col"],["class","actions",4,"ngIf"],[1,"me-2",3,"nzSize","nzText","nzSrc"],["nz-typography","","nzType","warning"],["nz-typography","","nzType","secondary"],["nz-typography","",1,"admin-role"],["nz-typography","",1,"owner-role"],["nz-typography","",1,"member-role"],[1,"actions"],["nz-button","","nz-tooltip","","nzSize","small",3,"nzTooltipPlacement","nzTooltipTitle","nzType","click",4,"nzSpaceItem"],["nz-button","","nz-popconfirm","","nz-tooltip","",3,"nzOkText","nzPopconfirmTitle","nzSize","nzTooltipPlacement","nzTooltipTitle","nzType","nzOnConfirm",4,"nzSpaceItem"],["nz-button","","nz-tooltip","","nzSize","small",3,"nzTooltipPlacement","nzTooltipTitle","nzType","click"],["nz-icon","","nzType","edit"],["nz-button","","nz-tooltip","","nz-popconfirm","","nzSize","small",3,"nzOkText","nzPopconfirmTitle","nzTooltipPlacement","nzTooltipTitle","nzType","nzOnConfirm",4,"ngIf"],["nz-button","","nz-tooltip","","nz-popconfirm","","nzSize","small",3,"nzOkText","nzPopconfirmTitle","nzTooltipPlacement","nzTooltipTitle","nzType","nzOnConfirm"],["nz-icon","","nzType","user-switch","nzTheme","outline"],["nz-button","","nz-popconfirm","","nz-tooltip","",3,"nzOkText","nzPopconfirmTitle","nzSize","nzTooltipPlacement","nzTooltipTitle","nzType","nzOnConfirm"],["nz-icon","","nzType","delete"]],template:function(t,n){if(1&t&&(e.TgZ(0,"div",0)(1,"nz-page-header",1)(2,"nz-page-header-title"),e._uU(3),e.qZA(),e.TgZ(4,"nz-page-header-extra")(5,"nz-space"),e.YNc(6,Et,2,1,"button",2),e.YNc(7,Jt,5,4,"form",3),e.YNc(8,Gt,3,0,"span",4),e.qZA()()(),e.TgZ(9,"nz-card")(10,"nz-skeleton",5)(11,"nz-table",6,7),e.NdJ("nzQueryParams",function(r){return n.onQueryParamsChange(r)}),e.TgZ(13,"thead")(14,"tr")(15,"th",8),e._uU(16,"Name"),e.qZA(),e.TgZ(17,"th",9),e._uU(18,"Projects"),e.qZA(),e.TgZ(19,"th",10),e._uU(20,"Email"),e.qZA(),e.TgZ(21,"th",11),e._uU(22,"Team Access"),e.qZA()()(),e.TgZ(23,"tbody"),e.YNc(24,en,18,12,"tr",12),e.qZA()()()()(),e.TgZ(25,"worklenz-team-members-form",13),e.NdJ("onCancel",function(){return n.reset()})("onCreateOrUpdate",function(r){return n.handleOnCreateOrUpdate(r)})("onMoreSeatsNeeded",function(r){return n.handleMoreSeatsNeeded(r)})("showChange",function(r){return n.showTeamMemberModal=r}),e.qZA(),e.TgZ(26,"worklenz-add-more-seats",14),e.NdJ("modalClosed",function(){return n.showMoreSeatsModal=!1}),e.qZA()),2&t){const o=e.MAs(12);e.xp6(1),e.Q6J("nzGhost",!1),e.xp6(2),e.AsE("",n.total||0," Member",n.total>1?"s":"",""),e.xp6(7),e.Q6J("nzActive",!0)("nzLoading",n.loading)("nzParagraph",e.DdM(22,tn)),e.xp6(1),e.Q6J("nzData",n.model.data||e.DdM(23,nn))("nzFrontPagination",!1)("nzLoading",n.loading)("nzPageIndex",n.pageIndex)("nzPageSizeOptions",n.paginationSizes)("nzPageSize",n.pageSize)("nzTotal",n.total),e.xp6(4),e.Q6J("nzSortFn",!0),e.xp6(2),e.Q6J("nzSortFn",!0),e.xp6(2),e.Q6J("nzSortFn",!0),e.xp6(2),e.Q6J("nzSortFn",!0),e.xp6(3),e.Q6J("ngForOf",o.data),e.xp6(1),e.Q6J("show",n.showTeamMemberModal)("memberId",n.selectedMemberId),e.xp6(1),e.Q6J("isVisible",n.showMoreSeatsModal)("subscriptionData",n.moreSeatsData)}},dependencies:[h.sg,h.O5,V.bd,Y.$O,Y.u9,Y.Jp,P.Ls,p._Y,p.Fj,p.JJ,p.JL,p.sg,p.u,x.Lr,M.Zp,M.gB,M.ke,O.ix,v.w,L.dQ,z.N8,z.qD,z.Uo,z._C,z.Om,z.p0,z.$Z,z.UX,Q.ZU,H.NU,H.$1,J.SY,$.ng,ie.x7,Te.JW,Ge.S,De.Dz,Ye.x]}),a})();var on=c(80697);function an(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",10),e.NdJ("click",function(){e.CHM(s);const n=e.oxw().$implicit,o=e.oxw();return e.KtG(o.editTemplate(n.id,n.name))}),e._UZ(1,"span",11),e.qZA()}}function rn(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",12),e.NdJ("nzOnConfirm",function(){e.CHM(s);const n=e.oxw().$implicit,o=e.oxw();return e.KtG(o.deleteTemplate(n.id))}),e._UZ(1,"span",13),e.qZA()}}function ln(i,a){if(1&i&&(e.TgZ(0,"tr",5)(1,"td"),e._uU(2),e.qZA(),e.TgZ(3,"td",6)(4,"div",7)(5,"nz-space"),e.YNc(6,an,2,0,"button",8),e.YNc(7,rn,2,0,"button",9),e.qZA()()()()),2&i){const s=a.$implicit;e.xp6(2),e.Oqu(s.name)}}const cn=function(){return{rows:5}};let pn=(()=>{var i;class a{constructor(t,n,o){this.router=t,this.cdr=n,this.api=o,this.loading=!1,this.projectTemplates=[]}ngOnInit(){this.get()}get(){var t=this;return(0,m.Z)(function*(){try{t.loading=!0;const n=yield t.api.getWorklenzCustomTemplates();n.done&&(t.projectTemplates=n.body,t.loading=!1,t.cdr.markForCheck()),t.loading=!1,t.cdr.markForCheck()}catch(n){(0,b.tu)(n),t.cdr.markForCheck()}})()}editTemplate(t,n){!t||!n||this.router.navigate([`/worklenz/settings/project-templates/edit/${t}/${n}`])}deleteTemplate(t){var n=this;return(0,m.Z)(function*(){if(t)try{(yield n.api.delete(t)).done&&n.get()}catch(o){(0,b.tu)(o)}})()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(N.F0),e.Y36(e.sBO),e.Y36(on.I))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-project-templates"]],decls:11,vars:7,consts:[[3,"nzActive","nzLoading","nzParagraph"],["nzPaginationType","small","nzSize","small",1,"custom-table",3,"nzData","nzLoading"],["table",""],["scope","col"],["class","actions-row",4,"ngFor","ngForOf"],[1,"actions-row"],["nzAlign","center",1,"actions-col"],[1,"actions"],["nz-button","","nz-tooltip","","nzSize","small","nzTooltipPlacement","top","nzTooltipTitle","Edit","nzType","default",3,"click",4,"nzSpaceItem"],["nz-button","","nz-popconfirm","","nz-tooltip","","nzOkText","Yes","nzPopconfirmTitle","Are you sure?","nzSize","small","nzTooltipPlacement","top","nzTooltipTitle","Delete","nzType","default",3,"nzOnConfirm",4,"nzSpaceItem"],["nz-button","","nz-tooltip","","nzSize","small","nzTooltipPlacement","top","nzTooltipTitle","Edit","nzType","default",3,"click"],["nz-icon","","nzType","edit"],["nz-button","","nz-popconfirm","","nz-tooltip","","nzOkText","Yes","nzPopconfirmTitle","Are you sure?","nzSize","small","nzTooltipPlacement","top","nzTooltipTitle","Delete","nzType","default",3,"nzOnConfirm"],["nz-icon","","nzType","delete"]],template:function(t,n){if(1&t&&(e.TgZ(0,"nz-card")(1,"nz-skeleton",0)(2,"nz-table",1,2)(4,"thead")(5,"tr")(6,"th",3),e._uU(7,"Name"),e.qZA(),e._UZ(8,"th",3),e.qZA()(),e.TgZ(9,"tbody"),e.YNc(10,ln,8,1,"tr",4),e.qZA()()()()),2&t){const o=e.MAs(3);e.xp6(1),e.Q6J("nzActive",!0)("nzLoading",n.loading)("nzParagraph",e.DdM(6,cn)),e.xp6(1),e.Q6J("nzData",n.projectTemplates)("nzLoading",n.loading),e.xp6(8),e.Q6J("ngForOf",o.data)}},dependencies:[h.sg,V.bd,P.Ls,O.ix,v.w,L.dQ,z.N8,z.Uo,z._C,z.Om,z.p0,z.$Z,z.UX,H.NU,H.$1,J.SY,$.ng,Te.JW],changeDetection:0}),a})();var Ce=c(26236),k=c(94489),R=c(78645),dn=c(65619),q=c(975);let ue=(()=>{var i;class a{constructor(){this._selectSbj$=new R.x,this._deselectSbj$=new R.x,this._deselectAllSbj$=new R.x,this._groupTaskMap=new Map,this._taskGroupIdsMap=new Map,this._selectedTasksMap=new Map,this._allTasksMap=new Map,this._subTasksMap=new Map,this._selectedCount=0}get tasks(){return this._allTasksMap}get onSelect$(){return this._selectSbj$.asObservable()}get onDeselect$(){return this._deselectSbj$.asObservable()}get onDeselectAll$(){return this._deselectAllSbj$.asObservable()}reset(){this._groupTaskMap.clear(),this._taskGroupIdsMap.clear(),this._selectedTasksMap.clear(),this._allTasksMap.clear(),this._subTasksMap.clear(),this._selectedCount=0}registerGroup(t){for(const n of t.tasks)this.add(t.id,n)}add(t,n){n.id&&(this.updateGroupTaskMap(t,n.id),this._taskGroupIdsMap.set(n.id,t),this._allTasksMap.set(n.id,n),n.parent_task_id&&this.updateSubtasksMap(n.parent_task_id,n))}addGroupTask(t,n){n.id&&this._taskGroupIdsMap.set(n.id,t)}has(t){return this._allTasksMap.has(t)}remove(t){t.id&&(this.deselectTask(t),this._taskGroupIdsMap.get(t.id),this._allTasksMap.delete(t.id))}updateGroupTaskMap(t,n,o){const r=this._groupTaskMap.get(t);r?("boolean"==typeof o?r[n]=o:delete r[n],this._groupTaskMap.set(t,r)):this._groupTaskMap.set(t,{[n]:o||!1})}updateSubtasksMap(t,n,o){const r=this._subTasksMap.get(t)||[];r.some(d=>d.id===n.id)||(r.push(n),this._subTasksMap.set(t,r))}selectTask(t){this._selectedTasksMap.get(t.id)||(this._selectedTasksMap.set(t.id,t),this._selectedCount++,this.updateGroupTaskMap(this._taskGroupIdsMap.get(t.id),t.id,!0),this._selectSbj$.next(t))}deselectTask(t){this._selectedTasksMap.has(t.id)&&(this._selectedTasksMap.delete(t.id),this._selectedCount--,this.updateGroupTaskMap(this._taskGroupIdsMap.get(t.id),t.id,!1),this._deselectSbj$.next(t))}deselectLocalGroups(){for(const[t,n]of this._groupTaskMap)for(const o in n)this.updateGroupTaskMap(t,o,!1)}deselectAll(){this._selectedTasksMap.size&&(this.deselectLocalGroups(),this._selectedTasksMap.clear(),this._selectedCount=0,this._deselectAllSbj$.next())}isAllSelected(t){const n=this._groupTaskMap.get(t);if(n){for(const o in n)if(!n[o])return!1;return!0}return!1}isAllDeselected(t){const n=this._groupTaskMap.get(t);if(n)for(const o in n)if(n[o])return!1;return!0}getSelectedCount(){return this._selectedCount}getGroupId(t){return this._taskGroupIdsMap.get(t)}getSelectedTasks(){const t=[];for(const[,n]of this._selectedTasksMap.entries())t.push(n);return t}getSelectedTaskIds(){const t=[];for(const[n]of this._selectedTasksMap.entries())t.push(n);return t}}return(i=a).\u0275fac=function(t){return new(t||i)},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})(),B=(()=>{var i;class a{get _currentGroup(){const t=localStorage.getItem("worklenz.pt-t-list.group_by");if(t){const n=this.GROUP_BY_OPTIONS.find(o=>o.value===t);if(n)return n}return this.GROUP_BY_OPTIONS[0]}set _currentGroup(t){localStorage.setItem("worklenz.pt-t-list.group_by",t.value)}set columns(t){this._cols=t,this.emitColsChange()}get columns(){return this._cols}set labels(t){this._labels=t,this.labelsSbj$.next()}get labels(){return this._labels}set priorities(t){this._priorities=t,this.prioritiesSbj$.next()}get priorities(){return this._priorities}set phases(t){this._phases=t,this.phasesSbj$.next()}get phases(){return this._phases}get onColumnsChange$(){return this.colsSbj$.asObservable()}get onLabelsChange$(){return this.labelsSbj$.asObservable()}get onStatusesChange$(){return this.statusesSbj$.asObservable()}get onPrioritiesChange$(){return this.prioritiesSbj$.asObservable()}get onContextMenu$(){return this.contextMenuSbj$.asObservable()}get onTaskAddOrDelete$(){return this.taskAddOrDeleteSbj$.asObservable()}get onGroupChange$(){return this.groupChangeSbj$.asObservable()}get onRefresh$(){return this.refreshSbj$.asObservable()}get onPhaseChange$(){return this.phasesSbj$.asObservable()}set statuses(t){this._statuses=t,this.statusesSbj$.next()}get statuses(){return this._statuses}get onRefreshSubtasksIncluded(){return this.refreshSubtasksIncludedSbj$.asObservable()}constructor(t,n){this.socket=t,this.map=n,this.colsSbj$=new R.x,this.labelsSbj$=new R.x,this.statusesSbj$=new R.x,this.prioritiesSbj$=new R.x,this.contextMenuSbj$=new R.x,this.taskAddOrDeleteSbj$=new dn.X(null),this.refreshSbj$=new R.x,this.groupChangeSbj$=new R.x,this.phasesSbj$=new R.x,this.updateGroupProgressSbj$=new R.x,this.refreshSubtasksIncludedSbj$=new R.x,this.HIGHLIGHT_COL_CLS="highlight-col",this.GROUP_BY_STATUS_VALUE="status",this.GROUP_BY_PRIORITY_VALUE="priority",this.GROUP_BY_PHASE_VALUE="phase",this.GROUP_BY_OPTIONS=[{label:"Status",value:this.GROUP_BY_STATUS_VALUE},{label:"Priority",value:this.GROUP_BY_PRIORITY_VALUE},{label:"Phase",value:this.GROUP_BY_PHASE_VALUE}],this.groups=[],this._templateId=null,this._cols=[],this._labels=[],this._statuses=[],this._priorities=[],this._phases=[],this.isSubtasksIncluded=!1}settemplateId(t){this._templateId=t}gettemplateId(){return this._templateId}setCurrentGroup(t){this._currentGroup=t}getCurrentGroup(){return this._currentGroup}emitColsChange(){this.colsSbj$.next()}emitOnContextMenu(t,n){this.contextMenuSbj$.next({event:t,task:n})}emitRefresh(){this.refreshSbj$.next()}emitGroupChange(t,n,o){this.groupChangeSbj$.next({groupId:t,taskId:n,color:o})}emitTaskAddOrDelete(t,n){this.taskAddOrDeleteSbj$.next({taskId:t,isSubTask:n})}emitRefreshSubtasksIncluded(){this.refreshSubtasksIncludedSbj$.next()}getGroupIdByGroupedColumn(t){const n=this.getCurrentGroup().value;return n===this.GROUP_BY_STATUS_VALUE?t.status:n===this.GROUP_BY_PRIORITY_VALUE?t.priority:n===this.GROUP_BY_PHASE_VALUE?t.phase_id:null}updateTaskGroup(t,n=!0){if(!t.id)return;const o=this.getGroupIdByGroupedColumn(t);o&&(this.deleteTask(t.id),this.addTask(t,o,n))}deleteTask(t,n=null){const o=this.map.getGroupId(t);if(!o||!t)return;const r=this.groups.find(d=>d.id===o);if(!r)return;const l=this.map.getSelectedTasks().find(d=>d.id===t);if(l?.is_sub_task){const d=r.tasks.find(_=>_.id===l.parent_task_id);if(d){const _=d.sub_tasks?.findIndex(y=>y.id===l.id);typeof _<"u"&&-1!==_&&(d.sub_tasks_count||(d.sub_tasks_count=0),d.sub_tasks_count=Math.max(+d.sub_tasks_count-1,0),d.sub_tasks?.splice(_,1),this.emitTaskAddOrDelete(d.id,!0))}this.map.remove(l)}else{const d=n??r.tasks.findIndex(_=>_.id===t);-1!==d&&(this.map.remove(r.tasks[d]),r.tasks.splice(d,1),this.emitTaskAddOrDelete(t,!1))}this.map.deselectAll()}removeSubtask(t,n=null){const o=this.map.getGroupId(t);if(!o||!t)return;const r=this.groups.find(d=>d.id===o);if(!r)return;const l=n??r.tasks.findIndex(d=>d.id===t);-1!==l&&(this.map.remove(r.tasks[l]),r.tasks.splice(l,1)),this.map.deselectAll()}addTask(t,n,o=!1){const r=this.groups.find(l=>l.id===n);if(r&&t.id){if(t.parent_task_id){const l=r.tasks.find(d=>d.id===t.parent_task_id);l&&(l.sub_tasks_count||(l.sub_tasks_count=0),l.sub_tasks_count=+l.sub_tasks_count+1,l.sub_tasks?.push(t))}else o?r.tasks.unshift(t):r.tasks.push(t);this.map.add(n,t),this.emitTaskAddOrDelete(t.parent_task_id,!!t.parent_task_id)}}reset(){this._cols=[],this._labels=[],this._statuses=[],this._priorities=[],this._templateId=null,this.groups=[],this.isSubtasksIncluded=!1}}return(i=a).\u0275fac=function(t){return new(t||i)(e.LFG(q.s),e.LFG(ue))},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})(),Qe=(()=>{var i;class a extends le.P{constructor(t){super(),this.http=t,this.root=`${this.API_BASE_URL}/pt-tasks`}getTaskList(t){const n=(0,b.UK)(t);return this._get(this.http,`${this.root}/list/${t.id}${n}`)}bulkDelete(t,n){return this._put(this.http,`${this.root}/bulk/delete`,t)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.LFG(ce.eN))},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})(),un=(()=>{var i;class a extends le.P{constructor(t){super(),this.http=t,this.root=`${this.API_BASE_URL}/labels`}get(t=null){const n=(0,b.UK)({project:t});return this._get(this.http,`${this.root}${n}`)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.LFG(ce.eN))},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})(),Ae=(()=>{var i;class a extends le.P{constructor(t){super(),this.http=t,this.root=`${this.API_BASE_URL}/pt-statuses`}create(t){return this._post(this.http,this.root,t)}get(t){return this._get(this.http,`${this.root}?template_id=${t}`)}getById(t){return this._get(this.http,`${this.root}/${t}`)}getCategories(){return this._get(this.http,`${this.root}/categories`)}update(t,n){return this._put(this.http,`${this.root}/${t}`,n)}updateName(t,n){return this._put(this.http,`${this.root}/name/${t}`,n)}delete(t,n,o){return this._delete(this.http,`${this.root}/${t}?project=${n}&replace=${o||null}`)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.LFG(ce.eN))},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})(),_n=(()=>{var i;class a extends le.P{constructor(t){super(),this.http=t,this.root=`${this.API_BASE_URL}/task-priorities`}get(){return this._get(this.http,this.root)}getById(t){return this._get(this.http,`${this.root}/${t}`)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.LFG(ce.eN))},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})(),Ie=(()=>{var i;class a extends le.P{constructor(t){super(),this.http=t,this.root=`${this.API_BASE_URL}/pt-task-phases`}create(t){const n=(0,b.UK)({id:t});return this._post(this.http,`${this.root}${n}`,{})}get(t){const n=(0,b.UK)({id:t});return this._get(this.http,`${this.root}${n}`)}getById(t,n){const o=(0,b.UK)({id:n});return this._get(this.http,`${this.root}/${t}${o}`)}updateLabel(t,n){return this._put(this.http,`${this.root}/label/${t}`,{name:n})}update(t,n){const o=(0,b.UK)({id:t});return this._put(this.http,`${this.root}/${n.id}${o}`,n)}updateColor(t,n){const o=(0,b.UK)({id:t,current_project_id:t});return this._put(this.http,`${this.root}/change-color/${n.id}${o}`,n)}delete(t,n){const o=(0,b.UK)({id:n});return this._delete(this.http,`${this.root}/${t}${o}`)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.LFG(ce.eN))},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})();const mn=["taskInput"];function hn(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"form",3)(1,"input",4,5),e.NdJ("blur",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.onInputBlur())})("keyup.enter",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.addInstantTask())}),e.qZA()()}if(2&i){const s=e.oxw();e.Q6J("formGroup",s.form),e.xp6(1),e.Q6J("nzBorderless",!0)("formControlName","name")("readOnly",s.creating)}}function gn(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"div",6),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.focusTaskInput())}),e._UZ(1,"span",7),e.TgZ(2,"span"),e._uU(3),e.qZA()()}if(2&i){const s=e.oxw();e.uIk("id",s.id),e.xp6(1),e.Q6J("nzType",s.creating?"loading":"plus")("nzTheme","outline"),e.xp6(2),e.hij(" ",s.label,"")}}let Re=(()=>{var i;class a{constructor(t,n,o,r,l,d,_){this.socket=t,this.auth=n,this.fb=o,this.service=r,this.ngZone=l,this.cdr=d,this.map=_,this.subTaskInput=!1,this.templateId=null,this.parentTask=null,this.groupId=null,this.label="Add Task",this.focusChange=new e.vpe,this.taskInputVisible=!1,this.creating=!1,this.id=(0,b.q$)(4),this._session=null,this.form=this.fb.group({name:[null,[p.kI.required,p.kI.pattern(/^(\s+\S+\s*)*(?!\s).*$/)]]}),this._session=this.auth.getCurrentSession()}focusTaskInput(){this.taskInputVisible=!0,this.focusChange.emit(this.taskInputVisible),this.ngZone.runOutsideAngular(()=>{setTimeout(()=>{this.taskInput?.nativeElement.focus(),this.taskInput?.nativeElement.select()},100)})}addTaskInputBlur(){this.taskInputVisible=!1,this.focusChange.emit(this.taskInputVisible)}onInputBlur(){var t=this;return(0,m.Z)(function*(){t.isValidInput()?yield t.addInstantTask():t.addTaskInputBlur()})()}createRequest(){if(!this.templateId||!this._session)return null;const t=this._session,n={name:this.form.value.name,template_id:this.templateId,reporter_id:t.id,team_id:t.team_id},o=this.service.getCurrentGroup();return o.value===this.service.GROUP_BY_STATUS_VALUE?n.status_id=this.groupId||void 0:o.value===this.service.GROUP_BY_PRIORITY_VALUE?n.priority_id=this.groupId||void 0:o.value===this.service.GROUP_BY_PHASE_VALUE&&(n.phase_id=this.groupId||void 0),this.parentTask&&(n.parent_task_id=this.parentTask),n}isValidInput(){return this.form.valid&&this.form.value.name.trim().length}addInstantTask(){var t=this;return(0,m.Z)(function*(){if(!t.creating&&t.templateId&&t._session&&t.isValidInput()){try{const n=t.createRequest();if(!n)return;t.creating=!0,t.socket.emit(k.C.PT_QUICK_TASK.toString(),JSON.stringify(n)),t.socket.once(k.C.PT_QUICK_TASK.toString(),o=>{t.creating=!1,t.onNewTaskReceived(o)})}catch{t.creating=!1}t.cdr.markForCheck()}})()}reset(t=!0){this.creating=!1,this.form.controls.name.setValue(null),this.taskInputVisible=!0,this.ngZone.runOutsideAngular(()=>{setTimeout(()=>{this.taskInput?.nativeElement.focus(),t&&window.scrollTo(0,document.body.scrollHeight)},j.GR)}),this.cdr.markForCheck()}onNewTaskReceived(t){if(this.groupId&&t.id){if(this.map.has(t.id))return;this.service.addTask(t,this.groupId),this.reset(!1)}}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(q.s),e.Y36(w.e),e.Y36(p.qu),e.Y36(B),e.Y36(e.R0b),e.Y36(e.sBO),e.Y36(ue))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-add-task-input"]],viewQuery:function(t,n){if(1&t&&e.Gf(mn,5),2&t){let o;e.iGM(o=e.CRH())&&(n.taskInput=o.first)}},inputs:{subTaskInput:"subTaskInput",templateId:"templateId",parentTask:"parentTask",groupId:"groupId",label:"label"},outputs:{focusChange:"focusChange"},standalone:!0,features:[e.jDz],decls:3,vars:3,consts:[[3,"ngSwitch"],[3,"formGroup",4,"ngSwitchCase"],["class","editable-row d-block w-25 task-name",3,"click",4,"ngSwitchCase"],[3,"formGroup"],["nz-input","","type","text","tabindex","1","placeholder","Type your task and hit enter",3,"nzBorderless","formControlName","readOnly","blur","keyup.enter"],["taskInput",""],[1,"editable-row","d-block","w-25","task-name",3,"click"],["nz-icon","",1,"input-icon",3,"nzType","nzTheme"]],template:function(t,n){1&t&&(e.ynx(0,0),e.YNc(1,hn,3,4,"form",1),e.YNc(2,gn,4,4,"div",2),e.BQk()),2&t&&(e.Q6J("ngSwitch",n.taskInputVisible),e.xp6(1),e.Q6J("ngSwitchCase",!0),e.xp6(1),e.Q6J("ngSwitchCase",!1))},dependencies:[h.ez,h.RF,h.n9,p.UX,p._Y,p.Fj,p.JJ,p.JL,p.sg,p.u,M.o7,M.Zp,P.PV,P.Ls],changeDetection:0}),a})();var fn=c(49278),Ze=c(63019),_e=c(76271);function zn(i,a){1&i&&e._UZ(0,"span",12),2&i&&e.Q6J("nzType","loading")("nzTheme","outline")}function Tn(i,a){if(1&i){const s=e.EpF();e.ynx(0),e.TgZ(1,"input",10),e.NdJ("ngModelChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.group.name=n)})("keydown.enter",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.updateName(n.group))})("blur",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.updateName(n.group))}),e.qZA(),e.YNc(2,zn,1,2,"span",11),e.BQk()}if(2&i){const s=e.oxw();e.xp6(1),e.ekj("bg-and-grey",!s.isEditColProgress),e.Q6J("id","group-name-"+s.group.id)("disabled",s.isEditColProgress)("ngModel",s.group.name),e.xp6(1),e.Q6J("ngIf",s.isEditColProgress)}}function Cn(i,a){if(1&i&&(e.ynx(0),e._uU(1),e.BQk()),2&i){const s=e.oxw();e.xp6(1),e.AsE(" ",s.group.name," (",s.group.tasks.length,") ")}}function kn(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",13),e.NdJ("nzVisibleChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.showMenu=n)}),e._UZ(1,"span",14),e.qZA()}if(2&i){e.oxw();const s=e.MAs(9);e.Q6J("nzType","text")("nzTrigger","click")("nzDropdownMenu",s),e.xp6(1),e.Q6J("nzType","ellipsis")("nzTheme","outline")}}function xn(i,a){if(1&i&&(e.TgZ(0,"span"),e._uU(1),e.qZA()),2&i){const s=e.oxw().$implicit,t=e.oxw(3);e.Udp("font-weight",s.id===t.group.category_id?"bold":null),e.xp6(1),e.hij(" ",(null==s?null:s.name)||null," ")}}function bn(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"li",21),e.NdJ("click",function(){const o=e.CHM(s).$implicit,r=e.oxw(3);return e.KtG(r.changeStatusCategory(r.group,o.id))}),e.ALo(1,"safeString"),e._UZ(2,"nz-badge",22),e.ALo(3,"safeString"),e.YNc(4,xn,2,3,"ng-template",null,23,e.W1O),e.qZA()}if(2&i){const s=a.$implicit,t=e.MAs(5);e.Q6J("nzTooltipTitle",e.lcZ(1,4,s.description))("nzTooltipPlacement","right"),e.xp6(2),e.Q6J("nzColor",e.lcZ(3,6,s.color_code))("nzText",t)}}function vn(i,a){if(1&i&&(e.TgZ(0,"li",19)(1,"ul"),e.YNc(2,bn,6,8,"li",20),e.qZA()()),2&i){const s=e.oxw(2),t=e.MAs(12);e.Q6J("nzTitle",t),e.xp6(2),e.Q6J("ngForOf",s.categories)}}function wn(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"ul",15)(1,"li",16),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.editGroupName())}),e._UZ(2,"span",17),e._uU(3," Rename "),e.qZA(),e.YNc(4,vn,3,2,"li",18),e.qZA()}if(2&i){const s=e.oxw();e.xp6(4),e.Q6J("ngIf",s.isGroupByStatus)}}function yn(i,a){1&i&&(e._UZ(0,"span",24),e._uU(1," Change category\n"))}let Sn=(()=>{var i;class a{constructor(t,n,o,r,l,d){this.cdr=t,this.auth=n,this.statusApi=o,this.list=r,this.ngZone=l,this.phaseApi=d,this.templateId=null,this.categories=[],this.toggle=new e.vpe,this.onCreateOrUpdate=new e.vpe,this.edit=!1,this.isEditColProgress=!1,this.showMenu=!1,this.isGroupByStatus=!1,this.isGroupByPhases=!1,this.isAdmin=!1,this.handleGroupProgressChange=()=>{this.group&&this.cdr.markForCheck()},(0,Ze.T)(this.list.onGroupChange$,this.list.onTaskAddOrDelete$).pipe((0,G.sL)()).subscribe(()=>{this.handleGroupProgressChange()})}ngOnInit(){this.isGroupByStatus=this.list.getCurrentGroup().value===this.list.GROUP_BY_STATUS_VALUE,this.isGroupByPhases=this.list.getCurrentGroup().value===this.list.GROUP_BY_PHASE_VALUE;const t=this.auth.getCurrentSession();t&&(this.isAdmin=!(!t.owner&&!t.is_admin))}canDisplayActions(){const t=this.list.getCurrentGroup().value;return t!==this.list.GROUP_BY_PRIORITY_VALUE&&(this.isAdmin||this.isGroupByStatus||t===this.list.GROUP_BY_PHASE_VALUE)&&this.group.name!==j.k9}changeStatusCategory(t,n){var o=this;return(0,m.Z)(function*(){n&&(t.category_id=n,yield o.onBlurEditColumn(t),o.list.emitRefresh())})()}editGroupName(){this.edit=!0,this.ngZone.runOutsideAngular(()=>{setTimeout(()=>{const n=document.querySelector(`#group-name-${this.group.id}`);n&&(n.focus(),n.select())})})}onToggleClick(t){this.edit||this.toggle.emit(t)}onBlurEditColumn(t){var n=this;return(0,m.Z)(function*(){if(n.templateId&&!n.isEditColProgress){try{n.isEditColProgress=!0;const o=n.list.getCurrentGroup().value;o===n.list.GROUP_BY_STATUS_VALUE?yield n.update(t):o===n.list.GROUP_BY_PHASE_VALUE&&(yield n.updatePhase(t)),n.isEditColProgress=!1,n.edit=!1}catch(o){(0,b.tu)(o),n.isEditColProgress=!1}n.cdr.markForCheck()}})()}updateName(t){var n=this;return(0,m.Z)(function*(){if(n.templateId&&!n.isEditColProgress){try{n.isEditColProgress=!0;const o=n.list.getCurrentGroup().value;o===n.list.GROUP_BY_STATUS_VALUE?yield n.updateGroupName(t):o===n.list.GROUP_BY_PHASE_VALUE&&(yield n.updatePhase(t)),n.isEditColProgress=!1,n.edit=!1}catch(o){(0,b.tu)(o),n.isEditColProgress=!1}n.cdr.markForCheck()}})()}updateGroupName(t){var n=this;return(0,m.Z)(function*(){if(t?.id&&n.templateId){try{const o={name:t.name,template_id:n.templateId,category_id:t.category_id},r=yield n.statusApi.updateName(t.id,o);if(r.done){const l=n.list.groups,d=l.find(_=>_.id===r.body.id);d&&(n.group.name=d.name=r.body.name||"",n.group.color_code=d.color_code=r.body.color_code||""),n.list.groups=l}}catch{}n.cdr.markForCheck()}})()}updatePhase(t){var n=this;return(0,m.Z)(function*(){if(t?.id&&n.templateId){try{const o={id:t.id,name:t.name},r=yield n.phaseApi.update(n.templateId,o);if(r.done){const l=n.list.phases,d=l.find(_=>_.id===r.body.id);d&&(n.group.name=d.name=r.body.name,n.group.color_code=d.color_code=r.body.color_code),n.list.phases=l}}catch{}n.cdr.markForCheck()}})()}update(t){var n=this;return(0,m.Z)(function*(){if(!n.isAdmin)return;const o={name:t.name,template_id:n.templateId,category_id:t.category_id},r=yield n.statusApi.update(t.id,o);r.done&&null!=r.body.color_code&&(t.color_code=r.body.color_code+j.Yj,n.onCreateOrUpdate.emit()),n.cdr.markForCheck()})()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(e.sBO),e.Y36(w.e),e.Y36(Ae),e.Y36(B),e.Y36(e.R0b),e.Y36(Ie))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-task-list-group-settings"]],inputs:{group:"group",templateId:"templateId",categories:"categories"},outputs:{toggle:"toggle",onCreateOrUpdate:"onCreateOrUpdate"},decls:13,vars:11,consts:[[1,"d-flex","justify-content-between","align-items-center","position-relative"],[1,"d-flex","align-items-center"],["nz-button","",1,"collapse","btn","border-0",3,"click"],["nz-icon","",1,"collapse-icon",3,"nzType","nzTheme"],[3,"ngSwitch"],[4,"ngSwitchCase"],["nz-button","","class","p-0","nz-dropdown","",3,"nzType","nzTrigger","nzDropdownMenu","nzVisibleChange",4,"ngIf"],["menu","nzDropdownMenu"],["nz-menu","",4,"ngIf"],["titleTemplate",""],["nz-input","",1,"p-0",3,"id","disabled","ngModel","ngModelChange","keydown.enter","blur"],["nz-icon","","class","ms-2",3,"nzType","nzTheme",4,"ngIf"],["nz-icon","",1,"ms-2",3,"nzType","nzTheme"],["nz-button","","nz-dropdown","",1,"p-0",3,"nzType","nzTrigger","nzDropdownMenu","nzVisibleChange"],["nz-icon","",3,"nzType","nzTheme"],["nz-menu",""],["nz-menu-item","",3,"click"],["nz-icon","","nzType","edit","nzTheme","outline",1,"me-2"],["nz-submenu","",3,"nzTitle",4,"ngIf"],["nz-submenu","",3,"nzTitle"],["class","m-0","nz-tooltip","","nz-menu-item","",3,"nzTooltipTitle","nzTooltipPlacement","click",4,"ngFor","ngForOf"],["nz-tooltip","","nz-menu-item","",1,"m-0",3,"nzTooltipTitle","nzTooltipPlacement","click"],[3,"nzColor","nzText"],["textTmpl",""],["nz-icon","","nzType","retweet","nzTheme","outline",1,"me-2"]],template:function(t,n){1&t&&(e.TgZ(0,"div",0)(1,"div",1)(2,"button",2),e.NdJ("click",function(r){return n.onToggleClick(r)}),e._UZ(3,"span",3),e.ynx(4,4),e.YNc(5,Tn,3,6,"ng-container",5),e.YNc(6,Cn,2,2,"ng-container",5),e.BQk(),e.qZA(),e.YNc(7,kn,2,5,"button",6),e.qZA()(),e.TgZ(8,"nz-dropdown-menu",null,7),e.YNc(10,wn,5,1,"ul",8),e.qZA(),e.YNc(11,yn,2,0,"ng-template",null,9,e.W1O)),2&t&&(e.xp6(2),e.Udp("background-color",n.group.color_code),e.ekj("active",n.group.tasks.length),e.xp6(1),e.Q6J("nzType","right")("nzTheme","outline"),e.xp6(1),e.Q6J("ngSwitch",n.edit),e.xp6(1),e.Q6J("ngSwitchCase",!0),e.xp6(1),e.Q6J("ngSwitchCase",!1),e.xp6(1),e.Q6J("ngIf",n.canDisplayActions()),e.xp6(3),e.Q6J("ngIf",n.showMenu))},dependencies:[h.sg,h.O5,h.RF,h.n9,P.Ls,p.Fj,p.JJ,M.Zp,O.ix,v.w,L.dQ,T.wO,T.r9,T.rY,J.SY,p.On,ie.x7,E.cm,E.RR,E.wA,_e.m],styles:[".collapse[_ngcontent-%COMP%]{color:hwb(0 0% 100%/.85);font-weight:500;padding:6px 13px 6px 12px;min-width:120px;width:auto;border:none;text-align:left;outline:none;border-top-right-radius:4px;border-top-left-radius:4px;-webkit-user-select:none;user-select:none;z-index:8;font-size:14px;height:30px;display:flex;align-items:center}.collapse.btn[_ngcontent-%COMP%] .collapse-icon[_ngcontent-%COMP%]{transition:transform .1s;transform:rotate(0)}.collapse.btn.active[_ngcontent-%COMP%] .collapse-icon[_ngcontent-%COMP%]{transition:transform .1s;transform:rotate(90deg)}.collapse.active[_ngcontent-%COMP%]{border-bottom-left-radius:0;border-bottom-right-radius:0}.collapse[_ngcontent-%COMP%]:after{color:#777;font-weight:700;float:left;margin-left:5px}"],changeDetection:0}),a})();var Be=c(48327),we=c(62612),Le=c(44889);function Mn(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",13),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.onPhaseSettingsClick())}),e._UZ(1,"span",14),e.qZA()}2&i&&(e.Q6J("nzShape","circle")("nzSize","small")("nzType","text"),e.xp6(1),e.Q6J("nzType","setting")("nzTheme","outline"))}let Pn=(()=>{var i;class a{get phaseLabel(){return this.phasesService.label}constructor(t,n,o,r,l){this.service=t,this.map=n,this.cdr=o,this.phasesService=r,this.auth=l,this.headerCls="flex-table header",this.selectChange=new e.vpe,this.phaseSettingsClick=new e.vpe,this.checked=!1,this.indeterminate=!1,this.map.onDeselectAll$.pipe((0,G.sL)()).subscribe(()=>{this.checked=!1,this.indeterminate=!1,this.cdr.markForCheck()}),this.phasesService.onLabelChange.pipe((0,G.sL)()).subscribe(d=>{this.cdr.markForCheck()}),(0,Ze.T)(this.map.onSelect$,this.map.onDeselect$).pipe((0,G.sL)()).subscribe(()=>{this.map.isAllDeselected(this.groupId)?(this.checked=!1,this.indeterminate=!1):this.map.isAllSelected(this.groupId)?(this.checked=!0,this.indeterminate=!1):this.indeterminate=!0,this.cdr.markForCheck()})}onAllChecked(t){this.selectChange?.emit(t)}onPhaseSettingsClick(){this.phaseSettingsClick.emit()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(B),e.Y36(ue),e.Y36(e.sBO),e.Y36(Be.u),e.Y36(w.e))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-task-list-header"]],hostVars:2,hostBindings:function(t,n){2&t&&e.Tol(n.headerCls)},inputs:{groupId:"groupId"},outputs:{selectChange:"selectChange",phaseSettingsClick:"phaseSettingsClick"},decls:27,vars:8,consts:[[1,"flex-row","task-drag-handler"],[1,"flex-row","task-check"],["nz-checkbox","",3,"nzChecked","nzIndeterminate","nzCheckedChange"],[1,"flex-row","task-arrow"],[1,"flex-row","task-name"],[1,"flex-row","task-description"],[1,"flex-row","task-labels"],[1,"flex-row","task-phase","d-flex","justify-content-between","align-items-center"],["nz-tooltip","",3,"nzTooltipTitle"],["nz-button","",3,"nzShape","nzSize","nzType","click",4,"ngIf"],[1,"flex-row","task-status"],[1,"flex-row","task-priority"],[1,"flex-row","task-estimation","justify-content-center"],["nz-button","",3,"nzShape","nzSize","nzType","click"],["nz-icon","",3,"nzType","nzTheme"]],template:function(t,n){1&t&&(e._UZ(0,"div",0),e.TgZ(1,"div",1)(2,"span",2),e.NdJ("nzCheckedChange",function(r){return n.checked=r})("nzCheckedChange",function(r){return n.onAllChecked(r)}),e.qZA()(),e._UZ(3,"div",3),e.TgZ(4,"div",4),e._uU(5,"Task"),e.qZA(),e.ynx(6),e.TgZ(7,"div",5),e._uU(8,"Description"),e.qZA(),e.BQk(),e.ynx(9),e.TgZ(10,"div",6),e._uU(11,"Labels"),e.qZA(),e.BQk(),e.ynx(12),e.TgZ(13,"div",7)(14,"span",8),e._uU(15),e.ALo(16,"ellipsis"),e.qZA(),e.YNc(17,Mn,2,5,"button",9),e.qZA(),e.BQk(),e.ynx(18),e.TgZ(19,"div",10),e._uU(20,"Status"),e.qZA(),e.BQk(),e.ynx(21),e.TgZ(22,"div",11),e._uU(23,"Priority"),e.qZA(),e.BQk(),e.ynx(24),e.TgZ(25,"div",12),e._uU(26,"Estimation"),e.qZA(),e.BQk()),2&t&&(e.xp6(2),e.Q6J("nzChecked",n.checked)("nzIndeterminate",n.indeterminate),e.xp6(12),e.Q6J("nzTooltipTitle",n.phaseLabel),e.xp6(1),e.Oqu(e.xi3(16,5,n.phaseLabel,10)),e.xp6(2),e.Q6J("ngIf",n.auth.isOwnerOrAdmin()))},dependencies:[h.O5,P.Ls,O.ix,v.w,L.dQ,J.SY,we.Ie,Le.p],styles:[".flex-row[_ngcontent-%COMP%]{padding:4px 11px;background-color:#fafafa;border-top:1px solid #f0f0f0;border-bottom:1px solid #f0f0f0;border-right:1px solid #f0f0f0;display:flex;align-items:center;flex-direction:row}.task-drag-handler[_ngcontent-%COMP%]{padding:0 0 0 4px!important;width:24px;border-bottom:1px solid #f0f0f0;border-right:none!important;position:sticky;left:0;z-index:1}.task-check[_ngcontent-%COMP%]{text-align:center;padding:8px 6px 8px 0!important;position:sticky;left:24px;z-index:1}.task-arrow[_ngcontent-%COMP%]{width:24px;padding:0!important;display:flex;align-items:center;position:sticky;border-right:0;left:47px;z-index:1}.task-name[_ngcontent-%COMP%]{width:450px;min-width:450px;position:sticky;left:71px;z-index:1}.task-name[_ngcontent-%COMP%] nz-filter-trigger[_ngcontent-%COMP%]{margin-left:auto}.task-name.left-0[_ngcontent-%COMP%]{left:47px}.task-description[_ngcontent-%COMP%]{width:225px}.task-labels[_ngcontent-%COMP%]{width:220px}.task-status[_ngcontent-%COMP%]{width:120px}.task-phase[_ngcontent-%COMP%]{width:150px}.task-priority[_ngcontent-%COMP%], .task-estimation[_ngcontent-%COMP%]{width:120px}.task-start-date[_ngcontent-%COMP%], .task-due-date[_ngcontent-%COMP%]{width:150px}"],changeDetection:0}),a})();var W=c(32181),He=c(42753),$e=c(6192);const On=["descriptionInput"],An=["descriptionEditor"];function In(i,a){if(1&i&&e._UZ(0,"span",8),2&i){const s=e.oxw();e.Q6J("innerHTML",s.task.description,e.oJD)}}function Zn(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"div",9)(1,"nz-form-item",10)(2,"nz-form-control",11)(3,"editor",12,13),e.NdJ("ngModelChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.task.description=n)})("onBlur",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.submit())}),e.qZA()()()()}if(2&i){const s=e.oxw();e.xp6(1),e.Udp("width","550px"),e.xp6(2),e.ekj("editing",s.isEditing),e.Q6J("init",s.CONFIG)("ngModel",s.task.description)}}let Ln=(()=>{var i;class a{constructor(t,n,o,r){this.cdr=t,this.socket=n,this.service=o,this.ngZone=r,this.task={},this.cls="flex-row task-description p-0",this.show=!1,this.loading=!1,this.CONFIG={base_url:"/tinymce",suffix:".min",plugins:"lists link code wordcount",toolbar:"blocks bold italic underline strikethrough | checklist numlist bullist link | alignleft aligncenter alignright alignjustify",menubar:!1,content_css:"/assets/css/prebuilt-editor.css",statusbar:!0,branding:!1,height:200,min_height:100},this.isEditing=!1,this.handleResponse=l=>{this.task.id===l?.id&&(this.task.description=l.description,this.closeDropdown(),this.cdr.markForCheck())}}ngOnInit(){this.socket.on(k.C.PT_TASK_DESCRIPTION_CHANGE.toString(),this.handleResponse)}ngOnDestroy(){this.socket.removeListener(k.C.PT_TASK_DESCRIPTION_CHANGE.toString(),this.handleResponse)}handleVisibleChange(t,n){this.show=t,t?n.classList.add(this.service.HIGHLIGHT_COL_CLS):n.classList.remove(this.service.HIGHLIGHT_COL_CLS)}submit(){this.socket.emit(k.C.PT_TASK_DESCRIPTION_CHANGE.toString(),JSON.stringify({task_id:this.task.id,description:this.task.description}))}closeDropdown(){this.ngZone.runOutsideAngular(()=>{document.body.click()})}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(e.sBO),e.Y36(q.s),e.Y36(B),e.Y36(e.R0b))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-task-description"]],viewQuery:function(t,n){if(1&t&&(e.Gf(On,5),e.Gf(An,5)),2&t){let o;e.iGM(o=e.CRH())&&(n.descriptionInput=o.first),e.iGM(o=e.CRH())&&(n.descriptionEditor=o.first)}},hostVars:2,hostBindings:function(t,n){2&t&&e.Tol(n.cls)},inputs:{task:"task"},decls:13,vars:11,consts:[["nz-dropdown","",1,"editable","w-100","h-100","d-flex","align-items-center","ps-2","pe-0","label-tag-container",3,"nzOverlayClassName","nzTrigger","nzClickHide","nzDropdownMenu","nzVisibleChange"],["element",""],["nz-tooltip","",3,"innerHTML","nzTooltipTitle"],["descriptionTooltip",""],["descriptionDropdown","nzDropdownMenu"],["class","bg-white px-0 py-0",4,"ngIf"],[1,"bg-white","d-flex","justify-content-end","p-2","border-top"],["nz-button","","nzType","primary","nzSize","small",3,"click"],["nz-typography","",1,"text-white",3,"innerHTML"],[1,"bg-white","px-0","py-0"],["nz-row","",1,"task-description-editor-list","mb-0"],[1,"description-hover","position-relative"],[1,"description-editor-list",3,"init","ngModel","ngModelChange","onBlur"],["descriptionEditor",""]],template:function(t,n){if(1&t){const o=e.EpF();e.TgZ(0,"div",0,1),e.NdJ("nzVisibleChange",function(l){e.CHM(o);const d=e.MAs(1);return e.KtG(n.handleVisibleChange(l,d))}),e.ynx(2),e._UZ(3,"span",2),e.ALo(4,"nzEllipsis"),e.BQk(),e.qZA(),e.YNc(5,In,1,1,"ng-template",null,3,e.W1O),e.TgZ(7,"nz-dropdown-menu",null,4),e.YNc(9,Zn,5,6,"div",5),e.TgZ(10,"div",6)(11,"button",7),e.NdJ("click",function(){return n.submit()}),e._uU(12,"OK"),e.qZA()()()}if(2&t){const o=e.MAs(6),r=e.MAs(8);e.Q6J("nzOverlayClassName","custom-shadow")("nzTrigger","click")("nzClickHide",!1)("nzDropdownMenu",r),e.xp6(3),e.Q6J("innerHTML",e.Dn7(4,7,n.task.description,55,"..."),e.oJD)("nzTooltipTitle",o),e.xp6(6),e.Q6J("ngIf",n.show)}},dependencies:[h.O5,p.JJ,Z.t3,Z.SK,x.Nx,x.Fd,O.ix,v.w,L.dQ,Q.ZU,J.SY,p.On,E.cm,E.RR,He.PG,$e.N7],styles:["[_nghost-%COMP%]{display:block;max-width:640px}.description-editor-placeholder[_ngcontent-%COMP%]{pointer-events:none;-webkit-user-select:none;user-select:none;position:absolute;inset:0;display:flex;align-items:center}.description-editor-preview[_ngcontent-%COMP%]{position:relative;min-height:32px}.description-editor-preview.empty[_ngcontent-%COMP%]{display:flex;align-items:center;padding:10px}.task-description-editor[_ngcontent-%COMP%]{padding-left:12px;padding-right:12px;padding-bottom:12px;margin-left:-12px;margin-right:-12px;margin-bottom:-12px}"],changeDetection:0}),a})();var Fn=c(86211);let Nn=(()=>{var i;class a{transform(t,n){return t&&t.length>n?t:""}}return(i=a).\u0275fac=function(t){return new(t||i)},i.\u0275pipe=e.Yjl({name:"ellipsisTooltipTitlePT",type:i,pure:!0}),a})(),En=(()=>{var i;class a{transform(t,...n){return!(!t.end||!t.names)}}return(i=a).\u0275fac=function(t){return new(t||i)},i.\u0275pipe=e.Yjl({name:"endNameCheckPT",type:i,pure:!0}),a})();const Un=["labelsSearchInput"];function Jn(i,a){if(1&i&&(e.TgZ(0,"nz-tag",14),e.ALo(1,"withAlpha"),e.ALo(2,"safeString"),e.TgZ(3,"span"),e._uU(4),e.ALo(5,"ellipsis"),e.qZA()()),2&i){const s=e.oxw().$implicit;e.Q6J("nzColor",e.lcZ(1,3,s.color_code))("nzTooltipTitle",e.lcZ(2,5,s.names)),e.xp6(4),e.Oqu(e.xi3(5,7,s.name,10))}}function Gn(i,a){if(1&i&&(e.TgZ(0,"nz-tag",15),e.ALo(1,"ellipsisTooltipTitlePT"),e.ALo(2,"withAlpha"),e.TgZ(3,"span"),e._uU(4),e.ALo(5,"ellipsis"),e.qZA()()),2&i){const s=e.oxw().$implicit;e.Q6J("nzTooltipTitle",e.xi3(1,3,s.name,5))("nzColor",e.lcZ(2,6,s.color_code)),e.xp6(4),e.Oqu(e.xi3(5,8,s.name,10))}}function Dn(i,a){if(1&i&&(e.ynx(0)(1,11),e.ALo(2,"endNameCheckPT"),e.YNc(3,Jn,6,10,"nz-tag",12),e.YNc(4,Gn,6,11,"nz-tag",13),e.BQk()()),2&i){const s=a.$implicit;e.xp6(1),e.Q6J("ngSwitch",e.lcZ(2,3,s)),e.xp6(2),e.Q6J("ngSwitchCase",!0),e.xp6(1),e.Q6J("ngSwitchCase",!1)}}function Yn(i,a){1&i&&(e.TgZ(0,"span",20),e._uU(1," Hit enter to create! "),e.qZA())}function Qn(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"div",16)(1,"input",17,18),e.NdJ("ngModelChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.searchText=n)})("keydown.enter",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.createLabel())}),e.qZA(),e.YNc(3,Yn,2,0,"span",19),e.qZA()}if(2&i){const s=e.oxw();e.ekj("border-bottom",s.hasFilteredLabel),e.xp6(1),e.Q6J("ngModel",s.searchText),e.xp6(2),e.Q6J("ngIf",!s.hasFilteredLabel)}}function Rn(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"li",23),e.NdJ("nzCheckedChange",function(n){const r=e.CHM(s).$implicit;return e.KtG(r.selected=n)})("nzCheckedChange",function(){const o=e.CHM(s).$implicit,r=e.oxw(2);return e.KtG(r.handleLabelChange(o))}),e._UZ(1,"nz-badge",24),e.qZA()}if(2&i){const s=a.$implicit;e.Q6J("nzChecked",s.selected),e.xp6(1),e.Q6J("nzColor",s.color_code)("nzText",(null==s?null:s.name)||null)}}function Bn(i,a){if(1&i&&(e.TgZ(0,"ul",21),e.YNc(1,Rn,2,3,"li",22),e.qZA()),2&i){const s=e.oxw();e.xp6(1),e.Q6J("ngForOf",s.filteredLabels)("ngForTrackBy",s.trackById)}}let Hn=(()=>{var i;class a{get hasFilteredLabel(){return!!this.filteredLabels.length}get filteredLabels(){return this.searchPipe.transform(this.labels,this.searchText)}constructor(t,n,o,r,l,d,_){this.cdr=t,this.searchPipe=n,this.auth=o,this.socket=r,this.utils=l,this.ngZone=d,this.service=_,this.task={},this.cls="flex-row task-labels",this.alpha=j.Yj,this.searchText=null,this.labels=[],this.show=!1,this.handleLabelsChange=y=>{if(y&&y.id===this.task.id){if(this.task.labels=y.labels,this.task.all_labels=y.all_labels,y.new_label)if(y.is_new){const S=[...this.service.labels];S.push(y.new_label),this.service.labels=[...S]}else{const S=this.labels.find(be=>be.id===y.new_label.id);S&&(S.selected=!0)}this.cdr.markForCheck()}},this.service.onLabelsChange$.pipe((0,G.sL)()).subscribe(()=>{this.updateLabels(),this.cdr.markForCheck()})}ngOnInit(){this.updateLabels(),this.socket.on(k.C.PT_TASK_LABELS_CHANGE.toString(),this.handleLabelsChange),this.socket.on(k.C.PT_CREATE_LABEL.toString(),this.handleLabelsChange)}ngOnDestroy(){this.labels=[],this.socket.removeListener(k.C.PT_TASK_LABELS_CHANGE.toString(),this.handleLabelsChange),this.socket.removeListener(k.C.PT_CREATE_LABEL.toString(),this.handleLabelsChange)}updateLabels(){this.labels=this.service.labels}trackById(t,n){return n.id}sortBySelected(t){this.utils.sortBySelection(t)}handleLabelsVisibleChange(t,n){if(this.show=t,t?n.classList.add(this.service.HIGHLIGHT_COL_CLS):n.classList.remove(this.service.HIGHLIGHT_COL_CLS),t){const o=this.task.all_labels?.map(r=>r.id)??[];for(const r of this.labels)r.selected=o.includes(r.id);this.focusLabelsSearchInput()}else{this.searchText=null;for(const o of this.labels)o.selected=!1}this.sortBySelected(this.labels)}focusLabelsSearchInput(){this.ngZone.runOutsideAngular(()=>{setTimeout(()=>{this.labelsSearchInput?.nativeElement?.focus()},100)})}handleLabelChange(t){this.socket.emit(k.C.PT_TASK_LABELS_CHANGE.toString(),JSON.stringify({task_id:this.task.id,label_id:t.id,parent_task:this.task.parent_task_id})),this.sortBySelected(this.labels)}createLabel(){if(this.hasFilteredLabel||!this.searchText)return;const t=this.auth.getCurrentSession();this.socket.emit(k.C.PT_CREATE_LABEL.toString(),JSON.stringify({task_id:this.task.id,label:this.searchText.trim(),team_id:t?.team_id,parent_task:this.task.parent_task_id})),this.searchText=null,this.cdr.detectChanges()}closeDropdown(){this.ngZone.runOutsideAngular(()=>{document.body.click()})}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(e.sBO),e.Y36(Oe.g),e.Y36(w.e),e.Y36(q.s),e.Y36(de.F),e.Y36(e.R0b),e.Y36(B))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-task-labels"]],viewQuery:function(t,n){if(1&t&&e.Gf(Un,5),2&t){let o;e.iGM(o=e.CRH())&&(n.labelsSearchInput=o.first)}},hostVars:2,hostBindings:function(t,n){2&t&&e.Tol(n.cls)},inputs:{task:"task"},decls:13,vars:11,consts:[["nz-dropdown","",1,"editable","w-100","h-100","d-flex","align-items-center","ps-2","pe-0","label-tag-container",3,"nzOverlayClassName","nzTrigger","nzClickHide","nzDropdownMenu","nzVisibleChange"],["element",""],[4,"ngFor","ngForOf","ngForTrackBy"],[1,"text-dark","avatar-dashed","empty-label","task-list-label"],["nz-typography","",3,"nzType"],["nz-icon","",3,"nzType","nzTheme"],["labelsDropdown","nzDropdownMenu"],["class","bg-white px-3 py-2",3,"border-bottom",4,"ngIf"],["style","box-shadow: none;","class","dropdown-ul","nz-menu","",4,"ngIf"],[1,"bg-white","d-flex","justify-content-end","p-2","border-top"],["nz-button","","nzType","primary","nzSize","small",3,"click"],[3,"ngSwitch"],["nz-tooltip","","class","text-dark task-list-label",3,"nzColor","nzTooltipTitle",4,"ngSwitchCase"],["class","text-dark task-list-label","nz-tooltip","",3,"nzTooltipTitle","nzColor",4,"ngSwitchCase"],["nz-tooltip","",1,"text-dark","task-list-label",3,"nzColor","nzTooltipTitle"],["nz-tooltip","",1,"text-dark","task-list-label",3,"nzTooltipTitle","nzColor"],[1,"bg-white","px-3","py-2"],["type","text","placeholder","Search or create","nz-input","",3,"ngModel","ngModelChange","keydown.enter"],["labelsSearchInput",""],["nz-typography","","nzType","secondary",4,"ngIf"],["nz-typography","","nzType","secondary"],["nz-menu","",1,"dropdown-ul",2,"box-shadow","none"],["nz-checkbox","","nz-menu-item","","class","m-0",3,"nzChecked","nzCheckedChange",4,"ngFor","ngForOf","ngForTrackBy"],["nz-checkbox","","nz-menu-item","",1,"m-0",3,"nzChecked","nzCheckedChange"],[3,"nzColor","nzText"]],template:function(t,n){if(1&t){const o=e.EpF();e.TgZ(0,"div",0,1),e.NdJ("nzVisibleChange",function(l){e.CHM(o);const d=e.MAs(1);return e.KtG(n.handleLabelsVisibleChange(l,d))}),e.YNc(2,Dn,5,5,"ng-container",2),e.TgZ(3,"nz-tag",3)(4,"span",4),e._UZ(5,"span",5),e.qZA()()(),e.TgZ(6,"nz-dropdown-menu",null,6),e.YNc(8,Qn,4,4,"div",7),e.YNc(9,Bn,2,2,"ul",8),e.TgZ(10,"div",9)(11,"button",10),e.NdJ("click",function(){return n.closeDropdown()}),e._uU(12,"OK"),e.qZA()()()}if(2&t){const o=e.MAs(7);e.Q6J("nzOverlayClassName","custom-shadow")("nzTrigger","click")("nzClickHide",!1)("nzDropdownMenu",o),e.xp6(2),e.Q6J("ngForOf",n.task.labels)("ngForTrackBy",n.trackById),e.xp6(2),e.Q6J("nzType","secondary"),e.xp6(1),e.Q6J("nzType","plus")("nzTheme","outline"),e.xp6(3),e.Q6J("ngIf",n.show),e.xp6(1),e.Q6J("ngIf",n.show)}},dependencies:[h.sg,h.O5,h.RF,h.n9,P.Ls,p.Fj,p.JJ,M.Zp,O.ix,v.w,L.dQ,Q.ZU,T.wO,T.r9,J.SY,p.On,ie.x7,E.cm,E.RR,ze.j,we.Ie,_e.m,Le.p,Fn.M,Nn,En],styles:[".dropdown-ul[_ngcontent-%COMP%]{max-height:250px;overflow:hidden;overflow-y:auto}.label-tag-container[_ngcontent-%COMP%]{max-width:220px;overflow:hidden;flex-wrap:wrap;padding-top:8px;padding-bottom:8px;padding-right:0}nz-tag[_ngcontent-%COMP%]{display:block;overflow:hidden;white-space:break-spaces;margin-left:3px;margin-right:3px;line-height:16px;padding-left:3px;padding-right:3px}.empty-label[_ngcontent-%COMP%]{padding:2px 8px 3px}.empty-label[_ngcontent-%COMP%] .ant-typography[_ngcontent-%COMP%]{display:flex}"],changeDetection:0}),a})(),$n=(()=>{var i;class a{transform(t,n=0){return t?t.length>n?`${t.slice(0,n)}...`:t:""}}return(i=a).\u0275fac=function(t){return new(t||i)},i.\u0275pipe=e.Yjl({name:"truncateIfLongPT",type:i,pure:!0}),a})();function jn(i,a){if(1&i&&(e.TgZ(0,"nz-option",3),e.ALo(1,"truncateIfLongPT"),e._UZ(2,"nz-badge",4),e.ALo(3,"safeString"),e.qZA()),2&i){const s=a.$implicit;e.Q6J("nzValue",s.id)("nzLabel",e.xi3(1,4,s.name,12)),e.xp6(2),e.Q6J("nzColor",e.lcZ(3,7,s.color_code))("nzText",(null==s?null:s.name)||null)}}let qn=(()=>{var i;class a{constructor(t,n,o,r,l,d){this.service=t,this.socket=n,this.cdr=o,this.ngZone=r,this.element=l,this.renderer=d,this.task={},this.cls="flex-row task-phase",this.PHASE_COLOR="#a9a9a9"+j.Yj,this.PLACEHOLDER_COLOR="rgba(0, 0, 0, 0.85) !important",this.phases=[],this.loading=!1,this.handleResponse=_=>{_&&_.task_id===this.task.id&&(this.task.phase_color=_.color_code||void 0,this.task.phase_id=_.id,this.isGroupByPhase()&&(this.task.is_sub_task||this.service.updateTaskGroup(this.task,!1),this.service.isSubtasksIncluded&&this.service.emitRefreshSubtasksIncluded()),this.cdr.markForCheck())},this.service.onPhaseChange$.pipe((0,G.sL)()).subscribe(()=>{this.updatePhases(),this.cdr.markForCheck()}),this.service.onGroupChange$.pipe((0,W.h)(_=>_.taskId===this.task.id),(0,W.h)(()=>this.isGroupByPhase()),(0,G.sL)()).subscribe(_=>{"Unmapped"===_.groupId&&(_.color=""),this.task.phase_id=_.groupId,this.task.phase_color=_.color,this.cdr.markForCheck()})}ngOnInit(){this.updatePhases(),this.socket.on(k.C.PT_TASK_PHASE_CHANGE.toString(),this.handleResponse)}ngOnDestroy(){this.socket.removeListener(k.C.PT_TASK_PHASE_CHANGE.toString(),this.handleResponse)}isGroupByPhase(){return this.service.getCurrentGroup().value===this.service.GROUP_BY_PHASE_VALUE}trackById(t,n){return n.id}handleChange(t,n){n&&this.socket.emit(k.C.PT_TASK_PHASE_CHANGE.toString(),JSON.stringify({task_id:n,phase_id:t}))}updatePhases(){this.phases=this.service.phases}toggleHighlightCls(t,n){this.ngZone.runOutsideAngular(()=>{t?this.renderer.addClass(n,this.service.HIGHLIGHT_COL_CLS):this.renderer.removeClass(n,this.service.HIGHLIGHT_COL_CLS)})}handleOpen(t){this.toggleHighlightCls(t,this.element.nativeElement)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(B),e.Y36(q.s),e.Y36(e.sBO),e.Y36(e.R0b),e.Y36(e.SBq),e.Y36(e.Qsj))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-task-phase"]],hostVars:2,hostBindings:function(t,n){2&t&&e.Tol(n.cls)},inputs:{task:"task"},decls:3,vars:9,consts:[[1,"py-0"],["nzAllowClear","",1,"rounded-pill","custom-select",3,"ngModel","nzLoading","nzDropdownMatchSelectWidth","nzSize","nzPlaceHolder","ngModelChange","nzOpenChange"],["nzCustomContent","",3,"nzValue","nzLabel",4,"ngFor","ngForOf","ngForTrackBy"],["nzCustomContent","",3,"nzValue","nzLabel"],[3,"nzColor","nzText"]],template:function(t,n){1&t&&(e.TgZ(0,"div",0)(1,"nz-select",1),e.NdJ("ngModelChange",function(r){return n.task.phase_id=r})("ngModelChange",function(r){return n.handleChange(r,n.task.id)})("nzOpenChange",function(r){return n.handleOpen(r)}),e.YNc(2,jn,4,9,"nz-option",2),e.qZA()()),2&t&&(e.xp6(1),e.Udp("background-color",n.task.phase_color),e.Q6J("ngModel",n.task.phase_id)("nzLoading",n.loading)("nzDropdownMatchSelectWidth",!1)("nzSize","small")("nzPlaceHolder","Select"),e.xp6(1),e.Q6J("ngForOf",n.phases)("ngForTrackBy",n.trackById))},dependencies:[h.sg,p.JJ,K.Ip,K.Vq,p.On,ie.x7,_e.m,$n],changeDetection:0}),a})();function Vn(i,a){if(1&i&&(e.TgZ(0,"nz-option",3),e.ALo(1,"safeString"),e._UZ(2,"nz-badge",4),e.ALo(3,"safeString"),e.qZA()),2&i){const s=a.$implicit;e.Q6J("nzValue",s.id)("nzLabel",e.lcZ(1,4,s.name)),e.xp6(2),e.Q6J("nzColor",e.lcZ(3,6,s.color_code))("nzText",(null==s?null:s.name)||null)}}let Kn=(()=>{var i;class a{constructor(t,n,o,r,l,d){this.service=t,this.socket=n,this.cdr=o,this.ngZone=r,this.element=l,this.renderer=d,this.task={},this.cls="flex-row task-status",this.statuses=[],this.loading=!1,this.handleResponse=_=>{_&&_.id===this.task.id&&(this.task.status_color=_.color_code,this.task.status=_.status_id,this.task.status_category=_.statusCategory,this.isGroupByStatus()&&(this.task.is_sub_task||this.service.updateTaskGroup(this.task,!1),this.service.isSubtasksIncluded&&this.service.emitRefreshSubtasksIncluded()),this.cdr.markForCheck())},this.service.onStatusesChange$.pipe((0,G.sL)()).subscribe(()=>{this.updateStatuses(),this.cdr.markForCheck()}),this.service.onGroupChange$.pipe((0,W.h)(_=>_.taskId===this.task.id),(0,W.h)(()=>this.isGroupByStatus()),(0,G.sL)()).subscribe(_=>{this.task.status=_.groupId,this.task.status_color=_.color,this.cdr.markForCheck()})}ngOnInit(){this.updateStatuses(),this.socket.on(k.C.PT_TASK_STATUS_CHANGE.toString(),this.handleResponse)}ngOnDestroy(){this.socket.removeListener(k.C.PT_TASK_STATUS_CHANGE.toString(),this.handleResponse)}isGroupByStatus(){return this.service.getCurrentGroup().value===this.service.GROUP_BY_STATUS_VALUE}trackById(t,n){return n.id}handleStatusChange(t,n){n&&this.socket.emit(k.C.PT_TASK_STATUS_CHANGE.toString(),JSON.stringify({task_id:n,status_id:t,parent_task:this.task.parent_task_id}))}updateStatuses(){this.statuses=this.service.statuses}toggleHighlightCls(t,n){this.ngZone.runOutsideAngular(()=>{t?this.renderer.addClass(n,this.service.HIGHLIGHT_COL_CLS):this.renderer.removeClass(n,this.service.HIGHLIGHT_COL_CLS)})}handleOpen(t){this.toggleHighlightCls(t,this.element.nativeElement)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(B),e.Y36(q.s),e.Y36(e.sBO),e.Y36(e.R0b),e.Y36(e.SBq),e.Y36(e.Qsj))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-task-status"]],hostVars:2,hostBindings:function(t,n){2&t&&e.Tol(n.cls)},inputs:{task:"task"},decls:3,vars:8,consts:[[1,"py-0"],[1,"rounded-pill","custom-select",3,"ngModel","nzLoading","nzDropdownMatchSelectWidth","nzSize","ngModelChange","nzOpenChange"],["nzCustomContent","",3,"nzValue","nzLabel",4,"ngFor","ngForOf","ngForTrackBy"],["nzCustomContent","",3,"nzValue","nzLabel"],[3,"nzColor","nzText"]],template:function(t,n){1&t&&(e.TgZ(0,"div",0)(1,"nz-select",1),e.NdJ("ngModelChange",function(r){return n.task.status=r})("ngModelChange",function(r){return n.handleStatusChange(r,n.task.id)})("nzOpenChange",function(r){return n.handleOpen(r)}),e.YNc(2,Vn,4,8,"nz-option",2),e.qZA()()),2&t&&(e.xp6(1),e.Udp("background-color",n.task.status_color),e.Q6J("ngModel",n.task.status)("nzLoading",n.loading)("nzDropdownMatchSelectWidth",!1)("nzSize","small"),e.xp6(1),e.Q6J("ngForOf",n.statuses)("ngForTrackBy",n.trackById))},dependencies:[h.sg,p.JJ,K.Ip,K.Vq,p.On,ie.x7,_e.m],styles:["nz-select[_ngcontent-%COMP%]{max-width:100px}"],changeDetection:0}),a})();var je=c(8660);function Wn(i,a){if(1&i&&(e.TgZ(0,"nz-option",3),e._UZ(1,"worklenz-task-priority-label",4),e.qZA()),2&i){const s=a.$implicit;e.Q6J("nzValue",s.id)("nzLabel",s.name)("nzCustomContent",!0),e.xp6(1),e.Q6J("name",s.name)}}let Xn=(()=>{var i;class a{constructor(t,n,o,r,l,d){this.service=t,this.socket=n,this.cdr=o,this.ngZone=r,this.element=l,this.renderer=d,this.task={},this.cls="flex-row task-priority",this.priorities=[],this.loading=!1,this.handleResponse=_=>{_&&_.id===this.task.id&&(this.task.priority_color=_.color_code,this.task.priority=_.priority_id,this.isGroupByPriority()&&(this.task.is_sub_task||this.service.updateTaskGroup(this.task,!1),this.service.isSubtasksIncluded&&this.service.emitRefreshSubtasksIncluded()),this.cdr.markForCheck())},this.service.onPrioritiesChange$.pipe((0,G.sL)()).subscribe(()=>{this.updatePriorities(),this.cdr.markForCheck()}),this.service.onGroupChange$.pipe((0,W.h)(_=>_.taskId===this.task.id),(0,W.h)(()=>this.isGroupByPriority()),(0,G.sL)()).subscribe(_=>{this.task.priority=_.groupId,this.task.priority_color=_.color,this.cdr.markForCheck()})}ngOnInit(){this.updatePriorities(),this.socket.on(k.C.PT_TASK_PRIORITY_CHANGE.toString(),this.handleResponse)}ngOnDestroy(){this.priorities=[],this.socket.removeListener(k.C.PT_TASK_PRIORITY_CHANGE.toString(),this.handleResponse)}isGroupByPriority(){return this.service.getCurrentGroup().value===this.service.GROUP_BY_PRIORITY_VALUE}trackById(t,n){return n.id}handlePriorityChange(t,n){this.socket.emit(k.C.PT_TASK_PRIORITY_CHANGE.toString(),JSON.stringify({task_id:n.id,priority_id:t,parent_task:this.task.parent_task_id}))}updatePriorities(){this.priorities=this.service.priorities}toggleHighlightCls(t,n){this.ngZone.runOutsideAngular(()=>{t?this.renderer.addClass(n,this.service.HIGHLIGHT_COL_CLS):this.renderer.removeClass(n,this.service.HIGHLIGHT_COL_CLS)})}handleOpen(t){this.toggleHighlightCls(t,this.element.nativeElement)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(B),e.Y36(q.s),e.Y36(e.sBO),e.Y36(e.R0b),e.Y36(e.SBq),e.Y36(e.Qsj))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-task-priority"]],hostVars:2,hostBindings:function(t,n){2&t&&e.Tol(n.cls)},inputs:{task:"task"},decls:3,vars:8,consts:[[1,"py-0"],[1,"rounded-pill","custom-select",3,"ngModel","nzLoading","nzDropdownMatchSelectWidth","nzSize","ngModelChange","nzOpenChange"],[3,"nzValue","nzLabel","nzCustomContent",4,"ngFor","ngForOf","ngForTrackBy"],[3,"nzValue","nzLabel","nzCustomContent"],[3,"name"]],template:function(t,n){1&t&&(e.TgZ(0,"div",0)(1,"nz-select",1),e.NdJ("ngModelChange",function(r){return n.task.priority=r})("ngModelChange",function(r){return n.handlePriorityChange(r,n.task)})("nzOpenChange",function(r){return n.handleOpen(r)}),e.YNc(2,Wn,2,4,"nz-option",2),e.qZA()()),2&t&&(e.xp6(1),e.Udp("background",n.task.priority_color),e.Q6J("ngModel",n.task.priority)("nzLoading",n.loading)("nzDropdownMatchSelectWidth",!1)("nzSize","small"),e.xp6(1),e.Q6J("ngForOf",n.priorities)("ngForTrackBy",n.trackById))},dependencies:[h.sg,p.JJ,K.Ip,K.Vq,p.On,je.o],changeDetection:0}),a})();var qe=c(68373);const ei=["labelsSearchInput"];function ti(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"div",7)(1,"nz-form-item",8)(2,"nz-form-control")(3,"div",9)(4,"nz-form-control",10)(5,"small",11),e._uU(6,"Hours"),e.qZA(),e.TgZ(7,"nz-input-number",12),e.NdJ("ngModelChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.task.total_hours=n)}),e.qZA()(),e.TgZ(8,"nz-form-control")(9,"small",11),e._uU(10,"Minutes"),e.qZA(),e.TgZ(11,"nz-input-number",13),e.NdJ("ngModelChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.task.total_minutes=n)}),e.qZA()()()()()()}if(2&i){const s=e.oxw();e.xp6(7),e.Q6J("ngModel",s.task.total_hours)("nzFormatter",s.utils.toRound)("nzMin",0)("nzPlaceHolder","Hours")("nzStep",1),e.xp6(4),e.Q6J("ngModel",s.task.total_minutes)("nzFormatter",s.utils.toRound)("nzMax",60)("nzMin",0)("nzPlaceHolder","Minutes")("nzStep",1)}}let ni=(()=>{var i;class a{constructor(t,n,o,r,l){this.cdr=t,this.socket=n,this.service=o,this.utils=r,this.ngZone=l,this.task={},this.cls="flex-row task-estimation p-0",this.show=!1,this.handleResponse=d=>{this.task.id===d?.id&&(this.task.total_time_string=d.total_time_string,this.closeDropdown(),this.cdr.markForCheck())}}ngOnInit(){this.socket.on(k.C.PT_TASK_TIME_ESTIMATION_CHANGE.toString(),this.handleResponse)}ngOnDestroy(){this.socket.removeListener(k.C.PT_TASK_TIME_ESTIMATION_CHANGE.toString(),this.handleResponse)}handleLabelsVisibleChange(t,n){this.show=t,t?n.classList.add(this.service.HIGHLIGHT_COL_CLS):n.classList.remove(this.service.HIGHLIGHT_COL_CLS)}submit(){this.task?.id&&this.socket.emit(k.C.PT_TASK_TIME_ESTIMATION_CHANGE.toString(),JSON.stringify({task_id:this.task.id,total_hours:this.task.total_hours||0,total_minutes:this.task.total_minutes||0,parent_task:this.task.parent_task_id}))}closeDropdown(){this.ngZone.runOutsideAngular(()=>{document.body.click()})}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(e.sBO),e.Y36(q.s),e.Y36(B),e.Y36(de.F),e.Y36(e.R0b))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-task-estimation"]],viewQuery:function(t,n){if(1&t&&e.Gf(ei,5),2&t){let o;e.iGM(o=e.CRH())&&(n.labelsSearchInput=o.first)}},hostVars:2,hostBindings:function(t,n){2&t&&e.Tol(n.cls)},inputs:{task:"task"},decls:11,vars:6,consts:[["nz-dropdown","",1,"editable","w-100","h-100","d-flex","align-items-center","justify-content-center",3,"nzOverlayClassName","nzTrigger","nzClickHide","nzDropdownMenu","nzVisibleChange"],["element",""],["nz-typography","",1,"mb-0"],["estimationDropdown","nzDropdownMenu"],["class","bg-white pt-3 px-3",4,"ngIf"],[1,"bg-white","d-flex","justify-content-end","p-2","border-top"],["nz-button","","nzType","primary","nzSize","small",3,"click"],[1,"bg-white","pt-3","px-3"],["nz-row","",1,"w-100","mb-0"],[1,"d-inline-flex",2,"position","relative","top","-12px"],[1,"me-3"],["nz-typography","","nzType","secondary",1,"d-block"],[3,"ngModel","nzFormatter","nzMin","nzPlaceHolder","nzStep","ngModelChange"],[3,"ngModel","nzFormatter","nzMax","nzMin","nzPlaceHolder","nzStep","ngModelChange"]],template:function(t,n){if(1&t){const o=e.EpF();e.TgZ(0,"div",0,1),e.NdJ("nzVisibleChange",function(l){e.CHM(o);const d=e.MAs(1);return e.KtG(n.handleLabelsVisibleChange(l,d))}),e.ynx(2),e.TgZ(3,"p",2),e._uU(4),e.qZA(),e.BQk(),e.qZA(),e.TgZ(5,"nz-dropdown-menu",null,3),e.YNc(7,ti,12,11,"div",4),e.TgZ(8,"div",5)(9,"button",6),e.NdJ("click",function(){return n.submit()}),e._uU(10,"OK"),e.qZA()()()}if(2&t){const o=e.MAs(6);e.Q6J("nzOverlayClassName","custom-shadow")("nzTrigger","click")("nzClickHide",!1)("nzDropdownMenu",o),e.xp6(4),e.Oqu(n.task.total_time_string),e.xp6(3),e.Q6J("ngIf",n.show)}},dependencies:[h.O5,p.JJ,Z.t3,Z.SK,x.Nx,x.Fd,O.ix,v.w,L.dQ,Q.ZU,p.On,E.cm,E.RR,qe._V],changeDetection:0}),a})(),ii=(()=>{var i;class a{transform(t,...n){return t.sub_tasks_count?"#191919":"rgba(0, 0, 0, 0.45)"}}return(i=a).\u0275fac=function(t){return new(t||i)},i.\u0275pipe=e.Yjl({name:"subTasksArrowColorPT",type:i,pure:!0}),a})(),si=(()=>{var i;class a{transform(t,...n){return t?"down":"right"}}return(i=a).\u0275fac=function(t){return new(t||i)},i.\u0275pipe=e.Yjl({name:"subTasksArrowIconPT",type:i,pure:!0}),a})();function oi(i,a){1&i&&e._UZ(0,"div",15)}function ai(i,a){1&i&&(e.TgZ(0,"div",16),e._UZ(1,"span",17),e.qZA()),2&i&&(e.xp6(1),e.Q6J("nzType","holder")("nzTheme","outline"))}function ri(i,a){if(1&i&&(e._UZ(0,"span",24),e.ALo(1,"subTasksArrowIconPT")),2&i){const s=e.oxw(3);e.Q6J("nzType",e.lcZ(1,2,s.task.show_sub_tasks))("nzTheme","outline")}}function li(i,a){1&i&&e._UZ(0,"span",25),2&i&&e.Q6J("nzType","loading")}function ci(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"div",20),e.NdJ("click",function(n){e.CHM(s);const o=e.oxw(2);return n.stopPropagation(),e.KtG(o.openSubTasks())}),e.ynx(1),e.TgZ(2,"span",21),e.ALo(3,"subTasksArrowColorPT"),e.YNc(4,ri,2,4,"span",22),e.YNc(5,li,1,1,"span",23),e.qZA(),e.BQk(),e.qZA()}if(2&i){const s=e.oxw(2);e.xp6(2),e.Udp("color",e.lcZ(3,6,s.task)),e.ekj("hidden-arrow",!s.Number(s.task.sub_tasks_count)&&!s.task.show_sub_tasks),e.xp6(2),e.Q6J("ngIf",!s.task.sub_tasks_loading),e.xp6(1),e.Q6J("ngIf",s.task.sub_tasks_loading)}}function pi(i,a){if(1&i&&(e.TgZ(0,"div",18),e.YNc(1,ci,6,8,"div",19),e.qZA()),2&i){const s=e.oxw();e.xp6(1),e.Q6J("ngIf",!s.task.is_sub_task)}}function di(i,a){1&i&&(e.TgZ(0,"small",32),e._UZ(1,"span",17),e.qZA()),2&i&&(e.Q6J("nzType","secondary"),e.xp6(1),e.Q6J("nzType","double-right")("nzTheme","outline"))}function ui(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"div",30),e.NdJ("click",function(n){e.CHM(s);const o=e.oxw(2),r=e.MAs(10);return e.KtG(o.onTaskNameClick(n,r,o.task))}),e.ALo(1,"safeString"),e.YNc(2,di,2,3,"small",31),e._uU(3),e.qZA()}if(2&i){const s=e.oxw(2);e.Q6J("nzTooltipMouseEnterDelay",.5)("nzTooltipTitle",e.lcZ(1,4,s.task.name)),e.xp6(2),e.Q6J("ngIf",s.task.is_sub_task),e.xp6(1),e.hij(" ",s.task.name," \xa0 ")}}function _i(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"nz-tag",33),e.NdJ("click",function(n){e.CHM(s);const o=e.oxw(2);return n.stopPropagation(),e.KtG(o.openSubTasks())}),e.TgZ(1,"span",34),e._uU(2),e._UZ(3,"span",35),e.qZA()()}if(2&i){const s=e.oxw(2);e.Q6J("nzColor","default"),e.xp6(2),e.hij(" ",s.task.sub_tasks_count," "),e.xp6(1),e.Q6J("nzType","double-right")("nzTheme","outline")}}function mi(i,a){if(1&i&&(e.TgZ(0,"div",26)(1,"div",27)(2,"div"),e.YNc(3,ui,4,6,"div",28),e.qZA(),e.YNc(4,_i,4,4,"nz-tag",29),e.qZA()()),2&i){const s=e.oxw();e.xp6(3),e.Q6J("ngIf",s.editId!==s.task.id),e.xp6(1),e.Q6J("ngIf",!s.task.is_sub_task&&!s.service.isSubtasksIncluded)}}function hi(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"input",36),e.NdJ("focus",function(){e.CHM(s);const n=e.oxw(),o=e.MAs(7),r=e.MAs(10);return n.selectCol(o),e.KtG(n.selectCol(r))})("ngModelChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.task.name=n)})("blur",function(){e.CHM(s);const n=e.oxw(),o=e.MAs(7),r=e.MAs(10);return n.handleNameChange(n.task),n.deselectCol(o),e.KtG(n.deselectCol(r))})("keydown.enter",function(){e.CHM(s);const n=e.oxw(),o=e.MAs(7),r=e.MAs(10);return n.handleNameChange(n.task),n.deselectCol(o),e.KtG(n.deselectCol(r))}),e.qZA()}if(2&i){const s=e.oxw();e.Q6J("ngModel",s.task.name)("nzBorderless",!0)}}let gi=(()=>{var i;class a{get id(){return this.task.id}constructor(t,n,o,r,l,d,_,y){this.element=t,this.renderer=n,this.cdr=o,this.service=r,this.socket=l,this.map=d,this.ngZone=_,this.utils=y,this.cls="position-relative task-row",this.onShowSubTasks=new e.vpe,this.highlight="highlight-col",this.Number=Number,this.editId=null,this.selected=!1,this.handleNameChangeResponse=S=>{S&&this.id===S.id&&this.task&&this.task.name!=S.name&&(this.task.name=S.name,this.markForCheck())},this.handleEstimationChangeResponse=S=>{S.id===this.id&&(this.task.total_time_string=S.total_time_string,this.cdr.markForCheck())},this.service.onColumnsChange$.pipe((0,G.sL)()).subscribe(()=>{this.markForCheck()}),(0,Ze.T)(this.map.onSelect$.pipe((0,W.h)(S=>S.id===this.id),(0,W.h)(()=>!this.selected)),this.map.onDeselect$.pipe((0,W.h)(S=>S.id===this.id),(0,W.h)(()=>this.selected)),this.map.onDeselectAll$.pipe((0,W.h)(()=>this.selected))).pipe((0,G.sL)()).subscribe(S=>{this.selected=!this.selected,this.toggleSelection(),this.markForCheck()})}toggleSelection(){this.ngZone.runOutsideAngular(()=>{const t="selected",n=this.element.nativeElement;this.selected?this.renderer.addClass(n,t):this.renderer.removeClass(n,t)})}ngOnInit(){this.registerSocketEvents()}ngOnDestroy(){this.unregisterSocketEvents()}registerSocketEvents(){this.socket.on(k.C.PT_TASK_NAME_CHANGE.toString(),this.handleNameChangeResponse),this.socket.on(k.C.PT_TASK_TIME_ESTIMATION_CHANGE.toString(),this.handleEstimationChangeResponse)}unregisterSocketEvents(){this.socket.removeListener(k.C.PT_TASK_NAME_CHANGE.toString(),this.handleNameChangeResponse),this.socket.removeListener(k.C.PT_TASK_TIME_ESTIMATION_CHANGE.toString(),this.handleEstimationChangeResponse)}onContextMenu(t){this.service.emitOnContextMenu(t,this.task)}focus(t){setTimeout(()=>{t.querySelector("input")?.focus()})}onCheckChange(t){t?this.map.selectTask(this.task):this.map.deselectTask(this.task),this.toggleSelection()}openSubTasks(){this.onShowSubTasks?.emit(this.task)}selectCol(t){t.classList.contains(this.highlight)||t.classList.add(this.highlight)}deselectCol(t){t.classList.remove(this.highlight),this.editId=null}handleNameChange(t){t&&(this.socket.emit(k.C.PT_TASK_NAME_CHANGE.toString(),JSON.stringify({task_id:t.id,name:t.name,parent_task:this.task.parent_task_id})),this.editId=null)}onTaskNameClick(t,n,o){t.stopPropagation(),this.focus(n),this.editId=o.id||null}markForCheck(){this.cdr.markForCheck()}detectChanges(){this.cdr.detectChanges()}onDragStart(){this.map.deselectAll(),this.detectChanges()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(e.SBq),e.Y36(e.Qsj),e.Y36(e.sBO),e.Y36(B),e.Y36(q.s),e.Y36(ue),e.Y36(e.R0b),e.Y36(de.F))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-pt-task-list-row"]],hostVars:2,hostBindings:function(t,n){1&t&&e.NdJ("contextmenu",function(r){return n.onContextMenu(r)}),2&t&&e.Tol(n.cls)},inputs:{task:"task"},outputs:{onShowSubTasks:"onShowSubTasks"},decls:26,vars:16,consts:[["cdkDrag","",1,"flex-table","rows",3,"ngClass","cdkDragData","cdkDragLockAxis","cdkDragBoundary","cdkDragStarted"],["class","task-placeholder",4,"cdkDragPlaceholder"],["cdkDragHandle","",1,"flex-row","task-drag-handler",3,"cdkDragHandleDisabled"],["class","drag-handle",4,"ngIf"],[1,"flex-row","task-check"],["nz-checkbox","",1,"p-0",3,"nzChecked","nzCheckedChange"],[1,"flex-row","task-arrow"],["tr0",""],["class","p-0 border-end-0",4,"ngIf"],[1,"flex-row","task-name"],["tr1",""],[1,"inner-task-name-container"],["class","editable-cell pointer-text ps-1 w-100",4,"ngIf"],["nz-input","","class","ps-1 name-input","style","min-width: 365px; max-width: 365px;",3,"ngModel","nzBorderless","focus","ngModelChange","blur","keydown.enter",4,"ngIf"],[3,"task"],[1,"task-placeholder"],[1,"drag-handle"],["nz-icon","",3,"nzType","nzTheme"],[1,"p-0","border-end-0"],["class","d-flex align-items-center justify-content-center sub-tasks-arrow dropdown-highlight",3,"click",4,"ngIf"],[1,"d-flex","align-items-center","justify-content-center","sub-tasks-arrow","dropdown-highlight",3,"click"],[1,"align-items-center","align-self-center","cursor-pointer","d-flex","sub-arrow"],["style","font-size: 12px","nz-icon","",3,"nzType","nzTheme",4,"ngIf"],["nz-icon","",3,"nzType",4,"ngIf"],["nz-icon","",2,"font-size","12px",3,"nzType","nzTheme"],["nz-icon","",3,"nzType"],[1,"editable-cell","pointer-text","ps-1","w-100"],[1,"d-flex","w-100"],["nz-tooltip","","class","task-name-text",3,"nzTooltipMouseEnterDelay","nzTooltipTitle","click",4,"ngIf"],["class","me-1 px-1 double-arrow",3,"nzColor","click",4,"ngIf"],["nz-tooltip","",1,"task-name-text",3,"nzTooltipMouseEnterDelay","nzTooltipTitle","click"],["nz-typography","",3,"nzType",4,"ngIf"],["nz-typography","",3,"nzType"],[1,"me-1","px-1","double-arrow",3,"nzColor","click"],[2,"color","#6d6e6f"],["nz-icon","",2,"font-size","10px",3,"nzType","nzTheme"],["nz-input","",1,"ps-1","name-input",2,"min-width","365px","max-width","365px",3,"ngModel","nzBorderless","focus","ngModelChange","blur","keydown.enter"]],template:function(t,n){1&t&&(e.TgZ(0,"div",0),e.NdJ("cdkDragStarted",function(){return n.onDragStart()}),e.YNc(1,oi,1,0,"div",1),e.TgZ(2,"div",2),e.YNc(3,ai,2,2,"div",3),e.qZA(),e.TgZ(4,"div",4)(5,"span",5),e.NdJ("nzCheckedChange",function(r){return n.selected=r})("nzCheckedChange",function(r){return n.onCheckChange(r)}),e.qZA()(),e.TgZ(6,"div",6,7),e.YNc(8,pi,2,1,"div",8),e.qZA(),e.TgZ(9,"div",9,10)(11,"div",11),e.YNc(12,mi,5,2,"div",12),e.qZA(),e.YNc(13,hi,1,2,"input",13),e.qZA(),e.ynx(14),e._UZ(15,"worklenz-task-description",14),e.BQk(),e.ynx(16),e._UZ(17,"worklenz-task-labels",14),e.BQk(),e.ynx(18),e._UZ(19,"worklenz-task-phase",14),e.BQk(),e.ynx(20),e._UZ(21,"worklenz-task-status",14),e.BQk(),e.ynx(22),e._UZ(23,"worklenz-task-priority",14),e.BQk(),e.ynx(24),e._UZ(25,"worklenz-task-estimation",14),e.BQk(),e.qZA()),2&t&&(e.Q6J("ngClass",n.task.is_sub_task?"subtask":"main-task")("cdkDragData",n.task)("cdkDragLockAxis","y")("cdkDragBoundary",".tasks-wrapper"),e.xp6(2),e.Q6J("cdkDragHandleDisabled",!!n.task.parent_task_id),e.xp6(1),e.Q6J("ngIf",!n.task.parent_task_id),e.xp6(2),e.Q6J("nzChecked",n.selected),e.xp6(3),e.Q6J("ngIf",!n.service.isSubtasksIncluded),e.xp6(4),e.Q6J("ngIf",n.editId!==n.task.id),e.xp6(1),e.Q6J("ngIf",n.editId===n.task.id),e.xp6(2),e.Q6J("task",n.task),e.xp6(2),e.Q6J("task",n.task),e.xp6(2),e.Q6J("task",n.task),e.xp6(2),e.Q6J("task",n.task),e.xp6(2),e.Q6J("task",n.task),e.xp6(2),e.Q6J("task",n.task))},dependencies:[h.mk,h.O5,P.Ls,p.Fj,p.JJ,M.Zp,v.w,Q.ZU,J.SY,p.On,ze.j,we.Ie,Ce.Zt,Ce.Bh,Ln,Hn,qn,Kn,Xn,ni,_e.m,ii,si],styles:["[_nghost-%COMP%]{position:relative;display:table-row;vertical-align:inherit;border-color:inherit;-webkit-user-select:none;user-select:none}[_nghost-%COMP%]:hover .plus-icon[_ngcontent-%COMP%]{display:block}[_nghost-%COMP%]:hover td[_ngcontent-%COMP%]{background:#ecf0f3}[_nghost-%COMP%]:hover .hidden-arrow[_ngcontent-%COMP%]{display:flex!important}.hidden-arrow[_ngcontent-%COMP%]{display:none!important}.dropdown-highlight[_ngcontent-%COMP%]:hover{background-color:#d0eefa54;border:#5587f5 1px solid;border-radius:3px}.plus-icon[_ngcontent-%COMP%]{display:none;position:absolute;right:0;z-index:1;top:0;bottom:0;height:100%}.expanded[_ngcontent-%COMP%]{transform:rotate(-90deg)}.sub-tasks-arrow[_ngcontent-%COMP%]{position:relative;cursor:pointer;left:3px;width:16px;padding:2px;border:1px solid transparent;z-index:1}.sub-tasks-arrow[_ngcontent-%COMP%] .sub-arrow[_ngcontent-%COMP%]{width:10px;height:10px;color:#191919;margin-left:-2px}.task-name-text[_ngcontent-%COMP%]{border:1px solid transparent;padding-left:2px;border-radius:4px}.task-name-text[_ngcontent-%COMP%]:hover{border:1px solid #d9d9d9}.task-name[_ngcontent-%COMP%]{border:1px solid transparent}.task-name[_ngcontent-%COMP%]:hover{cursor:text;background:#fff;border-radius:4px}.highlight-col[_ngcontent-%COMP%]{border:1px solid #1890ff!important}.highlight-col[_ngcontent-%COMP%] nz-date-picker[_ngcontent-%COMP%]{box-shadow:none}.editable[_ngcontent-%COMP%] .add-button[_ngcontent-%COMP%]{visibility:hidden}.editable[_ngcontent-%COMP%]:hover .add-button[_ngcontent-%COMP%]{visibility:visible}.ant-popover[_ngcontent-%COMP%]{width:500px}.flex-table[_ngcontent-%COMP%]{display:flex}.rows[_ngcontent-%COMP%] .flex-row[_ngcontent-%COMP%]{padding:3px 12px;border-bottom:1px solid #f0f0f0;border-right:1px solid #f0f0f0;background:white;display:flex;align-items:center;max-height:50px;border-radius:0}.rows[_ngcontent-%COMP%]:hover .flex-row[_ngcontent-%COMP%]{background:#f8f7f9}.subtask[_ngcontent-%COMP%] .flex-row[_ngcontent-%COMP%]{background:#fcfcfc}.task-check[_ngcontent-%COMP%]{text-align:center;padding:8px 6px 8px 0!important;border-left:none;position:sticky;left:24px;z-index:1}.task-arrow[_ngcontent-%COMP%]{width:24px;min-width:24px;padding:8px 11px 8px 2px!important;border-right:none!important;position:sticky;left:47px;z-index:1}.task-arrow.highlight-col[_ngcontent-%COMP%]{border-top:1px solid #188fff!important;border-left:1px solid #188fff!important;border-bottom:1px solid #188fff!important}.task-name[_ngcontent-%COMP%]{width:450px;min-width:450px;position:sticky;left:71px;z-index:1;border-radius:0;padding-right:65px!important}.task-name.highlight-col[_ngcontent-%COMP%]{border-top:1px solid #188fff!important;border-right:1px solid #188fff!important;border-bottom:1px solid #188fff!important;border-left:none!important}.task-name.left-0[_ngcontent-%COMP%]{left:47px}.task-key[_ngcontent-%COMP%]{width:85px;min-width:85px;padding-left:4px!important;padding-right:4px!important;justify-content:center}.task-key[_ngcontent-%COMP%] nz-tag[_ngcontent-%COMP%]{padding-left:4px;padding-right:4px;max-width:80px;text-overflow:ellipsis;overflow:hidden}.task-description[_ngcontent-%COMP%]{width:225px;min-width:225px;overflow:hidden;display:grid!important}.task-progress[_ngcontent-%COMP%]{width:80px;min-width:80px}.task-labels[_ngcontent-%COMP%]{padding:0!important}.task-labels[_ngcontent-%COMP%] .editable[_ngcontent-%COMP%]{padding:6px 11px;align-items:center;display:flex}.task-members[_ngcontent-%COMP%]{padding:0!important}.task-members[_ngcontent-%COMP%] .editable[_ngcontent-%COMP%]{padding:6px 11px;align-items:center;display:flex}.task-members[_ngcontent-%COMP%]{width:160px;min-width:160px}.task-labels[_ngcontent-%COMP%]{width:220px;min-width:220px}.task-status[_ngcontent-%COMP%]{width:120px;min-width:120px}.task-phase[_ngcontent-%COMP%]{width:150px;min-width:150px}.task-priority[_ngcontent-%COMP%], .task-time-tracking[_ngcontent-%COMP%], .task-estimation[_ngcontent-%COMP%]{width:120px;min-width:120px}.task-start-date[_ngcontent-%COMP%], .task-due-date[_ngcontent-%COMP%], .task-completed-date[_ngcontent-%COMP%], .task-created-date[_ngcontent-%COMP%], .task-update-date[_ngcontent-%COMP%]{width:150px;min-width:150px}.task-due-date[_ngcontent-%COMP%]{padding:0!important}.task-due-date[_ngcontent-%COMP%] .editable[_ngcontent-%COMP%]{align-items:center;display:flex}.task-drag-handler[_ngcontent-%COMP%]{padding:0 0 0 4px!important;width:24px;min-width:24px;border-right:none!important;position:sticky;left:0;z-index:1;background-color:#fff}.drag-handle[_ngcontent-%COMP%]{cursor:grab;opacity:.8}.drag-handle[_ngcontent-%COMP%]:hover span[_ngcontent-%COMP%]{color:#1890ff}.drag-handle[_ngcontent-%COMP%]:active{cursor:grabbing}.task-name-text[_ngcontent-%COMP%]{width:100%;-webkit-line-clamp:1;display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis}.inner-icon-cont[_ngcontent-%COMP%]{width:max-content;display:flex;justify-content:flex-end;align-items:center;column-gap:4px}.name-input[_ngcontent-%COMP%]{padding:5px 12px;border-left:1px solid transparent}.double-arrow[_ngcontent-%COMP%]{line-height:16px;border:none;cursor:pointer}.task-placeholder[_ngcontent-%COMP%]{width:100%;height:42px;border:1px dashed #d9d9d9;background:#fafafa}.v-line[_ngcontent-%COMP%]{background-color:#188fff!important;position:absolute;inset:0 -5px 0 0;width:1px;margin:auto}.double-arrow[_ngcontent-%COMP%]{height:16px;margin-top:4px}"]}),a})();var fi=c(59773);const zi=["contextMenuDropdown"];function Ti(i,a){if(1&i&&e._uU(0),2&i){const s=e.oxw().$implicit;e.hij(" ",(null==s?null:s.name)||null," ")}}function Ci(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"li",9),e.NdJ("click",function(){const o=e.CHM(s).$implicit,r=e.oxw(2);return e.KtG(r.changeGroup(o.id))}),e._UZ(1,"nz-badge",10),e.ALo(2,"safeString"),e.YNc(3,Ti,1,1,"ng-template",null,11,e.W1O),e.qZA()}if(2&i){const s=a.$implicit,t=e.MAs(4);e.xp6(1),e.Q6J("nzColor",e.lcZ(2,2,s.color_code))("nzText",t)}}function ki(i,a){if(1&i&&(e.TgZ(0,"li",7)(1,"ul"),e.YNc(2,Ci,5,4,"li",8),e.qZA()()),2&i){const s=e.oxw(),t=e.MAs(9);e.Q6J("nzTitle",t),e.xp6(2),e.Q6J("ngForOf",s.groups)}}function xi(i,a){1&i&&(e._UZ(0,"span",12),e._uU(1," Move to\n"))}let bi=(()=>{var i;class a{constructor(t,n,o,r,l,d){this.contextMenuService=t,this.service=n,this.map=o,this.api=r,this.socket=l,this.cdr=d,this.templateId=null,this.groups=[],this.deleting=!1,this.hasSubTasks=!1,this.selectedTask=null,this.destroy$=new R.x,this.service.onContextMenu$.pipe((0,fi.R)(this.destroy$)).subscribe(_=>{this.onContextMenu(_)})}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}onContextMenu(t){this.selectedTask=t.task,this.map.deselectAll(),this.map.selectTask(t.task),this.hasSubTasks=this.isSelectionHasSubTasks(),this.cdr.detectChanges(),this.contextMenuService.create(t.event,this.contextMenuDropdown)}isSelectionHasSubTasks(){return this.map.getSelectedTasks().some(t=>t.is_sub_task)}changeGroup(t){if(!this.selectedTask)return;const n=this.service.getCurrentGroup();n.value===this.service.GROUP_BY_STATUS_VALUE?this.handleStatusChange(t,this.selectedTask.id):n.value===this.service.GROUP_BY_PRIORITY_VALUE?this.handlePriorityChange(t,this.selectedTask.id):n.value===this.service.GROUP_BY_PHASE_VALUE&&this.handlePhaseChange(t,this.selectedTask.id)}handleStatusChange(t,n){n&&this.socket.emit(k.C.PT_TASK_STATUS_CHANGE.toString(),JSON.stringify({task_id:n,status_id:t}))}handlePriorityChange(t,n){n&&this.socket.emit(k.C.PT_TASK_PRIORITY_CHANGE.toString(),JSON.stringify({task_id:n,priority_id:t}))}handlePhaseChange(t,n){n&&this.socket.emit(k.C.PT_TASK_PHASE_CHANGE.toString(),{task_id:n,phase_id:t})}delete(){var t=this;return(0,m.Z)(function*(){if(!t.deleting)try{t.deleting=!0;const n=t.map.getSelectedTaskIds(),o=yield t.api.bulkDelete({tasks:n},t.templateId);if(o.done)for(const r of o.body.deleted_tasks)t.service.deleteTask(r);t.deleting=!1}catch{t.deleting=!1}})()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(E.Iw),e.Y36(B),e.Y36(ue),e.Y36(Qe),e.Y36(q.s),e.Y36(e.sBO))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-context-menu"]],viewQuery:function(t,n){if(1&t&&e.Gf(zi,5),2&t){let o;e.iGM(o=e.CRH())&&(n.contextMenuDropdown=o.first)}},inputs:{templateId:"templateId",groups:"groups"},decls:10,vars:4,consts:[["contextMenuDropdown","nzDropdownMenu"],["nz-menu",""],[3,"ngSwitch"],["nz-submenu","",3,"nzTitle",4,"ngSwitchCase"],["nz-menu-item","",3,"click"],["nz-icon","",1,"me-2",3,"nzType","nzTheme"],["titleTemplate",""],["nz-submenu","",3,"nzTitle"],["class","m-0","nz-menu-item","",3,"click",4,"ngFor","ngForOf"],["nz-menu-item","",1,"m-0",3,"click"],[3,"nzColor","nzText"],["textTmpl",""],["nz-icon","","nzType","retweet","nzTheme","outline",1,"me-2"]],template:function(t,n){1&t&&(e.TgZ(0,"nz-dropdown-menu",null,0)(2,"ul",1),e.ynx(3,2),e.YNc(4,ki,3,2,"li",3),e.BQk(),e.TgZ(5,"li",4),e.NdJ("click",function(){return n.delete()}),e._UZ(6,"span",5),e._uU(7," Delete "),e.qZA()()(),e.YNc(8,xi,2,0,"ng-template",null,6,e.W1O)),2&t&&(e.xp6(3),e.Q6J("ngSwitch",n.hasSubTasks),e.xp6(1),e.Q6J("ngSwitchCase",!1),e.xp6(2),e.Q6J("nzType",n.deleting?"loading":"delete")("nzTheme","outline"))},dependencies:[h.sg,h.RF,h.n9,P.Ls,v.w,T.wO,T.r9,T.rY,ie.x7,E.RR,_e.m],changeDetection:0}),a})();var vi=c(34554),Fe=c(66987);function wi(i,a){1&i&&(e.TgZ(0,"button",20),e._uU(1," Search "),e.qZA()),2&i&&e.Q6J("nzSize","small")("nzType","primary")}function yi(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",21),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.reset())}),e._uU(1," Reset "),e.qZA()}2&i&&e.Q6J("nzSize","small")}function Si(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"li",22),e.NdJ("click",function(){const o=e.CHM(s).$implicit,r=e.oxw();return e.KtG(r.changeGroup(o))}),e.TgZ(1,"span",23),e._uU(2),e.qZA()()}if(2&i){const s=a.$implicit,t=e.oxw();e.Q6J("nzSelected",s.value===t.selectedGroup.value),e.xp6(2),e.Oqu(s.label)}}function Mi(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",24),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.createStatusClick())}),e._uU(1," Add Status "),e.qZA()}2&i&&e.Q6J("nzType","primary")("nzTooltipTitle","Create status")}function Pi(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"button",24),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.phaseSettingsClick())}),e._uU(1," Add Phase "),e.qZA()}if(2&i){const s=e.oxw();e.Q6J("nzType","primary")("nzTooltipTitle",s.phaseLabel+" settings")}}let Oi=(()=>{var i;class a{get selectedGroup(){return this.service.getCurrentGroup()}get phaseLabel(){return this.phaseService.label}constructor(t,n,o,r,l,d,_,y){this.cdr=t,this.ngZone=n,this.tasksApi=o,this.socket=r,this.utils=l,this.phaseService=d,this.service=_,this.auth=y,this.onGroupBy=new e.vpe,this.onFilterSearch=new e.vpe,this.onPhaseSettingsClick=new e.vpe,this.onCreateStatusClick=new e.vpe,this.ASCEND="ascend",this.DESCEND="descend",this.COUNTS_LABELS_STYLE={backgroundColor:"#1890ff",color:"#fff"},this.taskSearch=null}changeGroup(t){this.service.setCurrentGroup(t),this.onGroupBy.emit(t)}isGroupByStatus(){return this.selectedGroup.value===this.service.GROUP_BY_STATUS_VALUE}isGroupByPhase(){return this.selectedGroup.value===this.service.GROUP_BY_PHASE_VALUE}trackById(t,n){return n.id}toIdsMap(t){return t.map(n=>n.id).join("+")}search(){this.taskSearch&&(this.onFilterSearch.emit(encodeURIComponent(this.taskSearch)),document.body.click())}reset(){this.taskSearch&&(this.taskSearch=null,this.onFilterSearch.emit(this.taskSearch),this.ngZone.runOutsideAngular(()=>{document.body.click()}))}onSearchDropdownVisibleChange(t){t&&this.ngZone.runOutsideAngular(()=>{setTimeout(()=>{document.querySelector("#task-search-input")?.focus()},j.GR)})}phaseSettingsClick(){this.onPhaseSettingsClick?.emit()}createStatusClick(){this.onCreateStatusClick?.emit()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(e.sBO),e.Y36(e.R0b),e.Y36(vi.c),e.Y36(q.s),e.Y36(de.F),e.Y36(Be.u),e.Y36(B),e.Y36(w.e))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-group-filter"]],inputs:{templateId:"templateId"},outputs:{onGroupBy:"onGroupBy",onFilterSearch:"onFilterSearch",onPhaseSettingsClick:"onPhaseSettingsClick",onCreateStatusClick:"onCreateStatusClick"},decls:26,vars:22,consts:[["nz-form","",3,"nzLayout"],["nz-row","",3,"nzAlign","nzJustify"],["nz-col",""],["nz-button","","nz-dropdown","",1,"me-2",3,"nzDropdownMenu","nzTrigger","nzVisibleChange"],["nz-icon","","nzType","search","nzTheme","outline"],["taskSearchDropdown","nzDropdownMenu"],[1,"bg-white","shadow","rounded-4","p-2",3,"submit"],["type","text","nz-input","","name","search","id","task-search-input","placeholder","Search by name",3,"ngModel","ngModelChange"],[1,"mt-2"],["nz-button","","type","submit",3,"nzSize","nzType",4,"nzSpaceItem"],["nz-button","","type","button",3,"nzSize","click",4,"nzSpaceItem"],[3,"nzType"],[1,"ms-1"],["nz-button","","nz-dropdown","",1,"ms-1","me-2",3,"nzTrigger","nzDropdownMenu","nzClickHide"],["nz-tooltip","",3,"nzTooltipTitle"],["nz-icon","",3,"nzType"],["groupByMenu",""],["nz-menu",""],["class","m-0","nz-menu-item","",3,"nzSelected","click",4,"ngFor","ngForOf","ngForTrackBy"],["nz-button","","class","ms-0","nz-tooltip","",3,"nzType","nzTooltipTitle","click",4,"ngIf"],["nz-button","","type","submit",3,"nzSize","nzType"],["nz-button","","type","button",3,"nzSize","click"],["nz-menu-item","",1,"m-0",3,"nzSelected","click"],["nz-typography",""],["nz-button","","nz-tooltip","",1,"ms-0",3,"nzType","nzTooltipTitle","click"]],template:function(t,n){if(1&t&&(e.TgZ(0,"form",0)(1,"div",1)(2,"div",2)(3,"button",3),e.NdJ("nzVisibleChange",function(r){return n.onSearchDropdownVisibleChange(r)}),e._UZ(4,"span",4),e.qZA(),e.TgZ(5,"nz-dropdown-menu",null,5)(7,"form",6),e.NdJ("submit",function(){return n.search()}),e.TgZ(8,"input",7),e.NdJ("ngModelChange",function(r){return n.taskSearch=r}),e.qZA(),e.TgZ(9,"nz-space",8),e.YNc(10,wi,2,2,"button",9),e.YNc(11,yi,2,1,"button",10),e.qZA()()(),e._UZ(12,"nz-divider",11),e.TgZ(13,"label",12),e._uU(14,"Group by: "),e.qZA(),e.TgZ(15,"button",13)(16,"span",14),e._uU(17),e.ALo(18,"ellipsis"),e.qZA(),e._UZ(19,"span",15),e.qZA(),e.TgZ(20,"nz-dropdown-menu",null,16)(22,"ul",17),e.YNc(23,Si,3,2,"li",18),e.qZA()(),e.YNc(24,Mi,2,2,"button",19),e.YNc(25,Pi,2,2,"button",19),e.qZA()()()),2&t){const o=e.MAs(6),r=e.MAs(21);e.Q6J("nzLayout","vertical"),e.xp6(1),e.Q6J("nzAlign","bottom")("nzJustify","space-between"),e.xp6(2),e.ekj("filter-active",!!n.taskSearch),e.Q6J("nzDropdownMenu",o)("nzTrigger","click"),e.xp6(5),e.Q6J("ngModel",n.taskSearch),e.xp6(4),e.Q6J("nzType","vertical"),e.xp6(3),e.Q6J("nzTrigger","click")("nzDropdownMenu",r)("nzClickHide",!0),e.xp6(1),e.Q6J("nzTooltipTitle",n.selectedGroup.label),e.xp6(1),e.hij(" ",e.xi3(18,19,n.selectedGroup.label,15)," "),e.xp6(2),e.Q6J("nzType","caret-down"),e.xp6(4),e.Q6J("ngForOf",n.service.GROUP_BY_OPTIONS)("ngForTrackBy",n.trackById),e.xp6(1),e.Q6J("ngIf",n.isGroupByStatus()&&n.auth.isOwnerOrAdmin()),e.xp6(1),e.Q6J("ngIf",n.isGroupByPhase()&&n.auth.isOwnerOrAdmin())}},dependencies:[h.sg,h.O5,P.Ls,p._Y,p.Fj,p.JJ,p.JL,Z.t3,Z.SK,x.Lr,M.Zp,O.ix,v.w,L.dQ,Q.ZU,T.wO,T.r9,H.NU,H.$1,J.SY,p.On,p.F,E.cm,E.RR,E.wA,Fe.g,Le.p]}),a})(),Ai=(()=>{var i;class a{get label(){return this._label||this.DEFAULT_LABEL}set label(t){this._label=t||this.DEFAULT_LABEL}get onLabelChange(){return this._labelChangeSbj$.asObservable()}get onPhaseOptionsChange(){return this._phaseOptionsChangeSbj$.asObservable()}constructor(t){this.list=t,this._labelChangeSbj$=new R.x,this._phaseOptionsChangeSbj$=new R.x,this.DEFAULT_LABEL="Phase",this._label=null}updateLabel(t){const n=this.list.GROUP_BY_OPTIONS.find(o=>o.value===this.list.GROUP_BY_PHASE_VALUE);n&&(this.label=t,n.label=this.label,this._labelChangeSbj$.next(this.label))}emitOptionsChange(){this._phaseOptionsChangeSbj$.next()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.LFG(B))},i.\u0275prov=e.Yz7({token:i,factory:i.\u0275fac,providedIn:"root"}),a})();var ke=c(43389);function Ii(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"li",23),e.NdJ("click",function(){const o=e.CHM(s).$implicit,r=e.oxw().$implicit,l=e.oxw(2);return e.KtG(l.setColorCode(r,o))}),e.TgZ(1,"nz-tag",24),e._uU(2,"\xa0 "),e.qZA()()}if(2&i){const s=a.$implicit;e.xp6(1),e.Q6J("nzColor",s+"69")}}function Zi(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"nz-form-item",14)(1,"nz-form-control")(2,"input",15,16),e.NdJ("ngModelChange",function(n){const r=e.CHM(s).$implicit;return e.KtG(r.name=n)})("focus",function(){const o=e.CHM(s).$implicit,r=e.MAs(3),l=e.oxw(2);return r.select(),e.KtG(l.setNameCache(o.id,o.name))})("blur",function(){const o=e.CHM(s).$implicit,r=e.oxw(2);return e.KtG(r.updateOption(o))})("keyup.enter",function(){e.CHM(s);const n=e.MAs(3);return e.KtG(n.blur())}),e.qZA()(),e.TgZ(4,"nz-tag",17),e._uU(5,"\xa0 "),e.qZA(),e.TgZ(6,"nz-dropdown-menu",null,18)(8,"ul",19),e.YNc(9,Ii,3,1,"li",20),e.qZA()(),e.TgZ(10,"nz-form-label",21)(11,"button",22),e.NdJ("click",function(){const o=e.CHM(s).$implicit,r=e.oxw(2);return e.KtG(r.removeOption(o.id))}),e._UZ(12,"span",12),e.qZA()()()}if(2&i){const s=a.$implicit,t=a.index,n=e.MAs(7),o=e.oxw(2);e.xp6(2),e.Q6J("name","opt"+t)("ngModel",s.name)("minLength",2)("maxLength",30)("disabled",o.deleting[s.id]),e.xp6(2),e.Q6J("nzColor",s.color_code)("nzDropdownMenu",n)("nzTrigger","click"),e.xp6(5),e.Q6J("ngForOf",o.COLOR_CODES),e.xp6(1),e.Q6J("nzNoColon",!0),e.xp6(1),e.Q6J("nzShape","circle")("nzType","text")("nzLoading",o.deleting[s.id]),e.xp6(1),e.Q6J("nzType","close-circle")("nzTheme","outline")}}function Li(i,a){if(1&i){const s=e.EpF();e.ynx(0),e.TgZ(1,"nz-skeleton",2)(2,"form",3)(3,"nz-form-item")(4,"nz-form-label",4),e._uU(5,"Phase Label:"),e.qZA(),e.TgZ(6,"nz-form-control")(7,"input",5),e.NdJ("ngModelChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.phaseLabel=n)})("focus",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.oldLabel=n.phaseLabel)})("blur",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.updateLabel(n.phaseLabel))}),e.qZA()()()(),e._UZ(8,"nz-divider",6),e.TgZ(9,"form",7)(10,"div",8)(11,"div",9)(12,"nz-form-label"),e._uU(13," Phase Options "),e.qZA()(),e.TgZ(14,"div",10)(15,"button",11),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.addNewOption())}),e._UZ(16,"span",12),e._uU(17," Add Option "),e.qZA()()(),e.YNc(18,Zi,13,15,"nz-form-item",13),e.qZA()(),e.BQk()}if(2&i){const s=e.oxw();e.xp6(1),e.Q6J("nzActive",!0)("nzLoading",s.loading),e.xp6(1),e.Q6J("nzLayout","vertical"),e.xp6(2),e.Q6J("nzFor","label"),e.xp6(3),e.Q6J("ngModel",s.phaseLabel),e.xp6(8),e.Q6J("nzType","primary")("nzLoading",s.creating),e.xp6(1),e.Q6J("nzType","plus")("nzTheme","outline"),e.xp6(2),e.Q6J("ngForOf",s.options)}}let Fi=(()=>{var i;class a{get options(){return this.list.phases}constructor(t,n,o,r){this.api=t,this.cdr=n,this.list=o,this.service=r,this.templateId=null,this.show=!1,this.showChange=new e.vpe,this.onCreateOrUpdate=new e.vpe,this.refresh=new e.vpe,this.COLOR_CODES=j.zW,this.loading=!1,this.creating=!1,this.updatingLabel=!1,this.updating={},this.deleting={},this.updateCache={},this.oldLabel=null,this.phaseLabel=null}close(){this.show=!1,this.showChange.emit(!1)}addNewOption(){this.create()}onVisibleChange(t){t&&(this.get(!0),this.phaseLabel=this.service.label)}removeOption(t){t&&this.delete(t)}updateOption(t){var n=this;return(0,m.Z)(function*(){yield n.update(t),delete n.updateCache[t.id]})()}setNameCache(t,n){this.updateCache[t]=n}create(){var t=this;return(0,m.Z)(function*(){if(t.templateId&&!t.creating){try{t.creating=!0,(yield t.api.create(t.templateId)).done&&(yield t.get(!1),t.service.emitOptionsChange(),t.onCreateOrUpdate.emit()),t.creating=!1}catch{t.creating=!1}t.cdr.markForCheck()}})()}get(t){var n=this;return(0,m.Z)(function*(){if(n.templateId){try{n.loading=t;const o=yield n.api.get(n.templateId);o.done&&(n.list.phases=o.body),n.loading=!1}catch{n.loading=!1}n.cdr.markForCheck()}})()}updateLabel(t){var n=this;return(0,m.Z)(function*(){if(n.templateId){if(!t?.trim())return void(n.phaseLabel=n.oldLabel);try{n.updatingLabel=!0,(yield n.api.updateLabel(n.templateId,t?.trim())).done&&n.service.updateLabel(t?.trim()),n.updatingLabel=!1}catch{n.updatingLabel=!1}n.cdr.markForCheck()}})()}update(t){var n=this;return(0,m.Z)(function*(){if(t?.id&&n.templateId&&!n.updating[t.id]&&n.updateCache[t.id]!==t.name){try{n.updating[t.id]=!0,(yield n.api.update(n.templateId,t)).done&&(yield n.get(!1),n.service.emitOptionsChange(),n.onCreateOrUpdate.emit()),n.updating[t.id]=!1}catch{t.name=n.updateCache[t.id],n.updating[t.id]=!1}n.cdr.markForCheck()}})()}delete(t){var n=this;return(0,m.Z)(function*(){if(t&&n.templateId&&!n.deleting[t]){try{if(n.deleting[t]=!0,(yield n.api.delete(t,n.templateId)).done){const r=n.list.phases.findIndex(l=>l.id===t);r>-1&&(n.list.phases.splice(r,1),n.service.emitOptionsChange(),n.onCreateOrUpdate.emit())}n.deleting[t]=!1}catch{n.deleting[t]=!1}n.cdr.markForCheck()}})()}setColorCode(t,n){var o=this;return(0,m.Z)(function*(){t.color_code=n+"69",yield o.updateColor(t)})()}updateColor(t){var n=this;return(0,m.Z)(function*(){if(t?.id&&n.templateId){try{n.updating[t.id]=!0,(yield n.api.updateColor(n.templateId,t)).done&&n.refresh.emit(),n.updating[t.id]=!1}catch{t.name=n.updateCache[t.id],n.updating[t.id]=!1}n.cdr.markForCheck()}})()}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(Ie),e.Y36(e.sBO),e.Y36(B),e.Y36(Ai))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-phase-settings-drawer"]],inputs:{templateId:"templateId",show:"show"},outputs:{showChange:"showChange",onCreateOrUpdate:"onCreateOrUpdate",refresh:"refresh"},decls:2,vars:4,consts:[[3,"nzClosable","nzVisible","nzPlacement","nzTitle","nzOnClose","nzVisibleChange"],[4,"nzDrawerContent"],[3,"nzActive","nzLoading"],["nz-form","",3,"nzLayout"],[3,"nzFor"],["nz-input","","id","label","placeholder","Enter a name for label","name","label","required","",3,"ngModel","ngModelChange","focus","blur"],[1,"mb-3"],["nz-form",""],[1,"d-flex","align-items-center","justify-content-between","mb-3"],[1,"d-block"],[1,"d-block","ms-auto"],["type","button","nz-button","","nzBlock","",3,"nzType","nzLoading","click"],["nz-icon","",3,"nzType","nzTheme"],["class","d-flex align-items-center mb-3",4,"ngFor","ngForOf"],[1,"d-flex","align-items-center","mb-3"],["nz-input","",3,"name","ngModel","minLength","maxLength","disabled","ngModelChange","focus","blur","keyup.enter"],["input",""],["nz-dropdown","",1,"ms-2","rounded-circle","cursor-pointer",2,"width","20px","height","20px",3,"nzColor","nzDropdownMenu","nzTrigger"],["menu","nzDropdownMenu"],["nz-menu","","nzSelectable","",2,"max-height","200px","overflow","hidden","overflow-y","auto"],["nz-menu-item","",3,"click",4,"ngFor","ngForOf"],[3,"nzNoColon"],["type","button","nz-button","",3,"nzShape","nzType","nzLoading","click"],["nz-menu-item","",3,"click"],[1,"me-1","w-100","rounded-pill",2,"height","16px !important","width","16px !important",3,"nzColor"]],template:function(t,n){1&t&&(e.TgZ(0,"nz-drawer",0),e.NdJ("nzOnClose",function(){return n.close()})("nzVisibleChange",function(r){return n.onVisibleChange(r)}),e.YNc(1,Li,19,10,"ng-container",1),e.qZA()),2&t&&e.Q6J("nzClosable",!0)("nzVisible",n.show)("nzPlacement","right")("nzTitle","Configure Phases")},dependencies:[h.sg,P.Ls,p._Y,p.Fj,p.JJ,p.JL,p.Q7,Z.t3,Z.SK,x.Lr,x.Nx,x.iK,x.Fd,M.Zp,O.ix,v.w,L.dQ,T.wO,T.r9,$.ng,p.On,p.F,E.cm,E.RR,ze.j,ke.Vz,ke.SQ,Fe.g],changeDetection:0}),a})();function Ni(i,a){if(1&i&&(e.TgZ(0,"nz-option",11),e._UZ(1,"nz-badge",12),e.qZA()),2&i){const s=a.$implicit;e.Q6J("nzLabel",s.name||null)("nzValue",s.id),e.xp6(1),e.Q6J("nzTooltipTitle",s.description)("nzText",(null==s?null:s.name)||null)("nzTooltipPlacement","left")("nzColor",s.color_code)}}function Ei(i,a){if(1&i){const s=e.EpF();e.ynx(0),e.TgZ(1,"nz-skeleton",2)(2,"form",3),e.NdJ("submit",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.submit())}),e.TgZ(3,"nz-form-item")(4,"nz-form-label",4),e._uU(5,"Name"),e.qZA(),e.TgZ(6,"nz-form-control",5),e._UZ(7,"input",6),e.qZA()(),e.TgZ(8,"nz-form-item")(9,"nz-form-label",4),e._uU(10,"Category"),e.qZA(),e.TgZ(11,"nz-form-control",7)(12,"nz-select",8),e.YNc(13,Ni,2,6,"nz-option",9),e.qZA()()(),e.TgZ(14,"button",10),e._uU(15),e.qZA()()(),e.BQk()}if(2&i){const s=e.oxw();e.xp6(1),e.Q6J("nzActive",!0)("nzLoading",s.loading),e.xp6(1),e.Q6J("formGroup",s.form)("nzLayout","vertical"),e.xp6(2),e.Q6J("nzSpan",null),e.xp6(2),e.Q6J("nzSpan",null),e.xp6(1),e.Q6J("formControlName","name"),e.xp6(2),e.Q6J("nzSpan",null),e.xp6(2),e.Q6J("nzSpan",null),e.xp6(1),e.Q6J("formControlName","category_id"),e.xp6(1),e.Q6J("ngForOf",s.categories),e.xp6(2),e.Oqu(s.action)}}let Ui=(()=>{var i;class a{constructor(t,n,o){this.api=t,this.fb=n,this.app=o,this.action="Create",this.show=!1,this.statusId=null,this.templateId=null,this.showChange=new e.vpe,this.onCreateOrUpdate=new e.vpe,this.loading=!0,this.loadingCategories=!1,this.colorCodes=j.lD,this.categories=[],this.taskStatus={},this.createForm()}init(){this.form.controls.template_id.setValue(this.templateId),this.getCategories(),this.statusId?this.getById(this.statusId):this.loading=!1}closeModal(){this.show=!1,this.form.reset(),this.action="Create",this.createForm(),this.showChange.emit()}submit(){var t=this;return(0,m.Z)(function*(){t.taskStatus&&t.taskStatus.id?yield t.updateStatus():yield t.addStatus()})()}getById(t){var n=this;return(0,m.Z)(function*(){try{n.loading=!0;const o=yield n.api.getById(t);o.done&&(n.taskStatus=o.body,n.form.patchValue(n.taskStatus)),n.loading=!1}catch(o){(0,b.tu)(o),n.loading=!1}})()}getCategories(){var t=this;return(0,m.Z)(function*(){try{t.loadingCategories=!0;const n=yield t.api.getCategories();n.done&&(t.categories=n.body,t.form.controls.category_id.setValue(t.categories[0].id)),t.loadingCategories=!1}catch(n){t.loadingCategories=!1,(0,b.tu)(n)}})()}addStatus(){var t=this;return(0,m.Z)(function*(){if(t.form.invalid)t.app.displayErrorsOf(t.form);else try{const n=yield t.api.create(t.form.value);n.done&&(n.body.color_code=n.body.color_code+"69",t.onCreateOrUpdate.emit(),t.closeModal())}catch(n){(0,b.tu)(n)}})()}updateStatus(){var t=this;return(0,m.Z)(function*(){if(t.taskStatus&&t.taskStatus.id){if(t.form.invalid)return void t.app.displayErrorsOf(t.form);try{(yield t.api.update(t.taskStatus.id,t.form.value)).done&&(t.onCreateOrUpdate.emit(),t.closeModal(),(0,me.mT)())}catch(n){(0,b.tu)(n)}}})()}onVisibilityChange(t){t&&setTimeout(()=>this.init(),100)}createForm(){this.form=this.fb.group({name:[null,[p.kI.required]],category_id:[null,[p.kI.required]],template_id:[this.templateId]})}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(Ae),e.Y36(p.qu),e.Y36(U.z))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-status-settings-drawer"]],inputs:{action:"action",show:"show",statusId:"statusId",templateId:"templateId"},outputs:{showChange:"showChange",onCreateOrUpdate:"onCreateOrUpdate"},decls:2,vars:3,consts:[["nzPlacement","right",3,"nzVisible","nzClosable","nzTitle","nzOnClose","nzVisibleChange"],[4,"nzDrawerContent"],[3,"nzActive","nzLoading"],["nz-form","",3,"formGroup","nzLayout","submit"],["nzRequired","",3,"nzSpan"],["nzErrorTip","Please enter a name!",3,"nzSpan"],["nz-input","","placeholder","Name",3,"formControlName"],[3,"nzSpan"],[3,"formControlName"],["nzCustomContent","",3,"nzLabel","nzValue",4,"ngFor","ngForOf"],["nz-button","","nzBlock","","nzType","primary","type","submit"],["nzCustomContent","",3,"nzLabel","nzValue"],["nz-tooltip","",1,"w-100",3,"nzTooltipTitle","nzText","nzTooltipPlacement","nzColor"]],template:function(t,n){1&t&&(e.TgZ(0,"nz-drawer",0),e.NdJ("nzOnClose",function(){return n.closeModal()})("nzVisibleChange",function(r){return n.show=r})("nzVisibleChange",function(r){return n.onVisibilityChange(r)}),e.YNc(1,Ei,16,12,"ng-container",1),e.qZA()),2&t&&(e.MGl("nzTitle","",n.action," Status"),e.Q6J("nzVisible",n.show)("nzClosable",!0))},dependencies:[h.sg,p._Y,p.Fj,p.JJ,p.JL,p.sg,p.u,Z.t3,Z.SK,x.Lr,x.Nx,x.iK,x.Fd,M.Zp,O.ix,v.w,L.dQ,J.SY,$.ng,K.Ip,K.Vq,ie.x7,ke.Vz,ke.SQ]}),a})();const Ji=["input"];function Gi(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"h4",3),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.focusInput())}),e._uU(1),e.qZA()}if(2&i){const s=e.oxw();e.xp6(1),e.Oqu(s.templateName)}}function Di(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"input",4,5),e.NdJ("ngModelChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.templateName=n)})("ngModelChange",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.validate())})("blur",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.onBlur())}),e.qZA()}if(2&i){const s=e.oxw();e.Q6J("ngClass",s.isEmpty?"error":"")("ngModel",s.templateName)}}function Yi(i,a){1&i&&(e.TgZ(0,"small",6),e._uU(1,"Name cannot be empty!"),e.qZA())}let Qi=(()=>{var i;class a{constructor(t,n,o,r){this.socket=t,this.ngZone=n,this.renderer=o,this.cdr=r,this.templateId=null,this.templateName=null,this.showInput=!1,this.isEmpty=!1,this.handleResponse=l=>{l&&(this.templateName=l.template_name,this.showInput=!1,this.cdr.markForCheck())}}ngOnInit(){this.socket.on(k.C.PT_NAME_CHANGE.toString(),this.handleResponse)}focusInput(){this.showInput=!0,this.ngZone.runOutsideAngular(()=>{setTimeout(()=>{this.input&&(this.input.nativeElement.focus(),this.cdr.markForCheck())},100)})}onBlur(){this.validate()&&this.changeName()}validate(){return""!==this.templateName?.trim()&&this.templateName?(this.isEmpty=!1,!0):(this.isEmpty=!0,!1)}changeName(){this.socket.emit(k.C.PT_NAME_CHANGE.toString(),JSON.stringify({template_id:this.templateId,template_name:this.templateName}))}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(q.s),e.Y36(e.R0b),e.Y36(e.Qsj),e.Y36(e.sBO))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-template-name"]],viewQuery:function(t,n){if(1&t&&e.Gf(Ji,5),2&t){let o;e.iGM(o=e.CRH())&&(n.input=o.first)}},inputs:{templateId:"templateId",templateName:"templateName"},decls:3,vars:3,consts:[["nz-typography","","class","mb-0",3,"click",4,"ngIf"],["nz-input","","placeholder","Template Name","type","text","class","temp-name-input",3,"ngClass","ngModel","ngModelChange","blur",4,"ngIf"],["nz-typography","","nzType","danger","class","error-text",4,"ngIf"],["nz-typography","",1,"mb-0",3,"click"],["nz-input","","placeholder","Template Name","type","text",1,"temp-name-input",3,"ngClass","ngModel","ngModelChange","blur"],["input",""],["nz-typography","","nzType","danger",1,"error-text"]],template:function(t,n){1&t&&(e.YNc(0,Gi,2,1,"h4",0),e.YNc(1,Di,2,2,"input",1),e.YNc(2,Yi,2,0,"small",2)),2&t&&(e.Q6J("ngIf",!n.showInput),e.xp6(1),e.Q6J("ngIf",n.showInput),e.xp6(1),e.Q6J("ngIf",n.isEmpty))},dependencies:[h.mk,h.O5,p.Fj,p.JJ,M.Zp,Q.ZU,p.On],styles:[".temp-name-input[_ngcontent-%COMP%]{color:#000000d9;font-weight:600;font-size:18px;line-height:1.4}.error[_ngcontent-%COMP%]{border-color:#ff4d4f;box-shadow:none}.error-text[_ngcontent-%COMP%]{position:absolute;left:0;bottom:-16px}h4[_ngcontent-%COMP%]{transition:.25s all;border:1px solid transparent}h4[_ngcontent-%COMP%]:hover{border:1px solid #d9d9d9;border-radius:4px}"],changeDetection:0}),a})();const Ri=["row"],Bi=["scrollPanel"];function Hi(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"span")(1,"button",14),e.NdJ("click",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.onBack())}),e._UZ(2,"span",15),e.qZA()()}}function $i(i,a){if(1&i&&(e.TgZ(0,"span",16),e._UZ(1,"worklenz-template-name",17),e.qZA()),2&i){const s=e.oxw();e.xp6(1),e.Q6J("templateId",s.templateId)("templateName",s.templateName)}}function ji(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"worklenz-group-filter",18),e.NdJ("onGroupBy",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.onGroupByChange(n))})("onFilterSearch",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.handleFilterSearch(n))})("onPhaseSettingsClick",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.openAddColumnDrawer())})("onCreateStatusClick",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.openStatusDrawer())}),e.qZA()}if(2&i){const s=e.oxw();e.Q6J("templateId",s.templateId)}}function qi(i,a){if(1&i&&(e.TgZ(0,"div",19)(1,"div",20),e._UZ(2,"img",21),e.qZA(),e._UZ(3,"span",22),e.qZA()),2&i){const s=e.oxw();e.xp6(3),e.Q6J("ngSwitch",s.isGroupByPhase())}}function Vi(i,a){1&i&&(e.TgZ(0,"div",39),e._uU(1," No tasks available "),e.qZA())}function Ki(i,a){1&i&&e._UZ(0,"worklenz-pt-task-list-row",44),2&i&&e.Q6J("task",a.$implicit)}function Wi(i,a){if(1&i){const s=e.EpF();e.ynx(0),e.TgZ(1,"div",45,46)(3,"div",47),e._uU(4," \xa0 "),e.qZA(),e.TgZ(5,"div",48)(6,"worklenz-add-task-input",49,50),e.NdJ("focusChange",function(n){e.CHM(s);const o=e.MAs(2),r=e.MAs(7),l=e.oxw(4);return e.KtG(l.quickTaskFocusChange(n,o,r))}),e.qZA()()(),e.BQk()}if(2&i){const s=e.oxw(2).$implicit,t=e.oxw().$implicit,n=e.oxw();e.xp6(6),e.Q6J("templateId",n.templateId)("label","Add sub-task")("parentTask",s.id)("groupId",t.id)("subTaskInput",!0)}}function Xi(i,a){if(1&i&&(e.ynx(0),e.TgZ(1,"div"),e.YNc(2,Ki,1,1,"worklenz-pt-task-list-row",43),e.YNc(3,Wi,8,5,"ng-container",42),e.qZA(),e.BQk()),2&i){const s=e.oxw().$implicit,t=e.oxw(2);e.xp6(2),e.Q6J("ngForOf",s.sub_tasks)("ngForTrackBy",t.trackById),e.xp6(1),e.Q6J("ngIf",t.templateId&&s.id&&s.show_sub_tasks)}}function es(i,a){if(1&i){const s=e.EpF();e.ynx(0),e.TgZ(1,"worklenz-pt-task-list-row",40,41),e.NdJ("onShowSubTasks",function(n){e.CHM(s);const o=e.MAs(2),r=e.oxw().$implicit,l=e.oxw();return e.KtG(l.displaySubTasks(n,o,r.id))}),e.qZA(),e.YNc(3,Xi,4,3,"ng-container",42),e.BQk()}if(2&i){const s=a.$implicit,t=e.oxw(2);e.xp6(1),e.ekj("selected",(null==t.selectedTask?null:t.selectedTask.id)===s.id),e.Q6J("task",s),e.xp6(2),e.Q6J("ngIf",s.sub_tasks&&s.show_sub_tasks)}}function ts(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"worklenz-add-task-input",51),e.NdJ("focusChange",function(n){e.CHM(s),e.oxw();const o=e.MAs(14),r=e.oxw();return e.KtG(r.handleFocusChange(n,o))}),e.qZA()}if(2&i){const s=e.oxw().$implicit,t=e.oxw();e.Q6J("templateId",t.templateId)("groupId",s.id)}}function ns(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"div",23)(1,"div",24)(2,"worklenz-task-list-group-settings",25),e.NdJ("toggle",function(n){e.CHM(s);const o=e.MAs(4),r=e.oxw();return e.KtG(r.toggleGroup(n,o))})("onCreateOrUpdate",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.refreshWithoutLoading())}),e.qZA(),e.TgZ(3,"div",26,27),e._UZ(5,"span",28),e.TgZ(6,"div",29)(7,"div",30,31),e.NdJ("cdkDropListDropped",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.onDrop(n))})("scroll",function(){e.CHM(s);const n=e.MAs(8),o=e.oxw();return e.KtG(o.onScroll(n))}),e.TgZ(9,"div",32)(10,"worklenz-task-list-header",33),e.NdJ("selectChange",function(n){const r=e.CHM(s).$implicit,l=e.oxw();return e.KtG(l.selectTasksInGroup(n,r))})("phaseSettingsClick",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.openAddColumnDrawer())}),e.qZA(),e.YNc(11,Vi,2,0,"div",34),e.YNc(12,es,4,4,"ng-container",35),e.qZA()(),e.TgZ(13,"div",36,37),e.YNc(15,ts,1,2,"worklenz-add-task-input",38),e.qZA()()()()()}if(2&i){const s=a.$implicit,t=e.oxw();e.xp6(2),e.Q6J("group",s)("templateId",t.templateId)("categories",t.categories),e.xp6(1),e.Udp("max-height",s.tasks.length?void 0:0),e.xp6(2),e.Udp("background",s.color_code),e.xp6(2),e.Q6J("id",s.id)("cdkDropListConnectedTo",t.groupIds)("cdkDropListData",s),e.xp6(3),e.Q6J("groupId",s.id),e.xp6(1),e.Q6J("ngIf",!s.tasks.length),e.xp6(1),e.Q6J("ngForOf",s.tasks)("ngForTrackBy",t.trackById),e.xp6(3),e.Q6J("ngIf",t.templateId)}}function is(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"worklenz-phase-settings-drawer",52),e.NdJ("showChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.displayPhaseModal=n)})("onCreateOrUpdate",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.refreshWithoutLoading())})("refresh",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.refreshWithoutLoading())}),e.qZA()}if(2&i){const s=e.oxw();e.Q6J("templateId",s.templateId)("show",s.displayPhaseModal)}}function ss(i,a){if(1&i){const s=e.EpF();e.TgZ(0,"worklenz-status-settings-drawer",53),e.NdJ("onCreateOrUpdate",function(){e.CHM(s);const n=e.oxw();return e.KtG(n.refreshWithoutLoading())})("showChange",function(n){e.CHM(s);const o=e.oxw();return e.KtG(o.displayStatusModal=n)}),e.qZA()}if(2&i){const s=e.oxw();e.Q6J("statusId",null)("templateId",s.templateId)("show",s.displayStatusModal)}}const as=[{path:"",component:ve,children:[{path:"",redirectTo:"profile",pathMatch:"full"},{path:"profile",component:F},{path:"language-and-region",component:zt},{path:"labels",canActivate:[oe.T],component:St},{path:"categories",canActivate:[oe.T],loadChildren:()=>c.e(931).then(c.bind(c,82931)).then(i=>i.CategoriesModule)},{path:"clients",canActivate:[oe.T],loadChildren:()=>c.e(554).then(c.bind(c,9554)).then(i=>i.ClientsModule)},{path:"job-titles",canActivate:[oe.T],loadChildren:()=>c.e(693).then(c.bind(c,93693)).then(i=>i.JobTitlesModule)},{path:"notifications",loadChildren:()=>c.e(940).then(c.bind(c,94940)).then(i=>i.NotificationSettingsModule)},{path:"teams",component:rt},{path:"team-members",canActivate:[oe.T],component:sn},{path:"password",canActivate:[(i,a)=>(0,e.f3M)(_t).canActivate(i,a)],component:ut},{path:"task-templates",canActivate:[oe.T],component:Zt},{path:"project-templates",canActivate:[oe.T],component:pn}]},{path:"project-templates/edit/:id/:name",canActivate:[oe.T],component:(()=>{var i;class a{get loading(){return this.loadingGroups}get defaultStatus(){return this.service.statuses.length?this.service.statuses[0].id:null}get groups(){return this.service.groups}constructor(t,n,o,r,l,d,_,y,S,be,ne,ae,zo,To,Co,ko){this.route=t,this.router=n,this.cdr=o,this.service=r,this.ngZone=l,this.map=d,this.socket=_,this.renderer=y,this.utils=S,this.api=be,this.labelsApi=ne,this.statusesApi=ae,this.prioritiesApi=zo,this.phasesApi=To,this.app=Co,this.auth=ko,this.scrollBy=0,this.templateId=null,this.templateName="",this.searchValue=null,this.templatesFilterString=null,this.selected=!1,this.loadingGroups=!1,this.groupChanging=!1,this.displayPhaseModal=!1,this.displayStatusModal=!1,this.checked=!1,this.indeterminate=!1,this.loadingFiltering=!1,this.loadingStatuses=!1,this.loadingPriorities=!1,this.loadingCategories=!1,this.loadingPhases=!1,this.loadingLabels=!1,this.taskId=null,this.selectedTask=null,this.groupIds=[],this.categories=[],this.handleSortOrderResponse=Ve=>{for(const Ke of Ve){const Ne=Ke.id;if(Ne){const Ee=this.map.tasks.get(Ne);Ee&&(Ee.sort_order=Ke.sort_order,this.map.tasks.set(Ne,Ee))}}},this.templateId=this.route.snapshot.paramMap.get("id"),this.templateName=this.route.snapshot.paramMap.get("name"),this.app.setTitle("Edit Template"),this.service.settemplateId(this.templateId),this.service.onTaskAddOrDelete$.pipe((0,G.sL)()).subscribe(Ve=>{this.cdr.markForCheck()}),this.service.onRefresh$.pipe((0,G.sL)()).subscribe(()=>{this.cdr.markForCheck()}),this.service.onRefreshSubtasksIncluded.pipe((0,G.sL)()).subscribe(()=>{this.cdr.markForCheck()})}ngOnInit(){this.service.isSubtasksIncluded=!1,this.init(!0)}init(t){Promise.all([this.getGroups(t),this.getLabels(),this.getStatuses(),this.getPriorities(),this.getCategories(),this.getPhases()]),this.socket.on(k.C.PT_TASK_SORT_ORDER_CHANGE.toString(),this.handleSortOrderResponse)}ngOnDestroy(){this.ngZone.runOutsideAngular(()=>{this.service.reset(),this.service.groups=[],this.map.reset()}),this.socket.removeListener(k.C.PT_TASK_SORT_ORDER_CHANGE.toString(),this.handleSortOrderResponse)}isGroupByPhase(){return this.service.getCurrentGroup().value===this.service.GROUP_BY_PHASE_VALUE}getConf(t){const n={id:this.templateId,search:this.searchValue,projects:this.templatesFilterString,group:this.service.getCurrentGroup().value,isSubtasksInclude:!1};return t&&(n.parent_task=t),n}displaySubTasks(t,n,o){var r=this;return(0,m.Z)(function*(){if(t.id||!t.sub_tasks_loading){if(!t.show_sub_tasks&&0===t.sub_tasks_count)return t.show_sub_tasks=!0,void(t.sub_tasks=[]);if(t.sub_tasks_loading=!0,t.show_sub_tasks=!t.show_sub_tasks,t.show_sub_tasks){t.sub_tasks=yield r.getSubTasks(t);for(const l of t.sub_tasks)r.map.add(o,l)}else{for(const l of t.sub_tasks||[])r.map.deselectTask(l);t.sub_tasks=[]}t.sub_tasks_loading=!1,n.detectChanges(),r.cdr.markForCheck()}})()}toggleGroup(t,n){this.ngZone.runOutsideAngular(()=>{const o=t.target;o&&(o.closest(".btn")?.classList.toggle("active"),this.renderer.setStyle(n,"max-height","0px"===n.style.maxHeight?n.scrollHeight+8+"px":"0px"))})}trackById(t,n){return n.id}onDrop(t){const n=t.previousIndex,o=t.currentIndex,r=t.previousContainer.data,l=t.container.data;0==r.tasks.length&&this.ngZone.runOutsideAngular(()=>{document.getElementById(`${t.previousContainer.id}`)?.closest("div")?.parentNode?.parentNode?.parentNode?.querySelector("button.collapse.active")?.classList.remove("active")});const d=t.item.data,_=l.tasks[o]?.sort_order;this.socket.emit(k.C.PT_TASK_SORT_ORDER_CHANGE.toString(),{template_id:this.service.gettemplateId(),from_index:r.tasks[n].sort_order,to_index:_||l.tasks[l.tasks.length-1]?.sort_order||-1,to_last_index:!_,from_group:r.id,to_group:l.id,group_by:this.service.getCurrentGroup().value,task:d,team_id:this.auth.getCurrentSession()?.team_id}),r.id===l.id?(0,Ce.bA)(t.container.data.tasks,n,o):((0,Ce.EA)(t.previousContainer.data.tasks,t.container.data.tasks,t.previousIndex,t.currentIndex),this.map.remove(d),this.map.add(l.id,d),this.service.emitGroupChange(l.id,d.id,l.color_code))}bulkUpdateSuccess(){var t=this;return(0,m.Z)(function*(){yield t.getGroups(!0)})()}selectTasksInGroup(t,n){for(const o of n.tasks)t?this.map.selectTask(o):this.map.deselectTask(o)}mapTasks(t){for(const n of t){this.map.registerGroup(n);for(const o of n.tasks)o.start_date&&(o.start_date=new Date(o.start_date)),o.end_date&&(o.end_date=new Date(o.end_date))}}toggleFocusCls(t,n){t?this.renderer.addClass(n,this.service.HIGHLIGHT_COL_CLS):this.renderer.removeClass(n,this.service.HIGHLIGHT_COL_CLS)}onShowChange(t){t||(this.selectedTask=null)}handleNewTaskReceive(t){if(t.isSubTask){const n=this.rows.find(o=>o.id===t.taskId);n&&n.detectChanges()}this.cdr.markForCheck()}handleFocusChange(t,n){this.ngZone.runOutsideAngular(()=>{this.toggleFocusCls(t,n)})}quickTaskFocusChange(t,n,o){this.ngZone.runOutsideAngular(()=>{this.toggleFocusCls(t,n)})}onGroupByChange(t){var n=this;return(0,m.Z)(function*(){n.service.setCurrentGroup(t),n.groupChanging=!0,yield n.getGroups(!0),setTimeout(()=>{n.groupChanging=!1,n.cdr.markForCheck()},100)})()}handleFilterSearch(t){var n=this;return(0,m.Z)(function*(){n.loadingFiltering=!0,n.searchValue=t,yield n.getGroups(!0),n.loadingFiltering=!1,n.cdr.markForCheck()})()}getGroups(t){var n=this;return(0,m.Z)(function*(){if(n.templateId){try{n.map.deselectAll(),n.loadingGroups=t;const o=n.getConf();o.isSubtasksInclude=n.service.isSubtasksIncluded;const r=yield n.api.getTaskList(o);if(r.done){const l=(0,b.I8)(r.body);n.groupIds=l.map(d=>d.id),n.mapTasks(l),n.service.groups=l}n.loadingGroups=!1}catch{n.loadingGroups=!1}n.cdr.markForCheck()}})()}getSubTasks(t){var n=this;return(0,m.Z)(function*(){let o=[];if(t?.id)try{const r=n.getConf(t.id),l=yield n.api.getTaskList(r);l.done&&(o=l.body)}catch{}return o})()}getStatuses(){var t=this;return(0,m.Z)(function*(){if(t.templateId)try{t.loadingStatuses=!0;const n=yield t.statusesApi.get(t.templateId);n.done&&(t.service.statuses=n.body),t.loadingStatuses=!1}catch{t.loadingStatuses=!1}})()}getPriorities(){var t=this;return(0,m.Z)(function*(){try{t.loadingPriorities=!0;const n=yield t.prioritiesApi.get();n.done&&(t.service.priorities=n.body),t.loadingPriorities=!1}catch{t.loadingPriorities=!1}})()}getCategories(){var t=this;return(0,m.Z)(function*(){try{t.loadingCategories=!0;const n=yield t.statusesApi.getCategories();n.done&&(t.categories=n.body),t.loadingCategories=!1}catch{t.loadingCategories=!1}})()}getPhases(){var t=this;return(0,m.Z)(function*(){if(t.templateId)try{t.loadingPhases=!0;const n=yield t.phasesApi.get(t.templateId);n.done&&(t.service.phases=n.body),t.loadingPhases=!1}catch{t.loadingPhases=!1}})()}getLabels(){var t=this;return(0,m.Z)(function*(){try{t.loadingLabels=!0;const n=yield t.labelsApi.get(t.templateId);n.done&&(t.service.labels=n.body),t.loadingLabels=!1}catch{t.loadingLabels=!1}})()}onScroll(t){this.ngZone.runOutsideAngular(()=>{const n="scrolling-panel";this.scrollBy=t.scrollLeft,this.scrollBy>0?t.classList.add(n):t.classList.remove(n)})}debounce(t,n){let o;return(...r)=>{clearTimeout(o),o=setTimeout(()=>t.apply(this,r),n)}}openAddColumnDrawer(){this.displayPhaseModal=!0}openStatusDrawer(){this.displayStatusModal=!0}onBack(){this.router.navigate(["/worklenz/settings/project-templates"])}refreshWithoutLoading(){this.init(!1)}}return(i=a).\u0275fac=function(t){return new(t||i)(e.Y36(N.gz),e.Y36(N.F0),e.Y36(e.sBO),e.Y36(B),e.Y36(e.R0b),e.Y36(ue),e.Y36(q.s),e.Y36(e.Qsj),e.Y36(de.F),e.Y36(Qe),e.Y36(un),e.Y36(Ae),e.Y36(_n),e.Y36(Ie),e.Y36(U.z),e.Y36(w.e))},i.\u0275cmp=e.Xpm({type:i,selectors:[["worklenz-project-template-edit-view"]],viewQuery:function(t,n){if(1&t&&(e.Gf(Ri,5),e.Gf(Bi,5)),2&t){let o;e.iGM(o=e.CRH())&&(n.rows=o),e.iGM(o=e.CRH())&&(n.scrollPanels=o)}},hostBindings:function(t,n){1&t&&e.NdJ("scroll",function(r){return n.onScroll(r.target)})},decls:15,vars:10,consts:[[1,"container"],[1,"nz-header","mt-3","mb-4"],[1,"align-items-center"],[4,"nzSpaceItem"],["class","name-input position-relative",4,"nzSpaceItem"],[1,"top-section","mb-3","mt-3"],[3,"templateId","onGroupBy","onFilterSearch","onPhaseSettingsClick","onCreateStatusClick",4,"ngIf"],[3,"nzActive","nzLoading"],["class","pt-5 pb-5",4,"ngIf"],["cdkDropListGroup","",1,"tasks-wrapper"],["class","mb-3",4,"rxFor","rxForOf","rxForTrackBy"],[3,"templateId","show","showChange","onCreateOrUpdate","refresh",4,"ngIf"],["action","Create",3,"statusId","templateId","show","onCreateOrUpdate","showChange",4,"ngIf"],[3,"templateId","groups"],["nz-button","","nzType","text",1,"ant-page-header-back-button","d-flex","align-items-center",2,"font-size","16px",3,"click"],["nz-icon","","nzType","arrow-left","nzTheme","outline"],[1,"name-input","position-relative"],[3,"templateId","templateName"],[3,"templateId","onGroupBy","onFilterSearch","onPhaseSettingsClick","onCreateStatusClick"],[1,"pt-5","pb-5"],[1,"no-data-img-holder","mx-auto","mb-4"],["src","/assets/images/empty-box.webp","alt","",1,"img-fluid"],["nz-typography","",1,"mx-auto","d-block","text-center","no-data-text",2,"width","max-content",3,"ngSwitch"],[1,"mb-3"],[1,"container","px-0"],[3,"group","templateId","categories","toggle","onCreateOrUpdate"],[1,"panel","position-relative"],["panel",""],[1,"panel-left-border"],[1,"tasks-table","position-relative"],["cdkDropList","",1,"container","px-0","table-container","table-1",3,"id","cdkDropListConnectedTo","cdkDropListData","cdkDropListDropped","scroll"],["scrollPanel",""],[1,"border-right"],[3,"groupId","selectChange","phaseSettingsClick"],["class","tasks-empty-placeholder d-flex align-items-center mb-0 ps-3","nz-typography","","nzType","secondary",4,"ngIf"],[4,"ngFor","ngForOf","ngForTrackBy"],[1,"new-task-input"],["td1",""],[3,"templateId","groupId","focusChange",4,"ngIf"],["nz-typography","","nzType","secondary",1,"tasks-empty-placeholder","d-flex","align-items-center","mb-0","ps-3"],[3,"task","onShowSubTasks"],["row",""],[4,"ngIf"],[3,"task",4,"ngFor","ngForOf","ngForTrackBy"],[3,"task"],[1,"d-flex","inner-subtask-create","cursor-pointer","sub-task-background-color"],["td",""],[1,"new-subtask-divider"],[1,"w-100"],[3,"templateId","label","parentTask","groupId","subTaskInput","focusChange"],["subTaskInput",""],[3,"templateId","groupId","focusChange"],[3,"templateId","show","showChange","onCreateOrUpdate","refresh"],["action","Create",3,"statusId","templateId","show","onCreateOrUpdate","showChange"]],template:function(t,n){1&t&&(e.TgZ(0,"div",0)(1,"div",1)(2,"nz-space",2),e.YNc(3,Hi,3,0,"span",3),e.YNc(4,$i,2,2,"span",4),e.qZA()(),e.TgZ(5,"nz-content")(6,"div",5),e.YNc(7,ji,1,1,"worklenz-group-filter",6),e.qZA(),e.TgZ(8,"nz-skeleton",7),e.YNc(9,qi,4,1,"div",8),e.TgZ(10,"div",9),e.YNc(11,ns,16,15,"div",10),e.qZA()()()(),e.YNc(12,is,1,2,"worklenz-phase-settings-drawer",11),e.YNc(13,ss,1,3,"worklenz-status-settings-drawer",12),e._UZ(14,"worklenz-context-menu",13)),2&t&&(e.xp6(7),e.Q6J("ngIf",n.templateId),e.xp6(1),e.Q6J("nzActive",!0)("nzLoading",n.loadingGroups),e.xp6(1),e.Q6J("ngIf",!n.groups.length),e.xp6(2),e.Q6J("rxForOf",n.groups)("rxForTrackBy",n.trackById),e.xp6(1),e.Q6J("ngIf",n.templateId),e.xp6(1),e.Q6J("ngIf",n.templateId),e.xp6(1),e.Q6J("templateId",n.templateId)("groups",n.groups))},dependencies:[h.sg,h.O5,h.RF,D.OK,P.Ls,O.ix,v.w,Q.ZU,H.NU,H.$1,$.ng,Ce.Wj,Re,fn._,Sn,Pn,gi,bi,Oi,Fi,Ui,Qi],styles:['.task-status-color[_ngcontent-%COMP%]{border-width:1.4px;border-style:solid;border-radius:4px;width:1px}.task-name-color[_ngcontent-%COMP%]{height:20px;margin:auto;border-color:#a9a9a9;background-color:#a9a9a9;position:absolute;top:0;bottom:0;left:0;border-width:1.2px}.editable-cell[_ngcontent-%COMP%]{white-space:nowrap;max-width:255px;overflow:hidden;text-overflow:ellipsis}nz-date-picker[_ngcontent-%COMP%]{background:transparent}.expanded[_ngcontent-%COMP%]{transform:rotate(-90deg)}.dropdown-highlight[_ngcontent-%COMP%]{padding:1px}.highlight-col[_ngcontent-%COMP%]{border:1px solid #1890ff!important}.dropdown-highlight[_ngcontent-%COMP%]:hover{background-color:#d0eefa54;border:#5587f5 1px solid;border-radius:3px}.pointer-text[_ngcontent-%COMP%]{cursor:text}.plus-icon[_ngcontent-%COMP%]{display:none;position:absolute;right:0;z-index:9;background-color:#e2e7ea}tr[_ngcontent-%COMP%]:hover .plus-icon[_ngcontent-%COMP%]{display:block}.selected[_ngcontent-%COMP%], .selected[_ngcontent-%COMP%] .task-name[_ngcontent-%COMP%], .selected[_ngcontent-%COMP%] .task-drag-handler[_ngcontent-%COMP%]{background:rgba(24,144,255,.0784313725)!important}.sub-task-background-color[_ngcontent-%COMP%]{background-color:#f5f5f58a}.drop-down-btn[_ngcontent-%COMP%] .anticon-caret-down[_ngcontent-%COMP%]{color:#000000a6}div[_ngcontent-%COMP%]{box-sizing:border-box}.flex-table[_ngcontent-%COMP%]{display:flex;width:max-content}.table-container[_ngcontent-%COMP%]{overflow:auto;display:flex}.tasks-table[_ngcontent-%COMP%]{width:max-content;margin-left:3px;border-right:1px solid #f0f0f0}.column-trigger[_ngcontent-%COMP%]{background-color:#f4f5f7;height:40px;width:40px;justify-content:center;display:flex;align-items:center}.header[_ngcontent-%COMP%]{margin-bottom:0;position:sticky;top:0;background-color:#fff;z-index:2}.header[_ngcontent-%COMP%] .flex-row[_ngcontent-%COMP%]{padding:4px 11px;background-color:#fafafa;border-top:1px solid #f0f0f0;border-bottom:1px solid #f0f0f0;border-right:1px solid #f0f0f0;display:flex;align-items:center;flex-direction:row}.br-right[_ngcontent-%COMP%]{border-right:1px solid #f0f0f0}.task-check[_ngcontent-%COMP%]{text-align:center;padding:8px 6px 8px 0!important;position:sticky;left:24px;z-index:1}.task-key[_ngcontent-%COMP%]{width:85px}.task-arrow[_ngcontent-%COMP%]{width:24px;padding:0!important;display:flex;align-items:center;border:none!important;position:sticky;left:47px;z-index:1}.task-name[_ngcontent-%COMP%]{width:450px;min-width:450px;position:sticky;left:71px;z-index:1;background-color:#fff}.task-name[_ngcontent-%COMP%] nz-filter-trigger[_ngcontent-%COMP%]{margin-left:auto}.task-description[_ngcontent-%COMP%]{width:225px}.task-progress[_ngcontent-%COMP%]{width:80px}.task-members[_ngcontent-%COMP%]{width:160px}.task-labels[_ngcontent-%COMP%]{width:210px}.task-status[_ngcontent-%COMP%], .task-priority[_ngcontent-%COMP%], .task-time-tracking[_ngcontent-%COMP%], .task-estimation[_ngcontent-%COMP%]{width:120px}.task-start-date[_ngcontent-%COMP%], .task-due-date[_ngcontent-%COMP%], .task-completed-date[_ngcontent-%COMP%], .task-created-date[_ngcontent-%COMP%], .task-update-date[_ngcontent-%COMP%]{width:150px}.task-drag-handler[_ngcontent-%COMP%]{padding:0 0 0 4px!important;width:24px;border-bottom:1px solid #f0f0f0;border-right:none!important;position:sticky;left:0}.inner-subtask-create[_ngcontent-%COMP%]{height:42px;display:flex;align-items:center;max-width:1316px;overflow:hidden;background-color:#fafafa;position:sticky;left:0;border-bottom:1px solid #eaeaea;border-top:1px solid #eaeaea}@media (max-width: 1400px){.inner-subtask-create[_ngcontent-%COMP%]{max-width:1136px}}@media (max-width: 1200px){.inner-subtask-create[_ngcontent-%COMP%]{max-width:956px}}.inner-subtask-create.highlight-col[_ngcontent-%COMP%]{background-color:#fff}.new-subtask-divider[_ngcontent-%COMP%]{width:50px}.new-subtask-divider.divider-large[_ngcontent-%COMP%]{width:135px}worklenz-quick-task[_ngcontent-%COMP%]{display:block}.overflow-x-auto[_ngcontent-%COMP%]{overflow-x:auto;overflow-y:hidden}.panel[_ngcontent-%COMP%]{padding:0;background-color:#fff;max-height:calc(100% + 8px);overflow:hidden;transition:max-height .1s ease-out;border-right:1px solid #f0f0f0}.panel-left-border[_ngcontent-%COMP%]{position:absolute;content:"";top:0;bottom:0;width:3px;z-index:3;border-bottom-left-radius:4px}.collapse.btn[_ngcontent-%COMP%] .collapse-icon[_ngcontent-%COMP%]{transition:transform .1s;transform:rotate(0)}.collapse.btn.active[_ngcontent-%COMP%] .collapse-icon[_ngcontent-%COMP%]{transition:transform .1s;transform:rotate(90deg)}.drop-down-btn[_ngcontent-%COMP%]{padding:4px 11px}.drop-down-btn[_ngcontent-%COMP%] .anticon-caret-down[_ngcontent-%COMP%]{color:#d9d9d9}.status-color[_ngcontent-%COMP%]{width:11px;height:11px;border-radius:2px;margin-right:7px}.drop-down-btn[_ngcontent-%COMP%] nz-badge[_ngcontent-%COMP%]{margin-top:-2px}.drop-down-btn.active[_ngcontent-%COMP%]{color:#1890ff;border-color:#1890ff;background-color:#e6f7ff}.drop-down-btn.active[_ngcontent-%COMP%] .drop-down-btn[_ngcontent-%COMP%] .anticon-caret-down[_ngcontent-%COMP%]{color:#1890ff}.ant-badge-count-sm[_ngcontent-%COMP%]{font-size:11px}.tab-name-edit[_ngcontent-%COMP%]{overflow:hidden}.tab-name-edit[_ngcontent-%COMP%] span[_ngcontent-%COMP%]{margin-right:0;font-size:14px}.tab-name-edit[_ngcontent-%COMP%] svg[_ngcontent-%COMP%]{margin-right:0}.new-task-input[_ngcontent-%COMP%]{padding-left:4px;padding-top:4px;padding-bottom:4px;border-bottom:1px solid #f0f0f0;border-top:1px solid #f0f0f0;max-width:1316px;overflow-x:auto}@media (max-width: 1400px){.new-task-input[_ngcontent-%COMP%]{max-width:1136px}}@media (max-width: 1200px){.new-task-input[_ngcontent-%COMP%]{max-width:956px}}.tasks-empty-placeholder[_ngcontent-%COMP%]{width:100%;height:42px;background:#fafafa}.no-data-img-holder[_ngcontent-%COMP%]{width:100px;margin-top:42px}.add-field-button[_ngcontent-%COMP%]{position:absolute;top:46px;right:0}.name-input[_ngcontent-%COMP%]{max-width:1270px;width:1270px;display:block}@media (max-width: 1400px){.name-input[_ngcontent-%COMP%]{max-width:1090px;width:1090px}}@media (max-width: 1200px){.name-input[_ngcontent-%COMP%]{max-width:910px;width:910px}}'],changeDetection:0}),a})()}];let rs=(()=>{var i;class a{}return(i=a).\u0275fac=function(t){return new(t||i)},i.\u0275mod=e.oAB({type:i}),i.\u0275inj=e.cJS({imports:[N.Bz.forChild(as),N.Bz]}),a})();var ls=c(3626),cs=c(79382),ps=c(82669),ds=c(62831),us=c(48128),_s=c(64345),ms=c(49388);let _o=(()=>{var i;class a{}return(i=a).\u0275fac=function(t){return new(t||i)},i.\u0275mod=e.oAB({type:i}),i.\u0275inj=e.cJS({imports:[ms.vT,h.ez,p.u5,ds.ud,J.cg,us.W,_s.YI,P.PV,O.sL]}),a})();var mo=c(48522),ho=c(29666),go=c(52682);let fo=(()=>{var i;class a{}return(i=a).\u0275fac=function(t){return new(t||i)},i.\u0275mod=e.oAB({type:i}),i.\u0275inj=e.cJS({providers:[Oe.g],imports:[h.ez,rs,D.wm,V.vh,Y.KJ,ls.lt,P.PV,cs.we,p.UX,x.U5,M.o7,O.sL,ps.j,z.HQ,fe.Qp,Q.ZJ,T.ip,H.zf,J.cg,$.H0,K.LV,p.u5,_o,Pe.l,ie.mS,E.b1,ze.X,Je.x,Te._p,Ge.S,De.Rt,mo.a,ho.V,we.Wr,Re,go.Hb,$e.YS,je.o,He.PG,qe.Zf,ke.BL,Fe.S,Ye.x]}),a})()},80697:(ye,se,c)=>{c.d(se,{I:()=>U});var h=c(26857),N=c(10708),e=c(65879),w=c(69862);let U=(()=>{var D;class Y extends h.P{constructor(T){super(),this.http=T,this.root=`${this.API_BASE_URL}/project-templates`}createCustomTemplate(T){return this._post(this.http,`${this.root}/custom-template`,T)}createFromTemplate(T){return this._post(this.http,`${this.root}/import-template`,T)}createFromCustomTemplate(T){return this._post(this.http,`${this.root}/import-custom-template`,T)}createTemplates(){return this._get(this.http,`${this.root}/create`)}getWorklenzTemplates(){return this._get(this.http,`${this.root}/worklenz-templates`)}getWorklenzTemplateById(T){return this._get(this.http,`${this.root}/worklenz-templates/${T}`)}getWorklenzCustomTemplates(){return this._get(this.http,`${this.root}/custom-templates`)}delete(T){return(0,N.n)(this.http.delete(`${this.root}/${T}`))}setupAccount(T){return this._post(this.http,`${this.root}/setup`,T)}}return(D=Y).\u0275fac=function(T){return new(T||D)(e.LFG(w.eN))},D.\u0275prov=e.Yz7({token:D,factory:D.\u0275fac,providedIn:"root"}),Y})()},26254:(ye,se,c)=>{c.d(se,{$:()=>L,$O:()=>ge,Jp:()=>O,KJ:()=>Me,u9:()=>P,yG:()=>Z});var h=c(49388),N=c(96814),e=c(65879),w=c(8324),U=c(62595),D=c(97582),Y=c(78645),v=c(59773),T=c(37398),re=c(40874),X=c(1608),ee=c(28802);function ve(u,g){if(1&u&&(e.ynx(0),e._UZ(1,"span",9),e.BQk()),2&u){const C=g.$implicit,f=e.oxw(2);e.xp6(1),e.Q6J("nzType",C||f.getBackIcon())}}function m(u,g){if(1&u){const C=e.EpF();e.TgZ(0,"div",6),e.NdJ("click",function(){e.CHM(C);const F=e.oxw();return e.KtG(F.onBack())}),e.TgZ(1,"div",7),e.YNc(2,ve,2,1,"ng-container",8),e.qZA()()}if(2&u){const C=e.oxw();e.xp6(2),e.Q6J("nzStringTemplateOutlet",C.nzBackIcon)}}function p(u,g){if(1&u&&(e.ynx(0),e._uU(1),e.BQk()),2&u){const C=e.oxw(2);e.xp6(1),e.Oqu(C.nzTitle)}}function me(u,g){if(1&u&&(e.TgZ(0,"span",10),e.YNc(1,p,2,1,"ng-container",8),e.qZA()),2&u){const C=e.oxw();e.xp6(1),e.Q6J("nzStringTemplateOutlet",C.nzTitle)}}function b(u,g){1&u&&e.Hsn(0,6,["*ngIf","!nzTitle"])}function A(u,g){if(1&u&&(e.ynx(0),e._uU(1),e.BQk()),2&u){const C=e.oxw(2);e.xp6(1),e.Oqu(C.nzSubtitle)}}function I(u,g){if(1&u&&(e.TgZ(0,"span",11),e.YNc(1,A,2,1,"ng-container",8),e.qZA()),2&u){const C=e.oxw();e.xp6(1),e.Q6J("nzStringTemplateOutlet",C.nzSubtitle)}}function he(u,g){1&u&&e.Hsn(0,7,["*ngIf","!nzSubtitle"])}const Se=[[["nz-breadcrumb","nz-page-header-breadcrumb",""]],[["nz-avatar","nz-page-header-avatar",""]],[["nz-page-header-tags"],["","nz-page-header-tags",""]],[["nz-page-header-extra"],["","nz-page-header-extra",""]],[["nz-page-header-content"],["","nz-page-header-content",""]],[["nz-page-header-footer"],["","nz-page-header-footer",""]],[["nz-page-header-title"],["","nz-page-header-title",""]],[["nz-page-header-subtitle"],["","nz-page-header-subtitle",""]]],V=["nz-breadcrumb[nz-page-header-breadcrumb]","nz-avatar[nz-page-header-avatar]","nz-page-header-tags, [nz-page-header-tags]","nz-page-header-extra, [nz-page-header-extra]","nz-page-header-content, [nz-page-header-content]","nz-page-header-footer, [nz-page-header-footer]","nz-page-header-title, [nz-page-header-title]","nz-page-header-subtitle, [nz-page-header-subtitle]"];let P=(()=>{var u;class g{}return(u=g).\u0275fac=function(f){return new(f||u)},u.\u0275dir=e.lG2({type:u,selectors:[["nz-page-header-title"],["","nz-page-header-title",""]],hostAttrs:[1,"ant-page-header-heading-title"],exportAs:["nzPageHeaderTitle"]}),g})(),Z=(()=>{var u;class g{}return(u=g).\u0275fac=function(f){return new(f||u)},u.\u0275dir=e.lG2({type:u,selectors:[["nz-page-header-subtitle"],["","nz-page-header-subtitle",""]],hostAttrs:[1,"ant-page-header-heading-sub-title"],exportAs:["nzPageHeaderSubtitle"]}),g})(),O=(()=>{var u;class g{}return(u=g).\u0275fac=function(f){return new(f||u)},u.\u0275dir=e.lG2({type:u,selectors:[["nz-page-header-extra"],["","nz-page-header-extra",""]],hostAttrs:[1,"ant-page-header-heading-extra"],exportAs:["nzPageHeaderExtra"]}),g})(),L=(()=>{var u;class g{}return(u=g).\u0275fac=function(f){return new(f||u)},u.\u0275dir=e.lG2({type:u,selectors:[["nz-page-header-footer"],["","nz-page-header-footer",""]],hostAttrs:[1,"ant-page-header-footer"],exportAs:["nzPageHeaderFooter"]}),g})(),Q=(()=>{var u;class g{}return(u=g).\u0275fac=function(f){return new(f||u)},u.\u0275dir=e.lG2({type:u,selectors:[["nz-breadcrumb","nz-page-header-breadcrumb",""]],exportAs:["nzPageHeaderBreadcrumb"]}),g})(),ge=(()=>{var u;class g{constructor(f,F,pe,te,z,fe){this.location=f,this.nzConfigService=F,this.elementRef=pe,this.nzResizeObserver=te,this.cdr=z,this.directionality=fe,this._nzModuleName="pageHeader",this.nzBackIcon=null,this.nzGhost=!0,this.nzBack=new e.vpe,this.compact=!1,this.destroy$=new Y.x,this.dir="ltr"}ngOnInit(){this.directionality.change?.pipe((0,v.R)(this.destroy$)).subscribe(f=>{this.dir=f,this.cdr.detectChanges()}),this.dir=this.directionality.value}ngAfterViewInit(){this.nzResizeObserver.observe(this.elementRef).pipe((0,T.U)(([f])=>f.contentRect.width),(0,v.R)(this.destroy$)).subscribe(f=>{this.compact=f<768,this.cdr.markForCheck()})}onBack(){if(this.nzBack.observers.length)this.nzBack.emit();else{if(!this.location)throw new Error(`${X.Bq} you should import 'RouterModule' or register 'Location' if you want to use 'nzBack' default event!`);this.location.back()}}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}getBackIcon(){return"rtl"===this.dir?"arrow-right":"arrow-left"}}return(u=g).\u0275fac=function(f){return new(f||u)(e.Y36(N.Ye,8),e.Y36(re.jY),e.Y36(e.SBq),e.Y36(ee.D3),e.Y36(e.sBO),e.Y36(h.Is,8))},u.\u0275cmp=e.Xpm({type:u,selectors:[["nz-page-header"]],contentQueries:function(f,F,pe){if(1&f&&(e.Suo(pe,L,5),e.Suo(pe,Q,5)),2&f){let te;e.iGM(te=e.CRH())&&(F.nzPageHeaderFooter=te.first),e.iGM(te=e.CRH())&&(F.nzPageHeaderBreadcrumb=te.first)}},hostAttrs:[1,"ant-page-header"],hostVars:10,hostBindings:function(f,F){2&f&&e.ekj("has-footer",F.nzPageHeaderFooter)("ant-page-header-ghost",F.nzGhost)("has-breadcrumb",F.nzPageHeaderBreadcrumb)("ant-page-header-compact",F.compact)("ant-page-header-rtl","rtl"===F.dir)},inputs:{nzBackIcon:"nzBackIcon",nzTitle:"nzTitle",nzSubtitle:"nzSubtitle",nzGhost:"nzGhost"},outputs:{nzBack:"nzBack"},exportAs:["nzPageHeader"],ngContentSelectors:V,decls:13,vars:5,consts:[[1,"ant-page-header-heading"],[1,"ant-page-header-heading-left"],["class","ant-page-header-back",3,"click",4,"ngIf"],["class","ant-page-header-heading-title",4,"ngIf"],[4,"ngIf"],["class","ant-page-header-heading-sub-title",4,"ngIf"],[1,"ant-page-header-back",3,"click"],["role","button","tabindex","0",1,"ant-page-header-back-button"],[4,"nzStringTemplateOutlet"],["nz-icon","","nzTheme","outline",3,"nzType"],[1,"ant-page-header-heading-title"],[1,"ant-page-header-heading-sub-title"]],template:function(f,F){1&f&&(e.F$t(Se),e.Hsn(0),e.TgZ(1,"div",0)(2,"div",1),e.YNc(3,m,3,1,"div",2),e.Hsn(4,1),e.YNc(5,me,2,1,"span",3),e.YNc(6,b,1,0,"ng-content",4),e.YNc(7,I,2,1,"span",5),e.YNc(8,he,1,0,"ng-content",4),e.Hsn(9,2),e.qZA(),e.Hsn(10,3),e.qZA(),e.Hsn(11,4),e.Hsn(12,5)),2&f&&(e.xp6(3),e.Q6J("ngIf",null!==F.nzBackIcon),e.xp6(2),e.Q6J("ngIf",F.nzTitle),e.xp6(1),e.Q6J("ngIf",!F.nzTitle),e.xp6(1),e.Q6J("ngIf",F.nzSubtitle),e.xp6(1),e.Q6J("ngIf",!F.nzSubtitle))},dependencies:[N.O5,w.f,U.Ls],encapsulation:2,changeDetection:0}),(0,D.gn)([(0,re.oS)()],g.prototype,"nzGhost",void 0),g})(),Me=(()=>{var u;class g{}return(u=g).\u0275fac=function(f){return new(f||u)},u.\u0275mod=e.oAB({type:u}),u.\u0275inj=e.cJS({imports:[h.vT,N.ez,w.T,U.PV]}),g})()}}]); \ No newline at end of file diff --git a/worklenz-backend/src/public/152.89824b7edf9e2b3f.js b/worklenz-backend/src/public/152.89824b7edf9e2b3f.js deleted file mode 100644 index ce8b9a4d..00000000 --- a/worklenz-backend/src/public/152.89824b7edf9e2b3f.js +++ /dev/null @@ -1,129 +0,0 @@ -(self.webpackChunkworklenz=self.webpackChunkworklenz||[]).push([[152],{79152:(bm,ao,oe)=>{"use strict";oe.r(ao),oe.d(ao,{ProjectsModule:()=>ZC});var Ie=oe(96814),wt=oe(35420),ce=oe(15861),e=oe(65879),Ot=oe(21406),yr=oe(13490),st=oe(69649),pt=oe(86408),Ve=oe(27782),Pe=oe(60095),Pt=oe(71993),ra=oe(55012),vd=oe(97192),hn=oe(10095),mn=oe(3599),nA=oe(20824),Vt=oe(42840),Wt=oe(70855),an=oe(41958),AA=oe(55695),QA=oe(82669),lA=oe(43389),zn=oe(73460),cn=oe(62787);const Mc=["nameInput"];function $h(r,i){if(1&r){const A=e.EpF();e.TgZ(0,"li",14),e.NdJ("click",function(){const s=e.CHM(A).$implicit,o=e.oxw(2);return e.KtG(o.setColorCode(s))}),e.TgZ(1,"nz-tag",15),e._uU(2,"\xa0 "),e.qZA()()}if(2&r){const A=i.$implicit;e.xp6(1),e.Q6J("nzColor",A)}}function ep(r,i){if(1&r){const A=e.EpF();e.ynx(0),e.TgZ(1,"nz-spin",2)(2,"form",3),e.NdJ("submit",function(){e.CHM(A);const n=e.oxw();return e.KtG(n.submit())}),e.TgZ(3,"nz-form-item")(4,"nz-form-label",4),e._uU(5,"Name"),e.qZA(),e.TgZ(6,"nz-form-control",5),e._UZ(7,"input",6,7),e.qZA()(),e.TgZ(9,"nz-form-item")(10,"nz-form-label",8),e._uU(11,"Folder Color "),e.TgZ(12,"nz-tag",9),e._uU(13,"\xa0 "),e.qZA()(),e.TgZ(14,"nz-dropdown-menu",null,10)(16,"ul",11),e.YNc(17,$h,3,1,"li",12),e.qZA()()(),e.TgZ(18,"button",13),e._uU(19),e.qZA()()(),e.BQk()}if(2&r){const A=e.MAs(15),t=e.oxw();e.xp6(1),e.Q6J("nzSpinning",t.loading),e.xp6(1),e.Q6J("formGroup",t.form)("nzLayout","vertical"),e.xp6(2),e.Q6J("nzSpan",null),e.xp6(2),e.Q6J("nzSpan",null)("nzErrorTip","Please enter a name!"),e.xp6(1),e.Q6J("formControlName","name"),e.xp6(3),e.Q6J("nzSpan",null),e.xp6(2),e.Q6J("nzColor",t.activeColor)("nzDropdownMenu",A)("nzTrigger","click"),e.xp6(5),e.Q6J("ngForOf",t.COLOR_CODES),e.xp6(1),e.Q6J("nzType","primary")("disabled",!t.valid),e.xp6(1),e.Oqu(t.buttonText)}}let Cd=(()=>{var r;class i{get activeColor(){return this.form.controls.color_code.value}get title(){return this.folderId?"Update the folder":"Create a folder"}get buttonText(){return this.folderId?"Update":"Create"}get valid(){return this.form.valid}constructor(t,n,s,o,h){this.ngZone=t,this.cdr=n,this.fb=s,this.api=o,this.service=h,this.folderId=null,this.show=!1,this.loading=!1,this.COLOR_CODES=Ot.lD,this.createCallback=null,this.form=this.fb.group({name:[null,[Pe.kI.required]],color_code:[null]}),this.service.onCreateInvoke.pipe((0,Pt.sL)()).subscribe(p=>{this.createCallback=p,this.open()})}open(){this.show=!0,this.cdr.markForCheck()}close(){this.show=!1,this.form.reset({name:null,color_code:null}),this.invokeCreate(),this.cdr.markForCheck()}invokeCreate(t){this.createCallback&&(this.createCallback(t),this.createCallback=null)}submit(){var t=this;return(0,ce.Z)(function*(){if(!t.form.invalid){try{t.loading=!0;const n={name:t.form.value.name,color_code:t.form.value.color_code},s=yield t.api.create(n);s.done&&(t.invokeCreate(s.body),t.close()),t.loading=!1}catch{t.loading=!1}t.cdr.markForCheck()}})()}setColorCode(t){this.form.controls.color_code.setValue(t)}onVisibilityChange(t){t&&this.setFocusToNameInput()}setFocusToNameInput(){this.ngZone.runOutsideAngular(()=>{setTimeout(()=>{const t=this.nameInput.nativeElement;t&&t.focus()},100)})}}return(r=i).\u0275fac=function(t){return new(t||r)(e.Y36(e.R0b),e.Y36(e.sBO),e.Y36(Pe.qu),e.Y36(ra.Y),e.Y36(vd.I))},r.\u0275cmp=e.Xpm({type:r,selectors:[["worklenz-projects-folder-form-drawer"]],viewQuery:function(t,n){if(1&t&&e.Gf(Mc,5),2&t){let s;e.iGM(s=e.CRH())&&(n.nameInput=s.first)}},inputs:{folderId:"folderId"},decls:2,vars:4,consts:[[3,"nzClosable","nzTitle","nzVisible","nzPlacement","nzOnClose","nzVisibleChange"],[4,"nzDrawerContent"],[3,"nzSpinning"],["nz-form","",3,"formGroup","nzLayout","submit"],["nzRequired","",3,"nzSpan"],[3,"nzSpan","nzErrorTip"],["nz-input","","placeholder","Enter your folder name",3,"formControlName"],["nameInput",""],["nzRequired","",1,"pb-0",3,"nzSpan"],["nz-dropdown","",1,"ms-2","rounded-circle","cursor-pointer",2,"width","20px","height","20px",3,"nzColor","nzDropdownMenu","nzTrigger"],["menu","nzDropdownMenu"],["nz-menu","","nzSelectable","",2,"max-height","200px","overflow","hidden","overflow-y","auto"],["nz-menu-item","",3,"click",4,"ngFor","ngForOf"],["nz-button","","nzBlock","","type","submit",3,"nzType","disabled"],["nz-menu-item","",3,"click"],[1,"me-1","w-100","rounded-pill",2,"height","16px !important","width","16px !important",3,"nzColor"]],template:function(t,n){1&t&&(e.TgZ(0,"nz-drawer",0),e.NdJ("nzOnClose",function(){return n.close()})("nzVisibleChange",function(o){return n.onVisibilityChange(o)}),e.YNc(1,ep,20,15,"ng-container",1),e.qZA()),2&t&&e.Q6J("nzClosable",!0)("nzTitle",n.title)("nzVisible",n.show)("nzPlacement","right")},dependencies:[Ie.sg,Pe._Y,Pe.Fj,Pe.JJ,Pe.JL,Pe.sg,Pe.u,hn.t3,hn.SK,mn.Lr,mn.Nx,mn.iK,mn.Fd,nA.Zp,Vt.ix,Wt.w,an.dQ,AA.j,QA.W,lA.Vz,lA.SQ,zn.wO,zn.r9,cn.cm,cn.RR],changeDetection:0}),i})();var Ec=oe(16406),ia=oe(90586),ji=oe(31215),yn=oe(32333),JA=oe(64532),Bd=oe(53980),Lc=oe(80697),YA=oe(55416),tp=oe(52732),Tr=oe(26254),xt=oe(13740),zt=oe(62595),Ut=oe(8083),qA=oe(62612),ms=oe(48128),Zt=oe(96109),Mn=oe(24139),DA=oe(82962),HA=oe(92574),fr=oe(33640),Ri=oe(22114),Nc=oe(98421),FA=oe(99183),VA=oe(76271);let bd=(()=>{var r;class i{transform(t){return`Click to filter by "${t}".`}}return(r=i).\u0275fac=function(t){return new(t||r)},r.\u0275pipe=e.Yjl({name:"projectFilterByTooltip",type:r,pure:!0}),i})();const Oc=["displayModeTemplate"];function np(r,i){if(1&r){const A=e.EpF();e.TgZ(0,"button",25),e.NdJ("click",function(){e.CHM(A);const n=e.oxw();return e.KtG(n.refresh())}),e._UZ(1,"span",26),e.qZA()}if(2&r){const A=e.oxw();e.Q6J("nzShape","circle")("nzTooltipTitle","Refresh projects")("nzType","default"),e.xp6(1),e.Q6J("nzSpin",A.loading)("nzTheme","outline")("nzType","sync")}}function co(r,i){if(1&r){const A=e.EpF();e.TgZ(0,"nz-segmented",27),e.NdJ("ngModelChange",function(n){e.CHM(A);const s=e.oxw();return e.KtG(s.filterIndex=n)})("nzValueChange",function(n){e.CHM(A);const s=e.oxw();return e.KtG(s.onFilterChange(n))}),e.qZA()}if(2&r){const A=e.oxw();e.Q6J("ngModel",A.filterIndex)("nzOptions",A.filters)}}function Pc(r,i){1&r&&e._UZ(0,"span",32)}function sa(r,i){if(1&r){const A=e.EpF();e.TgZ(0,"form",28),e.NdJ("ngSubmit",function(){e.CHM(A);const n=e.oxw();return e.KtG(n.searchProjects())}),e.TgZ(1,"nz-input-group",29),e._UZ(2,"input",30),e.qZA(),e.YNc(3,Pc,1,0,"ng-template",null,31,e.W1O),e.qZA()}if(2&r){const A=e.MAs(4),t=e.oxw();e.Q6J("formGroup",t.searchForm)("nzLayout","vertical"),e.xp6(1),e.Q6J("nzSuffix",A),e.xp6(1),e.Q6J("formControlName","search")}}function lo(r,i){if(1&r){const A=e.EpF();e.TgZ(0,"button",40),e.NdJ("click",function(){e.CHM(A);const n=e.oxw(3);return e.KtG(n.openProjectForm())}),e._UZ(1,"span",41),e._uU(2,"Create Project"),e.qZA()}2&r&&e.Q6J("nzType","primary")}function Ap(r,i){if(1&r&&(e.TgZ(0,"button",42),e._UZ(1,"span",43),e.qZA()),2&r){e.oxw(2);const A=e.MAs(3);e.Q6J("nzType","primary")("nzDropdownMenu",A)("nzTrigger","click")("nzPlacement","bottomRight")}}function xd(r,i){if(1&r&&(e.TgZ(0,"span")(1,"nz-button-group"),e.YNc(2,lo,3,1,"button",38),e.YNc(3,Ap,2,4,"button",39),e.qZA()()),2&r){const A=e.oxw(2);e.xp6(2),e.Q6J("ngIf",A.isOwnerOrAdmin()),e.xp6(1),e.Q6J("ngIf",A.isOwnerOrAdmin())}}function Dc(r,i){if(1&r){const A=e.EpF();e.ynx(0),e.YNc(1,xd,4,2,"span",33),e.TgZ(2,"nz-dropdown-menu",null,34)(4,"ul",35)(5,"li",36),e.NdJ("click",function(){e.CHM(A);const n=e.oxw();return e.KtG(n.openTemplateSelector())}),e._UZ(6,"span",37),e._uU(7," Create from template "),e.qZA()()(),e.BQk()}}function uo(r,i){if(1&r&&(e.TgZ(0,"div"),e._uU(1),e.ALo(2,"date"),e.qZA()),2&r){const A=e.oxw(3).$implicit;e.xp6(1),e.hij("Start date : ",e.xi3(2,1,A.start_date,"mediumDate"),"")}}function Hc(r,i){if(1&r&&(e.TgZ(0,"div"),e._uU(1),e.ALo(2,"date"),e.qZA()),2&r){const A=e.oxw(3).$implicit;e.xp6(1),e.hij("End date : ",e.xi3(2,1,A.end_date,"mediumDate"),"")}}function _s(r,i){if(1&r&&(e.YNc(0,uo,3,4,"div",5),e.YNc(1,Hc,3,4,"div",5)),2&r){const A=e.oxw(2).$implicit;e.Q6J("ngIf",A.start_date),e.xp6(1),e.Q6J("ngIf",A.end_date)}}function ZA(r,i){if(1&r&&(e.ynx(0),e.TgZ(1,"span",60),e._UZ(2,"span",12),e.qZA(),e.YNc(3,_s,2,2,"ng-template",null,61,e.W1O),e.BQk()),2&r){const A=e.MAs(4);e.xp6(1),e.Q6J("nzType","secondary")("nzTooltipTitle",A),e.xp6(1),e.Q6J("nzType","calendar")("nzTheme","outline")}}function ho(r,i){if(1&r){const A=e.EpF();e.TgZ(0,"nz-tag",62),e.NdJ("click",function(n){e.CHM(A);const s=e.oxw().$implicit,o=e.oxw();return n.stopPropagation(),e.KtG(o.filterByCategory(s.category_id))}),e.ALo(1,"projectFilterByTooltip"),e._uU(2),e.qZA()}if(2&r){const A=e.oxw().$implicit;e.Q6J("nzTooltipTitle",e.lcZ(1,3,A.category_name))("nzColor",A.category_color),e.xp6(2),e.Oqu(A.category_name)}}function po(r,i){1&r&&(e.ynx(0),e._uU(1,"-"),e.BQk())}function ws(r,i){1&r&&(e.ynx(0),e._uU(1," N/A "),e.BQk())}const yd=function(){return[]};function Gi(r,i){if(1&r&&e._UZ(0,"worklenz-avatars",63),2&r){const A=e.oxw().$implicit;e.Q6J("names",A.names||e.DdM(1,yd))}}function rp(r,i){if(1&r){const A=e.EpF();e.TgZ(0,"button",67),e.NdJ("click",function(n){e.CHM(A);const s=e.oxw(2).$implicit,o=e.oxw();return n.stopPropagation(),e.KtG(o.openProjectForm(s.id))}),e._UZ(1,"span",53),e.qZA()}2&r&&(e.Q6J("nzSize","small")("nzTooltipPlacement","top")("nzTooltipTitle","Settings")("nzType","default"),e.xp6(1),e.Q6J("nzType","setting"))}function oa(r,i){if(1&r){const A=e.EpF();e.TgZ(0,"button",68),e.NdJ("click",function(n){e.CHM(A);const s=e.oxw(2).$implicit,o=e.oxw();return n.stopPropagation(),e.KtG(o.toggleArchive(s))}),e._UZ(1,"span",12),e.qZA()}if(2&r){const A=e.oxw(2).$implicit;e.Q6J("nzSize","small")("nzTooltipPlacement","top")("nzTooltipTitle",A.archived?"Remove from archive":"Archive")("nzLoading",A.loading)("nzType","default"),e.xp6(1),e.Q6J("nzType","inbox")("nzTheme","outline")}}function aa(r,i){1&r&&(e.TgZ(0,"div",64)(1,"nz-space"),e.YNc(2,rp,2,5,"button",65),e.YNc(3,oa,2,7,"button",66),e.qZA()())}function Zc(r,i){if(1&r){const A=e.EpF();e.TgZ(0,"tr",44),e.NdJ("click",function(){const s=e.CHM(A).$implicit,o=e.oxw();return e.KtG(o.selectProject(s.id,s.team_member_default_view?s.team_member_default_view:"TASK_LIST"))}),e.TgZ(1,"td",45)(2,"div",46)(3,"nz-rate",47),e.NdJ("click",function(n){const o=e.CHM(A).$implicit,h=e.oxw();return n.stopPropagation(),e.KtG(h.toggleFavorite(o.id))}),e.qZA(),e.TgZ(4,"span",48),e._UZ(5,"nz-badge",49),e._uU(6),e.YNc(7,ZA,5,4,"ng-container",5),e.qZA()()(),e.TgZ(8,"td"),e._uU(9),e.qZA(),e.TgZ(10,"td"),e.ynx(11,50),e.YNc(12,ho,3,5,"nz-tag",51),e.YNc(13,po,2,0,"ng-container",52),e.BQk(),e.qZA(),e.TgZ(14,"td"),e._UZ(15,"span",53),e.ALo(16,"safeString"),e._uU(17),e.qZA(),e.TgZ(18,"td"),e._UZ(19,"nz-progress",54),e.qZA(),e.TgZ(20,"td")(21,"span",55),e.ALo(22,"date"),e._uU(23),e.qZA()(),e.TgZ(24,"td"),e.YNc(25,ws,2,0,"ng-container",56),e.YNc(26,Gi,1,2,"ng-template",null,57,e.W1O),e.qZA(),e.TgZ(28,"td",58),e.YNc(29,aa,4,0,"div",59),e.qZA()()}if(2&r){const A=i.$implicit,t=e.MAs(27),n=e.oxw();e.xp6(3),e.Q6J("nzTooltipTitle",A.favorite?"Remove from favorites":"Add to favorites")("nzCount",1)("ngModel",A.favorite?1:0),e.xp6(2),e.Q6J("nzColor",A.color_code)("nzSize","default"),e.xp6(1),e.hij(" ",A.name," "),e.xp6(1),e.Q6J("ngIf",A.start_date||A.end_date),e.xp6(2),e.Oqu(A.client_name||"-"),e.xp6(2),e.Q6J("ngSwitch",!!A.category_name),e.xp6(1),e.Q6J("ngSwitchCase",!0),e.xp6(1),e.Q6J("ngSwitchCase",!1),e.xp6(2),e.Udp("color",A.status_color),e.Q6J("nzType",e.lcZ(16,25,A.status_icon)),e.xp6(2),e.hij(" ",A.status," "),e.xp6(2),e.Q6J("nzPercent",A.progress)("nzStrokeColor",A.color_code)("nzTooltipTitle",n.getTaskProgressTitle(A)),e.xp6(2),e.Q6J("nzTooltipTitle",e.xi3(22,27,A.updated_at,"medium"))("nzTooltipMouseEnterDelay",1),e.xp6(2),e.Oqu(A.updated_at_string),e.xp6(2),e.Q6J("ngIf",!(null!=A.names&&A.names.length))("ngIfElse",t),e.xp6(3),e.Q6J("nzAlign","center"),e.xp6(1),e.Q6J("ngIf",n.isOwnerOrAdmin())}}function jc(r,i){1&r&&(e.ynx(0),e._uU(1,"The team currently has no active projects."),e.BQk())}function Rc(r,i){1&r&&(e.ynx(0),e._uU(1,"You're yet to mark any projects as favorites."),e.BQk())}function fo(r,i){1&r&&(e.ynx(0),e._uU(1,"The selected team has no archived projects."),e.BQk())}function Gc(r,i){if(1&r&&(e.TgZ(0,"div",69)(1,"div",70),e._UZ(2,"img",71),e.qZA(),e.TgZ(3,"div",72),e.YNc(4,jc,2,0,"ng-container",52),e.YNc(5,Rc,2,0,"ng-container",52),e.YNc(6,fo,2,0,"ng-container",52),e.qZA()()),2&r){const A=e.oxw();e.xp6(3),e.Q6J("ngSwitch",A.filterIndex),e.xp6(1),e.Q6J("ngSwitchCase",0),e.xp6(1),e.Q6J("ngSwitchCase",1),e.xp6(1),e.Q6J("ngSwitchCase",2)}}function ca(r,i){if(1&r){const A=e.EpF();e.TgZ(0,"li",74),e.NdJ("nzCheckedChange",function(n){const o=e.CHM(A).$implicit;return e.KtG(o.selected=n)})("nzCheckedChange",function(){e.CHM(A);const n=e.oxw(2);return e.KtG(n.onCategoryFilterChange())}),e._uU(1),e.qZA()}if(2&r){const A=i.$implicit;e.Q6J("nzChecked",A.selected),e.xp6(1),e.Oqu(A.name)}}function la(r,i){if(1&r&&(e.ynx(0),e.YNc(1,ca,2,2,"li",73),e.BQk()),2&r){const A=e.oxw();e.xp6(1),e.Q6J("ngForOf",A.categories)}}function Kc(r,i){1&r&&(e.TgZ(0,"li",75),e._uU(1,"No categories found"),e.qZA())}function Td(r,i){if(1&r){const A=e.EpF();e.TgZ(0,"li",74),e.NdJ("nzCheckedChange",function(n){const o=e.CHM(A).$implicit;return e.KtG(o.selected=n)})("nzCheckedChange",function(){e.CHM(A);const n=e.oxw(2);return e.KtG(n.onStatusFilterChange())}),e._uU(1),e.qZA()}if(2&r){const A=i.$implicit;e.Q6J("nzChecked",A.selected),e.xp6(1),e.Oqu(A.name)}}function ip(r,i){if(1&r&&(e.ynx(0),e.YNc(1,Td,2,2,"li",73),e.BQk()),2&r){const A=e.oxw();e.xp6(1),e.Q6J("ngForOf",A.statuses)}}function sp(r,i){1&r&&(e.TgZ(0,"li",75),e._uU(1,"No categories found"),e.qZA())}const kd=function(){return{rows:7}};let jt=(()=>{var r;class i{get filterIndex(){return+(localStorage.getItem(this.FILTER_INDEX_KEY)||0)}set filterIndex(t){localStorage.setItem(this.FILTER_INDEX_KEY,t.toString())}get displayAs(){return+(localStorage.getItem(this.DISPLAY_MODE_KEY)||0)}set displayAs(t){localStorage.setItem(this.DISPLAY_MODE_KEY,t.toString())}get profile(){return this.auth.getCurrentSession()}constructor(t,n,s,o,h,p,_,B,x,y,v,M){this.app=t,this.api=n,this.fb=s,this.auth=o,this.router=h,this.route=p,this.utilsService=_,this.categoriesApi=B,this.templatesApi=x,this.cdr=y,this.list=v,this.statusesApi=M,this.FILTER_INDEX_KEY="worklenz.projects.filter_index",this.DISPLAY_MODE_KEY="worklenz.projects.display_as",this.projectsModel={},this.categories=[],this.statuses=[],this.expandSet=new Set,this.total=0,this.pageSize=Ot.L8,this.pageIndex=1,this.paginationSizes=[5,10,15,20,50,100],this.sortField=null,this.sortOrder=null,this.categoriesFilterString=null,this.statusesFilterString=null,this.loading=!0,this.loadingCategories=!1,this.loadingStatuses=!1,this.showCategoriesFilter=!1,this.showStatusesFilter=!1,this.filteredByCategory=!1,this.filteredByStatus=!1,this.filters=["All","Favorites","Archived"],this.displayModes=[{label:"List",value:"list",useTemplate:!0},{label:"Folder",value:"folder",useTemplate:!0}],this.app.setTitle("Projects"),this.trackVisit(),this.pageSize=+(this.route.snapshot.queryParamMap.get("size")||Ot.L8),this.pageIndex=+(this.route.snapshot.queryParamMap.get("index")||1)}ngOnInit(){var t=this;return(0,ce.Z)(function*(){t.searchForm=t.fb.group({search:[]}),t.searchForm.valueChanges.subscribe(()=>{t.searchProjects()}),t.getCategories(),t.getStatuses()})()}getFilterIndexText(){return this.filters[this.filterIndex]}refresh(){pt.s.track(Ve.vQ),this.getCategories(),this.getProjects()}import(){var t=this;return(0,ce.Z)(function*(){yield t.templatesApi.createTemplates()})()}getCategories(){var t=this;return(0,ce.Z)(function*(){try{t.loadingCategories=!0;const n=yield t.categoriesApi.get();n.done&&(t.categories=n.body),t.loadingCategories=!1}catch{t.loadingCategories=!1}t.cdr.markForCheck()})()}getStatuses(){var t=this;return(0,ce.Z)(function*(){try{t.loadingStatuses=!0;const n=yield t.statusesApi.get();n.done&&(t.statuses=n.body),t.loadingStatuses=!1}catch{t.loadingStatuses=!1}t.cdr.markForCheck()})()}getProjects(t=!1){var n=this;return(0,ce.Z)(function*(){try{t||(n.loading=!0);const s=yield n.api.getByConfig({index:n.pageIndex,size:n.pageSize,field:n.sortField,order:n.sortOrder,search:n.searchForm.value.search,filter:n.filterIndex.toString(),categories:n.categoriesFilterString,statuses:n.statusesFilterString});s.done&&n.handleProjectsResponse(s),n.loading=!1}catch(s){n.loading=!1,(0,st.tu)(s)}n.cdr.markForCheck()})()}handleProjectsResponse(t){this.projectsModel=t.body,this.total=this.projectsModel.total||0,this.utilsService.handleLastIndex(this.total,this.projectsModel.data?.length||0,this.pageIndex,n=>{this.pageIndex=n,this.getProjects()})}searchProjects(){var t=this;return(0,ce.Z)(function*(){t.searchForm.value.search&&pt.s.track(Ve.KI),yield t.getProjects()})()}isOwnerOrAdmin(){return this.auth.isOwnerOrAdmin()}openProjectForm(t){pt.s.track(t?Ve.GN:Ve._0),this.projectsForm?.open(t,!!t)}selectProject(t,n){if(!t)return;let s="tasks-list";switch(n){case"TASK_LIST":default:s="tasks-list";break;case"BOARD":s="board"}this.router.navigate([`/worklenz/projects/${t}`],{queryParams:{tab:`${s}`,pinned_tab:`${s}`}})}getTaskProgressTitle(t){return t.all_tasks_count?t.all_tasks_count==t.completed_tasks_count?"All tasks completed.":`${t.completed_tasks_count||0}/${t.all_tasks_count||0} tasks completed.`:"No tasks available."}toggleFavorite(t){var n=this;return(0,ce.Z)(function*(){if(t)try{const s=yield n.api.toggleFavorite(t);1==n.filterIndex&&s.done&&n.getProjects(!0)}catch(s){(0,st.tu)(s)}})()}toggleArchive(t){var n=this;return(0,ce.Z)(function*(){if(t&&t.id&&!t.loading)try{t.loading=!0,n.isOwnerOrAdmin()?(yield n.api.toggleArchiveAll(t.id)).done&&(pt.s.track(t.archived?Ve.vE:Ve.S9),yield n.getProjects(!0)):(yield n.api.toggleArchive(t.id)).done&&(pt.s.track(t.archived?Ve.$n:Ve.z1),yield n.getProjects(!0)),t.loading=!1}catch(s){t.loading=!1,(0,st.tu)(s)}})()}onFilterChange(t){this.loading||(this.filterIndex=t,this.trackVisit(),this.getProjects())}trackBy(t,n){return n.id}trackVisit(){pt.s.track(Ve.QJ,{filter:this.getFilterIndexText()})}onProjectCreate(t){t?.id&&(this.list.setCurrentGroup(this.list.GROUP_BY_OPTIONS[0]),this.selectProject(t.id,"TASK_LIST"))}onQueryParamsChange(t){const{pageSize:n,pageIndex:s,sort:o}=t;this.pageIndex=s,this.pageSize=n;const h=o.find(p=>null!==p.value);this.sortField=h?.key??null,this.sortOrder=h?.value??null,this.getProjects()}filterByCategory(t){if(!t)return;const n=this.categories.find(s=>s.id===t);n&&(n.selected=!0,this.onCategoryFilterChange())}filterByStatus(t){if(!t)return;const n=this.statuses.find(s=>s.id===t);n&&(n.selected=!0,this.onStatusFilterChange())}onCategoryFilterChange(){const t=this.categories.filter(s=>s.selected);this.filteredByCategory=!!t.length;const n=t.map(s=>s.id).join("+");this.categoriesFilterString=n||null,this.getProjects()}onStatusFilterChange(){const t=this.statuses.filter(s=>s.selected);this.filteredByStatus=!!t.length;const n=t.map(s=>s.id).join("+");this.statusesFilterString=n||null,this.getProjects()}onProjectUpdated(){this.getProjects(),this.getCategories()}openTemplateSelector(){this.projectTemplateDrawer.open(),this.cdr.markForCheck(),pt.s.track(Ve.Ye)}}return(r=i).\u0275fac=function(t){return new(t||r)(e.Y36(ia.z),e.Y36(ji.U),e.Y36(Pe.qu),e.Y36(yn.e),e.Y36(wt.F0),e.Y36(wt.gz),e.Y36(JA.F),e.Y36(Bd.s),e.Y36(Lc.I),e.Y36(e.sBO),e.Y36(YA.b),e.Y36(tp.k))},r.\u0275cmp=e.Xpm({type:r,selectors:[["worklenz-projects"]],viewQuery:function(t,n){if(1&t&&(e.Gf(yr.g,5),e.Gf(Cd,5),e.Gf(Oc,7,e.Rgc),e.Gf(Ec.p,5)),2&t){let s;e.iGM(s=e.CRH())&&(n.projectsForm=s.first),e.iGM(s=e.CRH())&&(n.folderForm=s.first),e.iGM(s=e.CRH())&&(n.displayModeSegments=s.first),e.iGM(s=e.CRH())&&(n.projectTemplateDrawer=s.first)}},decls:51,vars:52,consts:[[1,"container"],[1,"px-0",3,"nzGhost"],["nz-button","","nz-tooltip","",3,"nzShape","nzTooltipTitle","nzType","click",4,"nzSpaceItem"],[3,"ngModel","nzOptions","ngModelChange","nzValueChange",4,"nzSpaceItem"],["nz-form","",3,"formGroup","nzLayout","ngSubmit",4,"nzSpaceItem"],[4,"ngIf"],[3,"nzActive","nzLoading","nzParagraph"],[1,"custom-table",3,"nzData","nzFrontPagination","nzLoading","nzPageIndex","nzPageSizeOptions","nzPageSize","nzTotal","nzShowSizeChanger","nzSize","nzNoResult","nzQueryParams"],["table",""],["scope","col",3,"nzSortFn","nzColumnKey"],["scope","col",3,"nzSortFn","nzColumnKey","nzFilterFn","nzCustomFilter"],[3,"nzVisible","nzActive","nzDropdownMenu","nzVisibleChange"],["nz-icon","",3,"nzType","nzTheme"],["scope","col",2,"width","110px",3,"nzSortFn","nzColumnKey","nzFilterFn","nzCustomFilter"],["scope","col",1,"pe-5","b-none"],["scope","col",3,"nzSortFn","nzColumnKey","nzAlign"],["colspan","2","scope","col",3,"nzAlign"],["class","actions-row cursor-pointer",3,"click",4,"ngFor","ngForOf","ngForTrackBy"],[3,"onCreate","onDelete","onUpdate"],[3,"showBothTabs","importProject"],["noDataTemplate",""],["categoriesDropdown","nzDropdownMenu"],["nz-menu","","nzSelectable",""],["nz-menu-item","","nzDisabled","",4,"ngIf"],["statusesDropdown","nzDropdownMenu"],["nz-button","","nz-tooltip","",3,"nzShape","nzTooltipTitle","nzType","click"],["nz-icon","",3,"nzSpin","nzTheme","nzType"],[3,"ngModel","nzOptions","ngModelChange","nzValueChange"],["nz-form","",3,"formGroup","nzLayout","ngSubmit"],[3,"nzSuffix"],["nz-input","","placeholder","Search by name","type","text",3,"formControlName"],["suffixIconSearch",""],["nz-icon","","nzType","search"],[4,"nzSpaceItem"],["menu","nzDropdownMenu"],["nz-menu",""],["nz-menu-item","",3,"click"],["nz-icon","","nzType","import","nzTheme","outline",1,"me-2"],["nz-button","",3,"nzType","click",4,"ngIf"],["nz-button","","nz-dropdown","",3,"nzType","nzDropdownMenu","nzTrigger","nzPlacement",4,"ngIf"],["nz-button","",3,"nzType","click"],["nz-icon","","nzType","edit","nzTheme","outline"],["nz-button","","nz-dropdown","",3,"nzType","nzDropdownMenu","nzTrigger","nzPlacement"],["nz-icon","","nzType","down","nzTheme","outline"],[1,"actions-row","cursor-pointer",3,"click"],[1,""],[1,"align-items-center","d-flex","w-100"],["nz-tooltip","",1,"me-2",3,"nzTooltipTitle","nzCount","ngModel","click"],[1,"w-100","project-name-col"],[3,"nzColor","nzSize"],[3,"ngSwitch"],["class","rounded-pill category-tag","nz-tooltip","",3,"nzTooltipTitle","nzColor","click",4,"ngSwitchCase"],[4,"ngSwitchCase"],["nz-icon","",3,"nzType"],["nz-tooltip","",3,"nzPercent","nzStrokeColor","nzTooltipTitle"],["nz-tooltip","",3,"nzTooltipTitle","nzTooltipMouseEnterDelay"],[4,"ngIf","ngIfElse"],["avatars",""],[3,"nzAlign"],["class","actions",4,"ngIf"],["nz-typography","","nz-tooltip","",1,"ms-1",3,"nzType","nzTooltipTitle"],["titleContent",""],["nz-tooltip","",1,"rounded-pill","category-tag",3,"nzTooltipTitle","nzColor","click"],[3,"names"],[1,"actions"],["nz-button","","nz-tooltip","",3,"nzSize","nzTooltipPlacement","nzTooltipTitle","nzType","click",4,"nzSpaceItem"],["nz-button","","nz-tooltip","",3,"nzSize","nzTooltipPlacement","nzTooltipTitle","nzLoading","nzType","click",4,"nzSpaceItem"],["nz-button","","nz-tooltip","",3,"nzSize","nzTooltipPlacement","nzTooltipTitle","nzType","click"],["nz-button","","nz-tooltip","",3,"nzSize","nzTooltipPlacement","nzTooltipTitle","nzLoading","nzType","click"],[1,"pt-5","pb-5"],[1,"no-data-img-holder","mx-auto","mb-4"],["src","/assets/images/empty-box.webp","alt","",1,"img-fluid"],["nz-typography","",1,"no-data-text",3,"ngSwitch"],["class","m-0 d-flex align-items-baseline","nz-checkbox","","nz-menu-item","",3,"nzChecked","nzCheckedChange",4,"ngFor","ngForOf"],["nz-checkbox","","nz-menu-item","",1,"m-0","d-flex","align-items-baseline",3,"nzChecked","nzCheckedChange"],["nz-menu-item","","nzDisabled",""]],template:function(t,n){if(1&t&&(e.TgZ(0,"div",0)(1,"nz-page-header",1)(2,"nz-page-header-title"),e._uU(3),e.qZA(),e.TgZ(4,"nz-page-header-extra")(5,"nz-space"),e.YNc(6,np,2,6,"button",2),e.YNc(7,co,1,2,"nz-segmented",3),e.YNc(8,sa,5,4,"form",4),e.YNc(9,Dc,8,0,"ng-container",5),e.qZA()()(),e.TgZ(10,"nz-card")(11,"nz-skeleton",6)(12,"nz-table",7,8),e.NdJ("nzQueryParams",function(o){return n.onQueryParamsChange(o)}),e.TgZ(14,"thead")(15,"tr")(16,"th",9),e._uU(17,"Name"),e.qZA(),e.TgZ(18,"th",9),e._uU(19,"Client"),e.qZA(),e.TgZ(20,"th",10),e._uU(21," Category "),e.TgZ(22,"nz-filter-trigger",11),e.NdJ("nzVisibleChange",function(o){return n.showCategoriesFilter=o}),e._UZ(23,"span",12),e.qZA()(),e.TgZ(24,"th",13),e._uU(25," Status "),e.TgZ(26,"nz-filter-trigger",11),e.NdJ("nzVisibleChange",function(o){return n.showStatusesFilter=o}),e._UZ(27,"span",12),e.qZA()(),e.TgZ(28,"th",14),e._uU(29,"Tasks Progress"),e.qZA(),e.TgZ(30,"th",15),e._uU(31,"Last Updated"),e.qZA(),e.TgZ(32,"th",16),e._uU(33,"Members"),e.qZA()()(),e.TgZ(34,"tbody"),e.YNc(35,Zc,30,30,"tr",17),e.qZA()()()()(),e.TgZ(36,"worklenz-project-form-modal",18),e.NdJ("onCreate",function(o){return n.onProjectCreate(o)})("onDelete",function(){return n.getProjects()})("onUpdate",function(){return n.onProjectUpdated()}),e.qZA(),e._UZ(37,"worklenz-projects-folder-form-drawer"),e.TgZ(38,"worklenz-project-template-import-drawer",19),e.NdJ("importProject",function(){return n.getProjects(!1)}),e.qZA(),e.YNc(39,Gc,7,4,"ng-template",null,20,e.W1O),e.TgZ(41,"nz-dropdown-menu",null,21)(43,"ul",22),e.YNc(44,la,2,1,"ng-container",5),e.YNc(45,Kc,2,0,"li",23),e.qZA()(),e.TgZ(46,"nz-dropdown-menu",null,24)(48,"ul",22),e.YNc(49,ip,2,1,"ng-container",5),e.YNc(50,sp,2,0,"li",23),e.qZA()()),2&t){const s=e.MAs(13),o=e.MAs(40),h=e.MAs(42),p=e.MAs(47);e.xp6(1),e.Q6J("nzGhost",!1),e.xp6(2),e.AsE("",n.total||0," Project",n.total>1?"s":"",""),e.xp6(6),e.Q6J("ngIf",n.isOwnerOrAdmin()),e.xp6(2),e.Q6J("nzActive",!0)("nzLoading",n.loading)("nzParagraph",e.DdM(50,kd)),e.xp6(1),e.Q6J("nzData",n.projectsModel.data||e.DdM(51,yd))("nzFrontPagination",!1)("nzLoading",n.loading)("nzPageIndex",n.pageIndex)("nzPageSizeOptions",n.paginationSizes)("nzPageSize",n.pageSize)("nzTotal",n.total)("nzShowSizeChanger",!0)("nzSize","small")("nzNoResult",o),e.xp6(4),e.Q6J("nzSortFn",!0)("nzColumnKey","name"),e.xp6(2),e.Q6J("nzSortFn",!0)("nzColumnKey","client_name"),e.xp6(2),e.Q6J("nzSortFn",!0)("nzColumnKey","category_id")("nzFilterFn",!0)("nzCustomFilter",!0),e.xp6(2),e.Q6J("nzVisible",n.showCategoriesFilter)("nzActive",n.filteredByCategory)("nzDropdownMenu",h),e.xp6(1),e.Q6J("nzType","filter")("nzTheme","fill"),e.xp6(1),e.Q6J("nzSortFn",!0)("nzColumnKey","status_id")("nzFilterFn",!0)("nzCustomFilter",!0),e.xp6(2),e.Q6J("nzVisible",n.showStatusesFilter)("nzActive",n.filteredByStatus)("nzDropdownMenu",p),e.xp6(1),e.Q6J("nzType","filter")("nzTheme","fill"),e.xp6(3),e.Q6J("nzSortFn",!0)("nzColumnKey","updated_at")("nzAlign","left"),e.xp6(2),e.Q6J("nzAlign","left"),e.xp6(3),e.Q6J("ngForOf",s.data)("ngForTrackBy",n.trackBy),e.xp6(3),e.Q6J("showBothTabs",!0),e.xp6(6),e.Q6J("ngIf",n.categories.length),e.xp6(1),e.Q6J("ngIf",!n.categories.length),e.xp6(4),e.Q6J("ngIf",n.statuses.length),e.xp6(1),e.Q6J("ngIf",!n.statuses.length)}},dependencies:[Ie.sg,Ie.O5,Ie.RF,Ie.n9,Pe._Y,Pe.Fj,Pe.JJ,Pe.JL,Pe.sg,Pe.u,mn.Lr,nA.Zp,nA.gB,nA.ke,Tr.$O,Tr.u9,Tr.Jp,Vt.ix,Vt.fY,Wt.w,an.dQ,xt.N8,xt.qD,xt.Uo,xt._C,xt.Om,xt.p0,xt.$Z,xt.Ql,xt.UX,zt.Ls,Ut.ZU,AA.j,Pe.On,qA.Ie,ms.M,Zt.SY,Mn.ng,DA.bd,HA.NU,HA.$1,fr.x7,Ri.wY,zn.wO,zn.r9,cn.cm,cn.RR,cn.wA,Nc.sn,FA.o,yr.g,Ec.p,Cd,Ie.uU,VA.m,bd],styles:[".actions-row[_ngcontent-%COMP%] td[_ngcontent-%COMP%]{padding:5px 8px!important}.project-name-col[_ngcontent-%COMP%]{display:flex;align-items:center}.no-data-img-holder[_ngcontent-%COMP%]{width:100px}.category-tag[_ngcontent-%COMP%]:hover{text-decoration:underline}"],changeDetection:0}),i})();var da=oe(5227),mA=oe(40581),qn=oe(62816);function vs(r,i){(0,qn.Z)(2,arguments);var A=(0,mA.Z)(r),t=(0,mA.Z)(i),n=A.getTime()-t.getTime();return n<0?-1:n>0?1:n}var Qd=oe(80275),Fd=oe(78970),zd=oe(13061),ap=oe(46719),Ud=oe(48292),Yc=oe(45351);function Id(r,i,A){var t,n;(0,qn.Z)(2,arguments);var s=(0,da.j)(),o=null!==(t=null!==(n=A?.locale)&&void 0!==n?n:s.locale)&&void 0!==t?t:ap.Z;if(!o.formatDistance)throw new RangeError("locale must contain formatDistance property");var h=vs(r,i);if(isNaN(h))throw new RangeError("Invalid time value");var _,B,p=(0,Ud.Z)(function Jc(r){return(0,Ud.Z)({},r)}(A),{addSuffix:!!A?.addSuffix,comparison:h});h>0?(_=(0,mA.Z)(i),B=(0,mA.Z)(r)):(_=(0,mA.Z)(r),B=(0,mA.Z)(i));var M,x=(0,zd.Z)(B,_),y=((0,Yc.Z)(B)-(0,Yc.Z)(_))/1e3,v=Math.round((x-y)/60);if(v<2)return null!=A&&A.includeSeconds?x<5?o.formatDistance("lessThanXSeconds",5,p):x<10?o.formatDistance("lessThanXSeconds",10,p):x<20?o.formatDistance("lessThanXSeconds",20,p):x<40?o.formatDistance("halfAMinute",0,p):o.formatDistance(x<60?"lessThanXMinutes":"xMinutes",1,p):0===v?o.formatDistance("lessThanXMinutes",1,p):o.formatDistance("xMinutes",v,p);if(v<45)return o.formatDistance("xMinutes",v,p);if(v<90)return o.formatDistance("aboutXHours",1,p);if(v<1440){var E=Math.round(v/60);return o.formatDistance("aboutXHours",E,p)}if(v<2520)return o.formatDistance("xDays",1,p);if(v<43200){var Z=Math.round(v/1440);return o.formatDistance("xDays",Z,p)}if(v<86400)return M=Math.round(v/43200),o.formatDistance("aboutXMonths",M,p);if(M=function op(r,i){(0,qn.Z)(2,arguments);var o,A=(0,mA.Z)(r),t=(0,mA.Z)(i),n=vs(A,t),s=Math.abs((0,Qd.Z)(A,t));if(s<1)o=0;else{1===A.getMonth()&&A.getDate()>27&&A.setDate(30),A.setMonth(A.getMonth()-n*s);var h=vs(A,t)===-n;(0,Fd.Z)((0,mA.Z)(r))&&1===s&&1===vs(r,t)&&(h=!1),o=n*(s-Number(h))}return 0===o?0:o}(B,_),M<12){var I=Math.round(v/43200);return o.formatDistance("xMonths",I,p)}var j=M%12,X=Math.floor(M/12);return j<3?o.formatDistance("aboutXYears",X,p):j<9?o.formatDistance("overXYears",X,p):o.formatDistance("almostXYears",X+1,p)}var en=oe(71002),tn=Uint8Array,Vn=Uint16Array,WA=Uint32Array,Bs=new tn([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0]),bs=new tn([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0]),Ci=new tn([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),xs=function(r,i){for(var A=new Vn(31),t=0;t<31;++t)A[t]=i+=1<>>1|(21845&Dt)<<1;_o[Dt]=((65280&(Bi=(61680&(Bi=(52428&Bi)>>>2|(13107&Bi)<<2))>>>4|(3855&Bi)<<4))>>>8|(255&Bi)<<8)>>>1}var XA=function(r,i,A){for(var t=r.length,n=0,s=new Vn(i);n>>p]=_}else for(h=new Vn(t),n=0;n>>15-r[n];return h},Yr=new tn(288);for(Dt=0;Dt<144;++Dt)Yr[Dt]=8;for(Dt=144;Dt<256;++Dt)Yr[Dt]=9;for(Dt=256;Dt<280;++Dt)Yr[Dt]=7;for(Dt=280;Dt<288;++Dt)Yr[Dt]=8;var ys=new tn(32);for(Dt=0;Dt<32;++Dt)ys[Dt]=5;var Ld=XA(Yr,9,0),Nd=XA(Yr,9,1),Od=XA(ys,5,0),Pd=XA(ys,5,1),Ji=function(r){for(var i=r[0],A=1;Ai&&(i=r[A]);return i},$A=function(r,i,A){var t=i/8>>0;return(r[t]|r[t+1]<<8)>>>(7&i)&A},wo=function(r,i){var A=i/8>>0;return(r[A]|r[A+1]<<8|r[A+2]<<16)>>>(7&i)},vo=function(r){return(r/8>>0)+(7&r&&1)},Yi=function(r,i,A){(null==i||i<0)&&(i=0),(null==A||A>r.length)&&(A=r.length);var t=new(r instanceof Vn?Vn:r instanceof WA?WA:tn)(A-i);return t.set(r.subarray(i,A)),t},er=function(r,i,A){var t=i/8>>0;r[t]|=A<<=7&i,r[t+1]|=A>>>8},tr=function(r,i,A){var t=i/8>>0;r[t]|=A<<=7&i,r[t+1]|=A>>>8,r[t+2]|=A>>>16},ha=function(r,i){for(var A=[],t=0;ty&&(y=s[t].s);var v=new Vn(y+1),M=pa(A[B-1],v,0);if(M>i){t=0;var E=0,Z=M-i,I=1<i))break;E+=I-(1<>>=Z;E>0;){var X=s[t].s;v[X]=0&&E;--t){var ie=s[t].s;v[ie]==i&&(--v[ie],++E)}M=i}return[new tn(v),M]},pa=function(r,i,A){return-1==r.s?Math.max(pa(r.l,i,A+1),pa(r.r,i,A+1)):i[r.s]=A},Wc=function(r){for(var i=r.length;i&&!r[--i];);for(var A=new Vn(++i),t=0,n=r[0],s=1,o=function(p){A[t++]=p},h=1;h<=i;++h)if(r[h]==n&&h!=i)++s;else{if(!n&&s>2){for(;s>138;s-=138)o(32754);s>2&&(o(s>10?s-11<<5|28690:s-3<<5|12305),s=0)}else if(s>3){for(o(n),--s;s>6;s-=6)o(8304);s>2&&(o(s-3<<5|8208),s=0)}for(;s--;)o(n);s=1,n=r[h]}return[A.subarray(0,t),i]},kr=function(r,i){for(var A=0,t=0;t>>8,r[n+2]=255^r[n],r[n+3]=255^r[n+1];for(var s=0;s4&&!Fe[Ci[P-1]];--P);var ve,ke,fe,Ue,$=_+5<<3,q=kr(n,Yr)+kr(s,ys)+o,be=kr(n,y)+kr(s,E)+o+14+3*P+kr(ue,Fe)+(2*ue[16]+3*ue[17]+7*ue[18]);if($<=q&&$<=be)return Bo(i,B,r.subarray(p,p+_));if(er(i,B,1+(be15&&(er(i,B,G[Y]>>>5&127),B+=G[Y]>>>12)}}else ve=Ld,ke=Yr,fe=Od,Ue=ys;for(Y=0;Y255){var K;tr(i,B,ve[257+(K=t[Y]>>>18&31)]),B+=ke[K+257],K>7&&(er(i,B,t[Y]>>>23&31),B+=Bs[K]);var ne=31&t[Y];tr(i,B,fe[ne]),B+=Ue[ne],ne>3&&(tr(i,B,t[Y]>>>5&8191),B+=bs[ne])}else tr(i,B,ve[t[Y]]),B+=ke[t[Y]];return tr(i,B,ve[256]),B+ke[256]},Dd=new WA([65540,131080,131088,131104,262176,1048704,1048832,2114560,2117632]),Hd=new tn(0),bo=function(r,i,A,t,n){return function(r,i,A,t,n,s){var o=r.length,h=new tn(t+o+5*(1+Math.floor(o/7e3))+n),p=h.subarray(t,h.length-n),_=0;if(!i||o<8)for(var B=0;B<=o;B+=65535){var x=B+65535;x>>13,M=8191&y,E=(1<7e3||Fe>24576)&&ve>423){_=Xc(r,p,0,_e,Se,ue,Qe,Fe,P,B-P,_),Fe=Y=Qe=0,P=B;for(var ke=0;ke<286;++ke)Se[ke]=0;for(ke=0;ke<30;++ke)ue[ke]=0}var fe=2,Ue=0,ze=M,et=q-be&32767;if(ve>2&&$==ie(B-et))for(var U=Math.min(v,ve)-1,G=Math.min(32767,B),K=Math.min(258,ve);et<=G&&--ze&&q!=be;){if(r[B+fe]==r[B+fe-et]){for(var ne=0;nefe){if(fe=ne,Ue=et,ne>U)break;var ae=Math.min(et,ne-2),ge=0;for(ke=0;kege&&(ge=Re,be=we)}}}et+=(q=be)-(be=Z[q])+32768&32767}if(Ue){_e[Fe++]=268435456|ua[fe]<<18|Ki[Ue];var je=31&ua[fe],rt=31&Ki[Ue];Qe+=Bs[je]+bs[rt],++Se[257+je],++ue[rt],D=B+fe,++Y}else _e[Fe++]=r[B],++Se[r[B]]}}_=Xc(r,p,s,_e,Se,ue,Qe,Fe,P,B-P,_),s||(_=Bo(p,_,Hd))}return Yi(h,0,t+vo(_)+n)}(r,null==i.level?6:i.level,null==i.mem?Math.ceil(1.5*Math.max(8,Math.min(13,Math.log(r.length)))):12+i.mem,A,t,!n)};function ks(r,i){void 0===i&&(i={});var A=function(){var r=1,i=0;return{p:function(A){for(var t=r,n=i,s=A.length,o=0;o!=s;){for(var h=Math.min(o+5552,s);o>>8<<16|(255&i)<<8|i>>>8)+2*((255&r)<<23)}}}();A.p(r);var t=bo(r,i,2,4);return function(r,i){var A=i.level,t=0==A?0:A<6?1:9==A?3:2;r[0]=120,r[1]=t<<6|(t?32-2*t:1)}(t,i),function(r,i,A){for(;A;++i)r[i]=A,A>>>=8}(t,t.length-4,A.d()),t}function nn(r,i){return function(r,i,A){var t=r.length,n=!i||A,s=!A||A.i;A||(A={}),i||(i=new tn(3*t));var o=function(ae){var ge=i.length;if(ae>ge){var we=new tn(Math.max(2*ge,ae));we.set(i),i=we}},h=A.f||0,p=A.p||0,_=A.b||0,B=A.l,x=A.d,y=A.m,v=A.n,M=8*t;do{if(!B){A.f=h=$A(r,p,1);var E=$A(r,p+1,3);if(p+=3,!E){var I=r[(Z=vo(p)+4)-4]|r[Z-3]<<8,j=Z+I;if(j>t){if(s)throw"unexpected EOF";break}n&&o(_+I),i.set(r.subarray(Z,j),_),A.b=_+=I,A.p=p=8*j;continue}if(1==E)B=Nd,x=Pd,y=9,v=5;else{if(2!=E)throw"invalid block type";var X=$A(r,p,31)+257,ie=$A(r,p+10,15)+4,_e=X+$A(r,p+5,31)+1;p+=14;for(var Se=new tn(_e),ue=new tn(19),Y=0;YM)break;var D=XA(ue,Qe,1);for(Y=0;Y<_e;){var Z,P=D[$A(r,p,Fe)];if(p+=15&P,(Z=P>>>4)<16)Se[Y++]=Z;else{var $=0,q=0;for(16==Z?(q=3+$A(r,p,3),p+=2,$=Se[Y-1]):17==Z?(q=3+$A(r,p,7),p+=3):18==Z&&(q=11+$A(r,p,127),p+=7);q--;)Se[Y++]=$}}var be=Se.subarray(0,X),ve=Se.subarray(X);y=Ji(be),v=Ji(ve),B=XA(be,y,1),x=XA(ve,v,1)}if(p>M)throw"unexpected EOF"}n&&o(_+131072);for(var ke=(1<>>4;if((p+=15&$)>M)throw"unexpected EOF";if(!$)throw"invalid length/literal";if(ze<256)i[_++]=ze;else{if(256==ze){B=null;break}var et=ze-254;ze>264&&(et=$A(r,p,(1<<(U=Bs[Y=ze-257]))-1)+Vc[Y],p+=U);var G=x[wo(r,p)&fe],K=G>>>4;if(!G)throw"invalid distance";if(p+=15&G,ve=mo[K],K>3){var U=bs[K];ve+=wo(r,p)&(1<M)throw"unexpected EOF";n&&o(_+131072);for(var ne=_+et;_>>4>7||(r[0]<<8|r[1])%31)throw"invalid zlib data";if(32&r[1])throw"invalid zlib data: preset dictionaries not supported"}(r),r.subarray(2,-4)),i)}var mt=function(){return typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:this}();function Ba(){mt.console&&"function"==typeof mt.console.log&&mt.console.log.apply(mt.console,arguments)}var sn={log:Ba,warn:function(r){mt.console&&("function"==typeof mt.console.warn?mt.console.warn.apply(mt.console,arguments):Ba.call(null,arguments))},error:function(r){mt.console&&("function"==typeof mt.console.error?mt.console.error.apply(mt.console,arguments):Ba(r))}};function ba(r,i,A){var t=new XMLHttpRequest;t.open("GET",r),t.responseType="blob",t.onload=function(){Tn(t.response,i,A)},t.onerror=function(){sn.error("could not download file")},t.send()}function sl(r){var i=new XMLHttpRequest;i.open("HEAD",r,!1);try{i.send()}catch{}return i.status>=200&&i.status<=299}function xA(r){try{r.dispatchEvent(new MouseEvent("click"))}catch{var i=document.createEvent("MouseEvents");i.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),r.dispatchEvent(i)}}var Ar,Qr,Tn=mt.saveAs||("object"!==(typeof window>"u"?"undefined":(0,en.Z)(window))||window!==mt?function(){}:typeof HTMLAnchorElement<"u"&&"download"in HTMLAnchorElement.prototype?function(r,i,A){var t=mt.URL||mt.webkitURL,n=document.createElement("a");n.download=i=i||r.name||"download",n.rel="noopener","string"==typeof r?(n.href=r,n.origin!==location.origin?sl(n.href)?ba(r,i,A):xA(n,n.target="_blank"):xA(n)):(n.href=t.createObjectURL(r),setTimeout(function(){t.revokeObjectURL(n.href)},4e4),setTimeout(function(){xA(n)},0))}:"msSaveOrOpenBlob"in navigator?function(r,i,A){if(i=i||r.name||"download","string"==typeof r)if(sl(r))ba(r,i,A);else{var t=document.createElement("a");t.href=r,t.target="_blank",setTimeout(function(){xA(t)})}else navigator.msSaveOrOpenBlob((n=r,void 0===(s=A)?s={autoBom:!1}:"object"!==(0,en.Z)(s)&&(sn.warn("Deprecated: Expected third argument to be a object"),s={autoBom:!s}),s.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(n.type)?new Blob([String.fromCharCode(65279),n],{type:n.type}):n),i);var n,s}:function(r,i,A,t){if((t=t||open("","_blank"))&&(t.document.title=t.document.body.innerText="downloading..."),"string"==typeof r)return ba(r,i,A);var n="application/octet-stream"===r.type,s=/constructor/i.test(mt.HTMLElement)||mt.safari,o=/CriOS\/[\d]+/.test(navigator.userAgent);if((o||n&&s)&&"object"===(typeof FileReader>"u"?"undefined":(0,en.Z)(FileReader))){var h=new FileReader;h.onloadend=function(){var B=h.result;B=o?B:B.replace(/^data:[^;]*;/,"data:attachment/file;"),t?t.location.href=B:location=B,t=null},h.readAsDataURL(r)}else{var p=mt.URL||mt.webkitURL,_=p.createObjectURL(r);t?t.location=_:location.href=_,t=null,setTimeout(function(){p.revokeObjectURL(_)},4e4)}});function Wr(r){var i;r=r||"",this.ok=!1,"#"==r.charAt(0)&&(r=r.substr(1,6)),r={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000000",blanchedalmond:"ffebcd",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"00ffff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dodgerblue:"1e90ff",feldspar:"d19275",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgrey:"d3d3d3",lightgreen:"90ee90",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslateblue:"8470ff",lightslategray:"778899",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"00ff00",limegreen:"32cd32",linen:"faf0e6",magenta:"ff00ff",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370d8",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"d87093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",red:"ff0000",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",violetred:"d02090",wheat:"f5deb3",white:"ffffff",whitesmoke:"f5f5f5",yellow:"ffff00",yellowgreen:"9acd32"}[r=(r=r.replace(/ /g,"")).toLowerCase()]||r;for(var A=[{re:/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,example:["rgb(123, 234, 45)","rgb(255,234,245)"],process:function(h){return[parseInt(h[1]),parseInt(h[2]),parseInt(h[3])]}},{re:/^(\w{2})(\w{2})(\w{2})$/,example:["#00ff00","336699"],process:function(h){return[parseInt(h[1],16),parseInt(h[2],16),parseInt(h[3],16)]}},{re:/^(\w{1})(\w{1})(\w{1})$/,example:["#fb0","f0f"],process:function(h){return[parseInt(h[1]+h[1],16),parseInt(h[2]+h[2],16),parseInt(h[3]+h[3],16)]}}],t=0;t255?255:this.r,this.g=this.g<0||isNaN(this.g)?0:this.g>255?255:this.g,this.b=this.b<0||isNaN(this.b)?0:this.b>255?255:this.b,this.toRGB=function(){return"rgb("+this.r+", "+this.g+", "+this.b+")"},this.toHex=function(){var h=this.r.toString(16),p=this.g.toString(16),_=this.b.toString(16);return 1==h.length&&(h="0"+h),1==p.length&&(p="0"+p),1==_.length&&(_="0"+_),"#"+h+p+_}}function xa(r,i){var A=r[0],t=r[1],n=r[2],s=r[3];A=rA(A,t,n,s,i[0],7,-680876936),s=rA(s,A,t,n,i[1],12,-389564586),n=rA(n,s,A,t,i[2],17,606105819),t=rA(t,n,s,A,i[3],22,-1044525330),A=rA(A,t,n,s,i[4],7,-176418897),s=rA(s,A,t,n,i[5],12,1200080426),n=rA(n,s,A,t,i[6],17,-1473231341),t=rA(t,n,s,A,i[7],22,-45705983),A=rA(A,t,n,s,i[8],7,1770035416),s=rA(s,A,t,n,i[9],12,-1958414417),n=rA(n,s,A,t,i[10],17,-42063),t=rA(t,n,s,A,i[11],22,-1990404162),A=rA(A,t,n,s,i[12],7,1804603682),s=rA(s,A,t,n,i[13],12,-40341101),n=rA(n,s,A,t,i[14],17,-1502002290),A=uA(A,t=rA(t,n,s,A,i[15],22,1236535329),n,s,i[1],5,-165796510),s=uA(s,A,t,n,i[6],9,-1069501632),n=uA(n,s,A,t,i[11],14,643717713),t=uA(t,n,s,A,i[0],20,-373897302),A=uA(A,t,n,s,i[5],5,-701558691),s=uA(s,A,t,n,i[10],9,38016083),n=uA(n,s,A,t,i[15],14,-660478335),t=uA(t,n,s,A,i[4],20,-405537848),A=uA(A,t,n,s,i[9],5,568446438),s=uA(s,A,t,n,i[14],9,-1019803690),n=uA(n,s,A,t,i[3],14,-187363961),t=uA(t,n,s,A,i[8],20,1163531501),A=uA(A,t,n,s,i[13],5,-1444681467),s=uA(s,A,t,n,i[2],9,-51403784),n=uA(n,s,A,t,i[7],14,1735328473),A=wA(A,t=uA(t,n,s,A,i[12],20,-1926607734),n,s,i[5],4,-378558),s=wA(s,A,t,n,i[8],11,-2022574463),n=wA(n,s,A,t,i[11],16,1839030562),t=wA(t,n,s,A,i[14],23,-35309556),A=wA(A,t,n,s,i[1],4,-1530992060),s=wA(s,A,t,n,i[4],11,1272893353),n=wA(n,s,A,t,i[7],16,-155497632),t=wA(t,n,s,A,i[10],23,-1094730640),A=wA(A,t,n,s,i[13],4,681279174),s=wA(s,A,t,n,i[0],11,-358537222),n=wA(n,s,A,t,i[3],16,-722521979),t=wA(t,n,s,A,i[6],23,76029189),A=wA(A,t,n,s,i[9],4,-640364487),s=wA(s,A,t,n,i[12],11,-421815835),n=wA(n,s,A,t,i[15],16,530742520),A=Zn(A,t=wA(t,n,s,A,i[2],23,-995338651),n,s,i[0],6,-198630844),s=Zn(s,A,t,n,i[7],10,1126891415),n=Zn(n,s,A,t,i[14],15,-1416354905),t=Zn(t,n,s,A,i[5],21,-57434055),A=Zn(A,t,n,s,i[12],6,1700485571),s=Zn(s,A,t,n,i[3],10,-1894986606),n=Zn(n,s,A,t,i[10],15,-1051523),t=Zn(t,n,s,A,i[1],21,-2054922799),A=Zn(A,t,n,s,i[8],6,1873313359),s=Zn(s,A,t,n,i[15],10,-30611744),n=Zn(n,s,A,t,i[6],15,-1560198380),t=Zn(t,n,s,A,i[13],21,1309151649),A=Zn(A,t,n,s,i[4],6,-145523070),s=Zn(s,A,t,n,i[11],10,-1120210379),n=Zn(n,s,A,t,i[2],15,718787259),t=Zn(t,n,s,A,i[9],21,-343485551),r[0]=iA(A,r[0]),r[1]=iA(t,r[1]),r[2]=iA(n,r[2]),r[3]=iA(s,r[3])}function ko(r,i,A,t,n,s){return i=iA(iA(i,r),iA(t,s)),iA(i<>>32-n,A)}function rA(r,i,A,t,n,s,o){return ko(i&A|~i&t,r,i,n,s,o)}function uA(r,i,A,t,n,s,o){return ko(i&t|A&~t,r,i,n,s,o)}function wA(r,i,A,t,n,s,o){return ko(i^A^t,r,i,n,s,o)}function Zn(r,i,A,t,n,s,o){return ko(A^(i|~t),r,i,n,s,o)}function Fr(r){var i,A=r.length,t=[1732584193,-271733879,-1732584194,271733878];for(i=64;i<=r.length;i+=64)xa(t,Cp(r.substring(i-64,i)));r=r.substring(i-64);var n=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(i=0;i>2]|=r.charCodeAt(i)<<(i%4<<3);if(n[i>>2]|=128<<(i%4<<3),i>55)for(xa(t,n),i=0;i<16;i++)n[i]=0;return n[14]=8*A,xa(t,n),t}function Cp(r){var i,A=[];for(i=0;i<64;i+=4)A[i>>2]=r.charCodeAt(i)+(r.charCodeAt(i+1)<<8)+(r.charCodeAt(i+2)<<16)+(r.charCodeAt(i+3)<<24);return A}Ar=mt.atob.bind(mt),Qr=mt.btoa.bind(mt);var $d="0123456789abcdef".split("");function ya(r){for(var i="",A=0;A<4;A++)i+=$d[r>>8*A+4&15]+$d[r>>8*A&15];return i}function eu(r){return String.fromCharCode((255&r)>>0,(65280&r)>>8,(16711680&r)>>16,(4278190080&r)>>24)}function ol(r){return Fr(r).map(eu).join("")}var Bp="5d41402abc4b2a76b9719d911017c592"!=function(r){for(var i=0;i>16)+(i>>16)+(A>>16)<<16|65535&A}return r+i&4294967295}function Ta(r,i){var A,t,n;if(r!==A){for(var o=(n=r,new Array(1+(256/r.length>>0)+1).join(n)),h=[],p=0;p<256;p++)h[p]=p;var _=0;for(p=0;p<256;p++){var B=h[p];_=(_+B+o.charCodeAt(p))%256,h[p]=h[_],h[_]=B}A=r,t=h}else h=t;var x=i.length,y=0,v=0,M="";for(p=0;p\x80/\f\xa9\xfedSiz";var s=(i+this.padding).substr(0,32),o=(A+this.padding).substr(0,32);this.O=this.processOwnerPassword(s,o),this.P=-(1+(255^n)),this.encryptionKey=ol(s+this.O+this.lsbFirstWord(this.P)+this.hexToBytes(t)).substr(0,5),this.U=Ta(this.encryptionKey,this.padding)}function Xr(r){if(/[^\u0000-\u00ff]/.test(r))throw new Error("Invalid PDF Name Object: "+r+", Only accept ASCII characters.");for(var i="",A=r.length,t=0;t126?"#"+("0"+n.toString(16)).slice(-2):r[t]}return i}function nu(r){if("object"!==(0,en.Z)(r))throw new Error("Invalid Context passed to initialize PubSub (jsPDF-module)");var i={};this.subscribe=function(A,t,n){if(n=n||!1,"string"!=typeof A||"function"!=typeof t||"boolean"!=typeof n)throw new Error("Invalid arguments passed to PubSub.subscribe (jsPDF-module)");i.hasOwnProperty(A)||(i[A]={});var s=Math.random().toString(35);return i[A][s]=[t,!!n],s},this.unsubscribe=function(A){for(var t in i)if(i[t][A])return delete i[t][A],0===Object.keys(i[t]).length&&delete i[t],!0;return!1},this.publish=function(A){if(i.hasOwnProperty(A)){var t=Array.prototype.slice.call(arguments,1),n=[];for(var s in i[A]){var o=i[A][s];try{o[0].apply(r,t)}catch(h){mt.console&&sn.error("jsPDF PubSub Error",h.message,h)}o[1]&&n.push(s)}n.length&&n.forEach(this.unsubscribe)}},this.getTopics=function(){return i}}function Qo(r){if(!(this instanceof Qo))return new Qo(r);var i="opacity,stroke-opacity".split(",");for(var A in r)r.hasOwnProperty(A)&&i.indexOf(A)>=0&&(this[A]=r[A]);this.id="",this.objectNumber=-1}function al(r,i){this.gState=r,this.matrix=i,this.id="",this.objectNumber=-1}function xi(r,i,A,t,n){if(!(this instanceof xi))return new xi(r,i,A,t,n);this.type="axial"===r?2:3,this.coords=i,this.colors=A,al.call(this,t,n)}function qi(r,i,A,t,n){if(!(this instanceof qi))return new qi(r,i,A,t,n);this.boundingBox=r,this.xStep=i,this.yStep=A,this.stream="",this.cloneIndex=0,al.call(this,t,n)}function gt(r){var i,A="string"==typeof arguments[0]?arguments[0]:"p",t=arguments[1],n=arguments[2],s=arguments[3],o=[],h=1,p=16,_="S",B=null;"object"===(0,en.Z)(r=r||{})&&(A=r.orientation,t=r.unit||t,n=r.format||n,s=r.compress||r.compressPdf||s,null!==(B=r.encryption||null)&&(B.userPassword=B.userPassword||"",B.ownerPassword=B.ownerPassword||"",B.userPermissions=B.userPermissions||[]),h="number"==typeof r.userUnit?Math.abs(r.userUnit):1,void 0!==r.precision&&(i=r.precision),void 0!==r.floatPrecision&&(p=r.floatPrecision),_=r.defaultPathOperation||"S"),o=r.filters||(!0===s?["FlateEncode"]:o),t=t||"mm",A=(""+(A||"P")).toLowerCase();var x=r.putOnlyUsedFonts||!1,y={},v={internal:{},__private__:{}};v.__private__.PubSub=nu;var M="1.3",E=v.__private__.getPdfVersion=function(){return M};v.__private__.setPdfVersion=function(g){M=g};var Z={a0:[2383.94,3370.39],a1:[1683.78,2383.94],a2:[1190.55,1683.78],a3:[841.89,1190.55],a4:[595.28,841.89],a5:[419.53,595.28],a6:[297.64,419.53],a7:[209.76,297.64],a8:[147.4,209.76],a9:[104.88,147.4],a10:[73.7,104.88],b0:[2834.65,4008.19],b1:[2004.09,2834.65],b2:[1417.32,2004.09],b3:[1000.63,1417.32],b4:[708.66,1000.63],b5:[498.9,708.66],b6:[354.33,498.9],b7:[249.45,354.33],b8:[175.75,249.45],b9:[124.72,175.75],b10:[87.87,124.72],c0:[2599.37,3676.54],c1:[1836.85,2599.37],c2:[1298.27,1836.85],c3:[918.43,1298.27],c4:[649.13,918.43],c5:[459.21,649.13],c6:[323.15,459.21],c7:[229.61,323.15],c8:[161.57,229.61],c9:[113.39,161.57],c10:[79.37,113.39],dl:[311.81,623.62],letter:[612,792],"government-letter":[576,756],legal:[612,1008],"junior-legal":[576,360],ledger:[1224,792],tabloid:[792,1224],"credit-card":[153,243]};v.__private__.getPageFormats=function(){return Z};var I=v.__private__.getPageFormat=function(g){return Z[g]};n=n||"a4";var X="compat";function ie(){this.saveGraphicsState(),J(new vt(At,0,0,-At,0,qs()*At).toString()+" cm"),this.setFontSize(this.getFontSize()/At),_="n",X="advanced"}function _e(){this.restoreGraphicsState(),_="S",X="compat"}var Se=v.__private__.combineFontStyleAndFontWeight=function(g,k){if("bold"==g&&"normal"==k||"bold"==g&&400==k||"normal"==g&&"italic"==k||"bold"==g&&"italic"==k)throw new Error("Invalid Combination of fontweight and fontstyle");return k&&(g=400==k||"normal"===k?"italic"===g?"italic":"normal":700!=k&&"bold"!==k||"normal"!==g?(700==k?"bold":k)+""+g:"bold"),g};v.advancedAPI=function(g){var k="compat"===X;return k&&ie.call(this),"function"!=typeof g||(g(this),k&&_e.call(this)),this},v.compatAPI=function(g){var k="advanced"===X;return k&&_e.call(this),"function"!=typeof g||(g(this),k&&ie.call(this)),this},v.isAdvancedAPI=function(){return"advanced"===X};var ue,Y=function(g){if("advanced"!==X)throw new Error(g+" is only available in 'advanced' API mode. You need to call advancedAPI() first.")},Qe=v.roundToPrecision=v.__private__.roundToPrecision=function(g,k){var R=i||k;if(isNaN(g)||isNaN(R))throw new Error("Invalid argument passed to jsPDF.roundToPrecision");return g.toFixed(R).replace(/0+$/,"")};ue=v.hpf=v.__private__.hpf="number"==typeof p?function(g){if(isNaN(g))throw new Error("Invalid argument passed to jsPDF.hpf");return Qe(g,p)}:"smart"===p?function(g){if(isNaN(g))throw new Error("Invalid argument passed to jsPDF.hpf");return Qe(g,g>-1&&g<1?16:5)}:function(g){if(isNaN(g))throw new Error("Invalid argument passed to jsPDF.hpf");return Qe(g,16)};var Fe=v.f2=v.__private__.f2=function(g){if(isNaN(g))throw new Error("Invalid argument passed to jsPDF.f2");return Qe(g,2)},D=v.__private__.f3=function(g){if(isNaN(g))throw new Error("Invalid argument passed to jsPDF.f3");return Qe(g,3)},P=v.scale=v.__private__.scale=function(g){if(isNaN(g))throw new Error("Invalid argument passed to jsPDF.scale");return"compat"===X?g*At:"advanced"===X?g:void 0},q=function(g){return P(function(g){return"compat"===X?qs()-g:"advanced"===X?g:void 0}(g))};v.__private__.setPrecision=v.setPrecision=function(g){"number"==typeof parseInt(g,10)&&(i=parseInt(g,10))};var be,ve="00000000000000000000000000000000",ke=v.__private__.getFileId=function(){return ve},fe=v.__private__.setFileId=function(g){return ve=void 0!==g&&/^[a-fA-F0-9]{32}$/.test(g)?g.toUpperCase():ve.split("").map(function(){return"ABCDEF0123456789".charAt(Math.floor(16*Math.random()))}).join(""),null!==B&&(OA=new zs(B.userPermissions,B.userPassword,B.ownerPassword,ve)),ve};v.setFileId=function(g){return fe(g),this},v.getFileId=function(){return ke()};var Ue=v.__private__.convertDateToPDFDate=function(g){var k=g.getTimezoneOffset(),R=k<0?"+":"-",V=Math.floor(Math.abs(k/60)),le=Math.abs(k%60),ye=[R,K(V),"'",K(le),"'"].join("");return["D:",g.getFullYear(),K(g.getMonth()+1),K(g.getDate()),K(g.getHours()),K(g.getMinutes()),K(g.getSeconds()),ye].join("")},ze=v.__private__.convertPDFDateToDate=function(g){var k=parseInt(g.substr(2,4),10),R=parseInt(g.substr(6,2),10)-1,V=parseInt(g.substr(8,2),10),le=parseInt(g.substr(10,2),10),ye=parseInt(g.substr(12,2),10),De=parseInt(g.substr(14,2),10);return new Date(k,R,V,le,ye,De,0)},et=v.__private__.setCreationDate=function(g){var k;if(void 0===g&&(g=new Date),g instanceof Date)k=Ue(g);else{if(!/^D:(20[0-2][0-9]|203[0-7]|19[7-9][0-9])(0[0-9]|1[0-2])([0-2][0-9]|3[0-1])(0[0-9]|1[0-9]|2[0-3])(0[0-9]|[1-5][0-9])(0[0-9]|[1-5][0-9])(\+0[0-9]|\+1[0-4]|-0[0-9]|-1[0-1])'(0[0-9]|[1-5][0-9])'?$/.test(g))throw new Error("Invalid argument passed to jsPDF.setCreationDate");k=g}return be=k},U=v.__private__.getCreationDate=function(g){var k=be;return"jsDate"===g&&(k=ze(be)),k};v.setCreationDate=function(g){return et(g),this},v.getCreationDate=function(g){return U(g)};var G,K=v.__private__.padd2=function(g){return("0"+parseInt(g)).slice(-2)},ne=v.__private__.padd2Hex=function(g){return("00"+(g=g.toString())).substr(g.length)},ae=0,ge=[],we=[],Ce=0,Re=[],je=[],rt=!1,at=we;v.__private__.setCustomOutputDestination=function(g){rt=!0,at=g};var Te=function(g){rt||(at=g)};v.__private__.resetCustomOutputDestination=function(){rt=!1,at=we};var J=v.__private__.out=function(g){return g=g.toString(),Ce+=g.length+1,at.push(g),at},It=v.__private__.write=function(g){return J(1===arguments.length?g.toString():Array.prototype.join.call(arguments," "))},ut=v.__private__.getArrayBuffer=function(g){for(var k=g.length,R=new ArrayBuffer(k),V=new Uint8Array(R);k--;)V[k]=g.charCodeAt(k);return R},He=[["Helvetica","helvetica","normal","WinAnsiEncoding"],["Helvetica-Bold","helvetica","bold","WinAnsiEncoding"],["Helvetica-Oblique","helvetica","italic","WinAnsiEncoding"],["Helvetica-BoldOblique","helvetica","bolditalic","WinAnsiEncoding"],["Courier","courier","normal","WinAnsiEncoding"],["Courier-Bold","courier","bold","WinAnsiEncoding"],["Courier-Oblique","courier","italic","WinAnsiEncoding"],["Courier-BoldOblique","courier","bolditalic","WinAnsiEncoding"],["Times-Roman","times","normal","WinAnsiEncoding"],["Times-Bold","times","bold","WinAnsiEncoding"],["Times-Italic","times","italic","WinAnsiEncoding"],["Times-BoldItalic","times","bolditalic","WinAnsiEncoding"],["ZapfDingbats","zapfdingbats","normal",null],["Symbol","symbol","normal",null]];v.__private__.getStandardFonts=function(){return He};var Ge=r.fontSize||16;v.__private__.setFontSize=v.setFontSize=function(g){return Ge="advanced"===X?g/At:g,this};var nt,qe=v.__private__.getFontSize=v.getFontSize=function(){return"compat"===X?Ge:Ge*At},ht=r.R2L||!1;v.__private__.setR2L=v.setR2L=function(g){return ht=g,this},v.__private__.getR2L=v.getR2L=function(){return ht};var Qt,Nt=v.__private__.setZoomMode=function(g){if(/^(?:\d+\.\d*|\d*\.\d+|\d+)%$/.test(g))nt=g;else if(isNaN(g)){if(-1===[void 0,null,"fullwidth","fullheight","fullpage","original"].indexOf(g))throw new Error('zoom must be Integer (e.g. 2), a percentage Value (e.g. 300%) or fullwidth, fullheight, fullpage, original. "'+g+'" is not recognized.');nt=g}else nt=parseInt(g,10)};v.__private__.getZoomMode=function(){return nt};var Rt,An=v.__private__.setPageMode=function(g){if(-1==[void 0,null,"UseNone","UseOutlines","UseThumbs","FullScreen"].indexOf(g))throw new Error('Page mode must be one of UseNone, UseOutlines, UseThumbs, or FullScreen. "'+g+'" is not recognized.');Qt=g};v.__private__.getPageMode=function(){return Qt};var vn=v.__private__.setLayoutMode=function(g){if(-1==[void 0,null,"continuous","single","twoleft","tworight","two"].indexOf(g))throw new Error('Layout mode must be one of continuous, single, twoleft, tworight. "'+g+'" is not recognized.');Rt=g};v.__private__.getLayoutMode=function(){return Rt},v.__private__.setDisplayMode=v.setDisplayMode=function(g,k,R){return Nt(g),vn(k),An(R),this};var Tt={title:"",subject:"",author:"",keywords:"",creator:""};v.__private__.getDocumentProperty=function(g){if(-1===Object.keys(Tt).indexOf(g))throw new Error("Invalid argument passed to jsPDF.getDocumentProperty");return Tt[g]},v.__private__.getDocumentProperties=function(){return Tt},v.__private__.setDocumentProperties=v.setProperties=v.setDocumentProperties=function(g){for(var k in Tt)Tt.hasOwnProperty(k)&&g[k]&&(Tt[k]=g[k]);return this},v.__private__.setDocumentProperty=function(g,k){if(-1===Object.keys(Tt).indexOf(g))throw new Error("Invalid arguments passed to jsPDF.setDocumentProperty");return Tt[g]=k};var Gt,At,NA,on,si,bn={},In={},Mi=[],fn={},Yo={},Gn={},oi={},is=null,Kn=0,Ft=[],gn=new nu(v),qo=r.hotfixes||[],TA={},Ei={},Li=[],vt=function g(k,R,V,le,ye,De){if(!(this instanceof g))return new g(k,R,V,le,ye,De);isNaN(k)&&(k=1),isNaN(R)&&(R=0),isNaN(V)&&(V=0),isNaN(le)&&(le=1),isNaN(ye)&&(ye=0),isNaN(De)&&(De=0),this._matrix=[k,R,V,le,ye,De]};Object.defineProperty(vt.prototype,"sx",{get:function(){return this._matrix[0]},set:function(g){this._matrix[0]=g}}),Object.defineProperty(vt.prototype,"shy",{get:function(){return this._matrix[1]},set:function(g){this._matrix[1]=g}}),Object.defineProperty(vt.prototype,"shx",{get:function(){return this._matrix[2]},set:function(g){this._matrix[2]=g}}),Object.defineProperty(vt.prototype,"sy",{get:function(){return this._matrix[3]},set:function(g){this._matrix[3]=g}}),Object.defineProperty(vt.prototype,"tx",{get:function(){return this._matrix[4]},set:function(g){this._matrix[4]=g}}),Object.defineProperty(vt.prototype,"ty",{get:function(){return this._matrix[5]},set:function(g){this._matrix[5]=g}}),Object.defineProperty(vt.prototype,"a",{get:function(){return this._matrix[0]},set:function(g){this._matrix[0]=g}}),Object.defineProperty(vt.prototype,"b",{get:function(){return this._matrix[1]},set:function(g){this._matrix[1]=g}}),Object.defineProperty(vt.prototype,"c",{get:function(){return this._matrix[2]},set:function(g){this._matrix[2]=g}}),Object.defineProperty(vt.prototype,"d",{get:function(){return this._matrix[3]},set:function(g){this._matrix[3]=g}}),Object.defineProperty(vt.prototype,"e",{get:function(){return this._matrix[4]},set:function(g){this._matrix[4]=g}}),Object.defineProperty(vt.prototype,"f",{get:function(){return this._matrix[5]},set:function(g){this._matrix[5]=g}}),Object.defineProperty(vt.prototype,"rotation",{get:function(){return Math.atan2(this.shx,this.sx)}}),Object.defineProperty(vt.prototype,"scaleX",{get:function(){return this.decompose().scale.sx}}),Object.defineProperty(vt.prototype,"scaleY",{get:function(){return this.decompose().scale.sy}}),Object.defineProperty(vt.prototype,"isIdentity",{get:function(){return 1===this.sx&&0===this.shy&&0===this.shx&&1===this.sy&&0===this.tx&&0===this.ty}}),vt.prototype.join=function(g){return[this.sx,this.shy,this.shx,this.sy,this.tx,this.ty].map(ue).join(g)},vt.prototype.multiply=function(g){return new vt(g.sx*this.sx+g.shy*this.shx,g.sx*this.shy+g.shy*this.sy,g.shx*this.sx+g.sy*this.shx,g.shx*this.shy+g.sy*this.sy,g.tx*this.sx+g.ty*this.shx+this.tx,g.tx*this.shy+g.ty*this.sy+this.ty)},vt.prototype.decompose=function(){var g=this.sx,k=this.shy,R=this.shx,V=this.sy,le=this.tx,ye=this.ty,De=Math.sqrt(g*g+k*k),it=(g/=De)*R+(k/=De)*V;R-=g*it,V-=k*it;var ft=Math.sqrt(R*R+V*V);return it/=ft,g*(V/=ft)>16&255,V=ft>>8&255,le=255&ft}if(void 0===V||void 0===ye&&R===V&&V===le)k="string"==typeof R?R+" "+De[0]:2===g.precision?Fe(R/255)+" "+De[0]:D(R/255)+" "+De[0];else if(void 0===ye||"object"===(0,en.Z)(ye)){if(ye&&!isNaN(ye.a)&&0===ye.a)return["1.","1.","1.",De[1]].join(" ");k="string"==typeof R?[R,V,le,De[1]].join(" "):2===g.precision?[Fe(R/255),Fe(V/255),Fe(le/255),De[1]].join(" "):[D(R/255),D(V/255),D(le/255),De[1]].join(" ")}else k="string"==typeof R?[R,V,le,ye,De[2]].join(" "):2===g.precision?[Fe(R),Fe(V),Fe(le),Fe(ye),De[2]].join(" "):[D(R),D(V),D(le),D(ye),De[2]].join(" ");return k},Oi=v.__private__.getFilters=function(){return o},Zr=v.__private__.putStream=function(g){var k=(g=g||{}).data||"",R=g.filters||Oi(),V=g.alreadyAppliedFilters||[],le=g.addLength1||!1,ye=k.length,De=g.objectId,it=function(PA){return PA};if(null!==B&&void 0===De)throw new Error("ObjectId must be passed to putStream for file encryption");null!==B&&(it=OA.encryptor(De,0));var ft={};!0===R&&(R=["FlateEncode"]);var Mt=g.additionalKeyValues||[],Ht=(ft=void 0!==gt.API.processDataByFilters?gt.API.processDataByFilters(k,R):{data:k,reverseChain:[]}).reverseChain+(Array.isArray(V)?V.join(" "):V.toString());if(0!==ft.data.length&&(Mt.push({key:"Length",value:ft.data.length}),!0===le&&Mt.push({key:"Length1",value:ye})),0!=Ht.length)if(Ht.split("/").length-1==1)Mt.push({key:"Filter",value:Ht});else{Mt.push({key:"Filter",value:"["+Ht+"]"});for(var $t=0;$t>"),0!==ft.data.length&&(J("stream"),J(it(ft.data)),J("endstream"))},Pi=v.__private__.putPage=function(g){var k=g.number,R=g.data,V=g.objId,le=g.contentsObjId;vr(V,!0),J("<>"),J("endobj");var ye=R.join("\n");return"advanced"===X&&(ye+="\nQ"),vr(le,!0),Zr({data:ye,filters:Oi(),objectId:le}),J("endobj"),V},Vo=v.__private__.putPages=function(){var g,k,R=[];for(g=1;g<=Kn;g++)Ft[g].objId=sA(),Ft[g].contentsObjId=sA();for(g=1;g<=Kn;g++)R.push(Pi({number:g,data:je[g],objId:Ft[g].objId,contentsObjId:Ft[g].contentsObjId,mediaBox:Ft[g].mediaBox,cropBox:Ft[g].cropBox,bleedBox:Ft[g].bleedBox,trimBox:Ft[g].trimBox,artBox:Ft[g].artBox,userUnit:Ft[g].userUnit,rootDictionaryObjId:ss,resourceDictionaryObjId:ci}));vr(ss,!0),J("<>"),J("endobj"),gn.publish("postPutPages")},oc=function(g){gn.publish("putFont",{font:g,out:J,newObject:fA,putStream:Zr}),!0!==g.isAlreadyPutted&&(g.objectNumber=fA(),J("<<"),J("/Type /Font"),J("/BaseFont /"+Xr(g.postScriptName)),J("/Subtype /Type1"),"string"==typeof g.encoding&&J("/Encoding /"+g.encoding),J("/FirstChar 32"),J("/LastChar 255"),J(">>"),J("endobj"))},cc=function(g){g.objectNumber=fA();var k=[];k.push({key:"Type",value:"/XObject"}),k.push({key:"Subtype",value:"/Form"}),k.push({key:"BBox",value:"["+[ue(g.x),ue(g.y),ue(g.x+g.width),ue(g.y+g.height)].join(" ")+"]"}),k.push({key:"Matrix",value:"["+g.matrix.toString()+"]"});var R=g.pages[1].join("\n");Zr({data:R,additionalKeyValues:k,objectId:g.objectNumber}),J("endobj")},Ig=function(g,k){k||(k=21);var R=fA(),V=function(g,k){var R,V=[],le=1/(k-1);for(R=0;R<1;R+=le)V.push(R);V.push(1),0!=g[0].offset&&g.unshift({offset:0,color:g[0].color}),1!=g[g.length-1].offset&&g.push({offset:1,color:g[g.length-1].color});for(var it="",ft=0,Mt=0;Mtg[ft+1].offset;)ft++;var Ht=g[ft].offset,$t=(R-Ht)/(g[ft+1].offset-Ht),Hn=g[ft].color,Jn=g[ft+1].color;it+=ne(Math.round((1-$t)*Hn[0]+$t*Jn[0]).toString(16))+ne(Math.round((1-$t)*Hn[1]+$t*Jn[1]).toString(16))+ne(Math.round((1-$t)*Hn[2]+$t*Jn[2]).toString(16))}return it.trim()}(g.colors,k),le=[];le.push({key:"FunctionType",value:"0"}),le.push({key:"Domain",value:"[0.0 1.0]"}),le.push({key:"Size",value:"["+k+"]"}),le.push({key:"BitsPerSample",value:"8"}),le.push({key:"Range",value:"[0.0 1.0 0.0 1.0 0.0 1.0]"}),le.push({key:"Decode",value:"[0.0 1.0 0.0 1.0 0.0 1.0]"}),Zr({data:V,additionalKeyValues:le,alreadyAppliedFilters:["/ASCIIHexDecode"],objectId:R}),J("endobj"),g.objectNumber=fA(),J("<< /ShadingType "+g.type),J("/ColorSpace /DeviceRGB");var ye="/Coords ["+ue(parseFloat(g.coords[0]))+" "+ue(parseFloat(g.coords[1]))+" ";ye+=2===g.type?ue(parseFloat(g.coords[2]))+" "+ue(parseFloat(g.coords[3])):ue(parseFloat(g.coords[2]))+" "+ue(parseFloat(g.coords[3]))+" "+ue(parseFloat(g.coords[4]))+" "+ue(parseFloat(g.coords[5])),J(ye+="]"),g.matrix&&J("/Matrix ["+g.matrix.toString()+"]"),J("/Function "+R+" 0 R"),J("/Extend [true true]"),J(">>"),J("endobj")},Mg=function(g,k){var R=sA(),V=fA();k.push({resourcesOid:R,objectOid:V}),g.objectNumber=V;var le=[];le.push({key:"Type",value:"/Pattern"}),le.push({key:"PatternType",value:"1"}),le.push({key:"PaintType",value:"1"}),le.push({key:"TilingType",value:"1"}),le.push({key:"BBox",value:"["+g.boundingBox.map(ue).join(" ")+"]"}),le.push({key:"XStep",value:ue(g.xStep)}),le.push({key:"YStep",value:ue(g.yStep)}),le.push({key:"Resources",value:R+" 0 R"}),g.matrix&&le.push({key:"Matrix",value:"["+g.matrix.toString()+"]"}),Zr({data:g.stream,additionalKeyValues:le,objectId:g.objectNumber}),J("endobj")},hh=function(g){for(var k in g.objectNumber=fA(),J("<<"),g)switch(k){case"opacity":J("/ca "+Fe(g[k]));break;case"stroke-opacity":J("/CA "+Fe(g[k]))}J(">>"),J("endobj")},eA=function(g){vr(g.resourcesOid,!0),J("<<"),J("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]"),function(){for(var g in J("/Font <<"),bn)bn.hasOwnProperty(g)&&(!1===x||!0===x&&y.hasOwnProperty(g))&&J("/"+g+" "+bn[g].objectNumber+" 0 R");J(">>")}(),function(){if(Object.keys(fn).length>0){for(var g in J("/Shading <<"),fn)fn.hasOwnProperty(g)&&fn[g]instanceof xi&&fn[g].objectNumber>=0&&J("/"+g+" "+fn[g].objectNumber+" 0 R");gn.publish("putShadingPatternDict"),J(">>")}}(),function(g){if(Object.keys(fn).length>0){for(var k in J("/Pattern <<"),fn)fn.hasOwnProperty(k)&&fn[k]instanceof v.TilingPattern&&fn[k].objectNumber>=0&&fn[k].objectNumber>")}}(g.objectOid),function(){if(Object.keys(Gn).length>0){var g;for(g in J("/ExtGState <<"),Gn)Gn.hasOwnProperty(g)&&Gn[g].objectNumber>=0&&J("/"+g+" "+Gn[g].objectNumber+" 0 R");gn.publish("putGStateDict"),J(">>")}}(),function(){for(var g in J("/XObject <<"),TA)TA.hasOwnProperty(g)&&TA[g].objectNumber>=0&&J("/"+g+" "+TA[g].objectNumber+" 0 R");gn.publish("putXobjectDict"),J(">>")}(),J(">>"),J("endobj")},mh=function(g){In[g.fontName]=In[g.fontName]||{},In[g.fontName][g.fontStyle]=g.id},Kl=function(g,k,R,V,le){var ye={id:"F"+(Object.keys(bn).length+1).toString(10),postScriptName:g,fontName:k,fontStyle:R,encoding:V,isStandardFont:le||!1,metadata:{}};return gn.publish("addFont",{font:ye,instance:this}),bn[ye.id]=ye,mh(ye),ye.id},lr=v.__private__.pdfEscape=v.pdfEscape=function(g,k){return function(g,k){var R,V,le,ye,De,it,ft,Mt,Ht;if(le=(k=k||{}).sourceEncoding||"Unicode",De=k.outputEncoding,(k.autoencode||De)&&bn[Gt].metadata&&bn[Gt].metadata[le]&&bn[Gt].metadata[le].encoding&&(ye=bn[Gt].metadata[le].encoding,!De&&bn[Gt].encoding&&(De=bn[Gt].encoding),!De&&ye.codePages&&(De=ye.codePages[0]),"string"==typeof De&&(De=ye[De]),De)){for(ft=!1,it=[],R=0,V=g.length;R>8&&(ft=!0);g=it.join("")}for(R=g.length;void 0===ft&&0!==R;)g.charCodeAt(R-1)>>8&&(ft=!0),R--;if(!ft)return g;for(it=k.noBOM?[]:[254,255],R=0,V=g.length;R>8)>>8)throw new Error("Character at position "+R+" of string '"+g+"' exceeds 16bits. Cannot be encoded into UCS-2 BE");it.push(Ht),it.push(Mt-(Ht<<8))}return String.fromCharCode.apply(void 0,it)}(g,k).replace(/\\/g,"\\\\").replace(/\(/g,"\\(").replace(/\)/g,"\\)")},Jl=v.__private__.beginPage=function(g){je[++Kn]=[],Ft[Kn]={objId:0,contentsObjId:0,userUnit:Number(h),artBox:null,bleedBox:null,cropBox:null,trimBox:null,mediaBox:{bottomLeftX:0,bottomLeftY:0,topRightX:Number(g[0]),topRightY:Number(g[1])}},wh(Kn),Te(je[G])},_h=function(g,k){var R,V,le;switch(A=k||A,"string"==typeof g&&(R=I(g.toLowerCase()),Array.isArray(R)&&(V=R[0],le=R[1])),Array.isArray(g)&&(V=g[0]*At,le=g[1]*At),isNaN(V)&&(V=n[0],le=n[1]),(V>14400||le>14400)&&(sn.warn("A page in a PDF can not be wider or taller than 14400 userUnit. jsPDF limits the width/height to 14400"),V=Math.min(14400,V),le=Math.min(14400,le)),n=[V,le],A.substr(0,1)){case"l":le>V&&(n=[le,V]);break;case"p":V>le&&(n=[le,V])}Jl(n),yh(Wl),J(hi),0!==$l&&J($l+" J"),0!==ed&&J(ed+" j"),gn.publish("addPage",{pageNumber:Kn})},Dg=function(g){g>0&&g<=Kn&&(je.splice(g,1),Ft.splice(g,1),Kn--,G>Kn&&(G=Kn),this.setPage(G))},wh=function(g){g>0&&g<=Kn&&(G=g)},Hg=v.__private__.getNumberOfPages=v.getNumberOfPages=function(){return je.length-1},vh=function(g,k,R){var V,le=void 0;return R=R||{},k=void 0!==k?k:bn[Gt].fontStyle,V=(g=void 0!==g?g:bn[Gt].fontName).toLowerCase(),void 0!==In[V]&&void 0!==In[V][k]?le=In[V][k]:void 0!==In[g]&&void 0!==In[g][k]?le=In[g][k]:!1===R.disableWarning&&sn.warn("Unable to look up font label for font '"+g+"', '"+k+"'. Refer to getFontList() for available fonts."),le||R.noFallback||null==(le=In.times[k])&&(le=In.times.normal),le},Zg=v.__private__.putInfo=function(){var g=fA(),k=function(V){return V};for(var R in null!==B&&(k=OA.encryptor(g,0)),J("<<"),J("/Producer ("+lr(k("jsPDF "+gt.version))+")"),Tt)Tt.hasOwnProperty(R)&&Tt[R]&&J("/"+R.substr(0,1).toUpperCase()+R.substr(1)+" ("+lr(k(Tt[R]))+")");J("/CreationDate ("+lr(k(be))+")"),J(">>"),J("endobj")},Yl=v.__private__.putCatalog=function(g){var k=(g=g||{}).rootDictionaryObjId||ss;switch(fA(),J("<<"),J("/Type /Catalog"),J("/Pages "+k+" 0 R"),nt||(nt="fullwidth"),nt){case"fullwidth":J("/OpenAction [3 0 R /FitH null]");break;case"fullheight":J("/OpenAction [3 0 R /FitV null]");break;case"fullpage":J("/OpenAction [3 0 R /Fit]");break;case"original":J("/OpenAction [3 0 R /XYZ null null 1]");break;default:var R=""+nt;"%"===R.substr(R.length-1)&&(nt=parseInt(nt)/100),"number"==typeof nt&&J("/OpenAction [3 0 R /XYZ null null "+Fe(nt)+"]")}switch(Rt||(Rt="continuous"),Rt){case"continuous":J("/PageLayout /OneColumn");break;case"single":J("/PageLayout /SinglePage");break;case"two":case"twoleft":J("/PageLayout /TwoColumnLeft");break;case"tworight":J("/PageLayout /TwoColumnRight")}Qt&&J("/PageMode /"+Qt),gn.publish("putCatalog"),J(">>"),J("endobj")},jg=v.__private__.putTrailer=function(){J("trailer"),J("<<"),J("/Size "+(ae+1)),J("/Root "+ae+" 0 R"),J("/Info "+(ae-1)+" 0 R"),null!==B&&J("/Encrypt "+OA.oid+" 0 R"),J("/ID [ <"+ve+"> <"+ve+"> ]"),J(">>")},Rg=v.__private__.putHeader=function(){J("%PDF-"+M),J("%\xba\xdf\xac\xe0")},Gg=v.__private__.putXRef=function(){var g="0000000000";J("xref"),J("0 "+(ae+1)),J("0000000000 65535 f ");for(var k=1;k<=ae;k++)J("function"==typeof ge[k]?(g+ge[k]()).slice(-10)+" 00000 n ":void 0!==ge[k]?(g+ge[k]).slice(-10)+" 00000 n ":"0000000000 00000 n ")},os=v.__private__.buildDocument=function(){ae=0,Ce=0,we=[],ge=[],Re=[],ss=sA(),ci=sA(),Te(we),gn.publish("buildDocument"),Rg(),Vo(),function(){gn.publish("putAdditionalObjects");for(var g=0;g"),J("/O <"+OA.toHexString(OA.O)+">"),J("/P "+OA.P),J(">>"),J("endobj")),Zg(),Yl();var g=Ce;return Gg(),jg(),J("startxref"),J(""+g),J("%%EOF"),Te(je[G]),we.join("\n")},pc=v.__private__.getBlob=function(g){return new Blob([ut(g)],{type:"application/pdf"})},fc=v.output=v.__private__.output=((g=function(g,k){switch("string"==typeof(k=k||{})?k={filename:k}:k.filename=k.filename||"generated.pdf",g){case void 0:return os();case"save":v.save(k.filename);break;case"arraybuffer":return ut(os());case"blob":return pc(os());case"bloburi":case"bloburl":if(void 0!==mt.URL&&"function"==typeof mt.URL.createObjectURL)return mt.URL&&mt.URL.createObjectURL(pc(os()))||void 0;sn.warn("bloburl is not supported by your system, because URL.createObjectURL is not supported by your browser.");break;case"datauristring":case"dataurlstring":var R="",V=os();try{R=Qr(V)}catch{R=Qr(unescape(encodeURIComponent(V)))}return"data:application/pdf;filename="+k.filename+";base64,"+R;case"pdfobjectnewwindow":if("[object Window]"===Object.prototype.toString.call(mt)){var le="https://cdnjs.cloudflare.com/ajax/libs/pdfobject/2.1.1/pdfobject.min.js",ye=' integrity="sha512-4ze/a9/4jqu+tX9dfOqJYSvyYd5M6qum/3HpCLr+/Jqf0whc37VUbkpNGHR7/8pSnCFw47T1fmIpwBV7UySh3g==" crossorigin="anonymous"';k.pdfObjectUrl&&(le=k.pdfObjectUrl,ye="");var De=' '; + const directionality = editor.getBody().dir; + const dirAttr = directionality ? ' dir="' + encode(directionality) + '"' : ''; + const previewHtml = '' + '' + '' + headHtml + '' + '' + editor.getContent() + preventClicksOnLinksScript + '' + ''; + return previewHtml; + }; + + const open = editor => { + const content = getPreviewHtml(editor); + const dataApi = editor.windowManager.open({ + title: 'Preview', + size: 'large', + body: { + type: 'panel', + items: [{ + name: 'preview', + type: 'iframe', + sandboxed: true, + transparent: false + }] + }, + buttons: [{ + type: 'cancel', + name: 'close', + text: 'Close', + primary: true + }], + initialData: { preview: content } + }); + dataApi.focus('close'); + }; + + const register$1 = editor => { + editor.addCommand('mcePreview', () => { + open(editor); + }); + }; + + const register = editor => { + const onAction = () => editor.execCommand('mcePreview'); + editor.ui.registry.addButton('preview', { + icon: 'preview', + tooltip: 'Preview', + onAction + }); + editor.ui.registry.addMenuItem('preview', { + icon: 'preview', + text: 'Preview', + onAction + }); + }; + + var Plugin = () => { + global$2.add('preview', editor => { + register$1(editor); + register(editor); + }); + }; + + Plugin(); + +})(); diff --git a/worklenz-backend/src/public/tinymce/plugins/preview/plugin.min.js b/worklenz-backend/src/public/tinymce/plugins/preview/plugin.min.js new file mode 100644 index 00000000..14b0af4b --- /dev/null +++ b/worklenz-backend/src/public/tinymce/plugins/preview/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.8.4 (2024-06-19) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.Env"),o=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=e=>t=>t.options.get(e),i=n("content_style"),s=n("content_css_cors"),c=n("body_class"),r=n("body_id");e.add("preview",(e=>{(e=>{e.addCommand("mcePreview",(()=>{(e=>{const n=(e=>{var n;let l="";const a=e.dom.encode,d=null!==(n=i(e))&&void 0!==n?n:"";l+='';const m=s(e)?' crossorigin="anonymous"':"";o.each(e.contentCSS,(t=>{l+='"})),d&&(l+='");const y=r(e),u=c(e),v=' '; + const directionality = editor.getBody().dir; + const dirAttr = directionality ? ' dir="' + encode(directionality) + '"' : ''; + previewHtml = '' + '' + '' + '' + contentCssEntries + preventClicksOnLinksScript + '' + '' + previewHtml + '' + ''; + } + return replaceTemplateValues(previewHtml, getPreviewReplaceValues(editor)); + }; + const open = (editor, templateList) => { + const createTemplates = () => { + if (!templateList || templateList.length === 0) { + const message = editor.translate('No templates defined.'); + editor.notificationManager.open({ + text: message, + type: 'info' + }); + return Optional.none(); + } + return Optional.from(global$2.map(templateList, (template, index) => { + const isUrlTemplate = t => t.url !== undefined; + return { + selected: index === 0, + text: template.title, + value: { + url: isUrlTemplate(template) ? Optional.from(template.url) : Optional.none(), + content: !isUrlTemplate(template) ? Optional.from(template.content) : Optional.none(), + description: template.description + } + }; + })); + }; + const createSelectBoxItems = templates => map(templates, t => ({ + text: t.text, + value: t.text + })); + const findTemplate = (templates, templateTitle) => find(templates, t => t.text === templateTitle); + const loadFailedAlert = api => { + editor.windowManager.alert('Could not load the specified template.', () => api.focus('template')); + }; + const getTemplateContent = t => t.value.url.fold(() => Promise.resolve(t.value.content.getOr('')), url => fetch(url).then(res => res.ok ? res.text() : Promise.reject())); + const onChange = (templates, updateDialog) => (api, change) => { + if (change.name === 'template') { + const newTemplateTitle = api.getData().template; + findTemplate(templates, newTemplateTitle).each(t => { + api.block('Loading...'); + getTemplateContent(t).then(previewHtml => { + updateDialog(api, t, previewHtml); + }).catch(() => { + updateDialog(api, t, ''); + api.setEnabled('save', false); + loadFailedAlert(api); + }); + }); + } + }; + const onSubmit = templates => api => { + const data = api.getData(); + findTemplate(templates, data.template).each(t => { + getTemplateContent(t).then(previewHtml => { + editor.execCommand('mceInsertTemplate', false, previewHtml); + api.close(); + }).catch(() => { + api.setEnabled('save', false); + loadFailedAlert(api); + }); + }); + }; + const openDialog = templates => { + const selectBoxItems = createSelectBoxItems(templates); + const buildDialogSpec = (bodyItems, initialData) => ({ + title: 'Insert Template', + size: 'large', + body: { + type: 'panel', + items: bodyItems + }, + initialData, + buttons: [ + { + type: 'cancel', + name: 'cancel', + text: 'Cancel' + }, + { + type: 'submit', + name: 'save', + text: 'Save', + primary: true + } + ], + onSubmit: onSubmit(templates), + onChange: onChange(templates, updateDialog) + }); + const updateDialog = (dialogApi, template, previewHtml) => { + const content = getPreviewContent(editor, previewHtml); + const bodyItems = [ + { + type: 'listbox', + name: 'template', + label: 'Templates', + items: selectBoxItems + }, + { + type: 'htmlpanel', + html: `

${ htmlEscape(template.value.description) }

` + }, + { + label: 'Preview', + type: 'iframe', + name: 'preview', + sandboxed: false, + transparent: false + } + ]; + const initialData = { + template: template.text, + preview: content + }; + dialogApi.unblock(); + dialogApi.redial(buildDialogSpec(bodyItems, initialData)); + dialogApi.focus('template'); + }; + const dialogApi = editor.windowManager.open(buildDialogSpec([], { + template: '', + preview: '' + })); + dialogApi.block('Loading...'); + getTemplateContent(templates[0]).then(previewHtml => { + updateDialog(dialogApi, templates[0], previewHtml); + }).catch(() => { + updateDialog(dialogApi, templates[0], ''); + dialogApi.setEnabled('save', false); + loadFailedAlert(dialogApi); + }); + }; + const optTemplates = createTemplates(); + optTemplates.each(openDialog); + }; + + const showDialog = editor => templates => { + open(editor, templates); + }; + const register$1 = editor => { + editor.addCommand('mceInsertTemplate', curry(insertTemplate, editor)); + editor.addCommand('mceTemplate', createTemplateList(editor, showDialog(editor))); + }; + + const setup = editor => { + editor.on('PreProcess', o => { + const dom = editor.dom, dateFormat = getMdateFormat(editor); + global$2.each(dom.select('div', o.node), e => { + if (dom.hasClass(e, 'mceTmpl')) { + global$2.each(dom.select('*', e), e => { + if (hasAnyClasses(dom, e, getModificationDateClasses(editor))) { + e.innerHTML = getDateTime(editor, dateFormat); + } + }); + replaceVals(editor, e); + } + }); + }); + }; + + const onSetupEditable = editor => api => { + const nodeChanged = () => { + api.setEnabled(editor.selection.isEditable()); + }; + editor.on('NodeChange', nodeChanged); + nodeChanged(); + return () => { + editor.off('NodeChange', nodeChanged); + }; + }; + const register = editor => { + const onAction = () => editor.execCommand('mceTemplate'); + editor.ui.registry.addButton('template', { + icon: 'template', + tooltip: 'Insert template', + onSetup: onSetupEditable(editor), + onAction + }); + editor.ui.registry.addMenuItem('template', { + icon: 'template', + text: 'Insert template...', + onSetup: onSetupEditable(editor), + onAction + }); + }; + + var Plugin = () => { + global$3.add('template', editor => { + register$2(editor); + register(editor); + register$1(editor); + setup(editor); + }); + }; + + Plugin(); + +})(); diff --git a/worklenz-backend/src/public/tinymce/plugins/template/plugin.min.js b/worklenz-backend/src/public/tinymce/plugins/template/plugin.min.js new file mode 100644 index 00000000..5f07fd45 --- /dev/null +++ b/worklenz-backend/src/public/tinymce/plugins/template/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.8.4 (2024-06-19) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(a=n=e,(r=String).prototype.isPrototypeOf(a)||(null===(s=n.constructor)||void 0===s?void 0:s.name)===r.name)?"string":t;var a,n,r,s})(t)===e,a=t("string"),n=t("object"),r=t("array"),s=("function",e=>"function"==typeof e);const l=(!1,()=>false);var o=tinymce.util.Tools.resolve("tinymce.util.Tools");const c=e=>t=>t.options.get(e),i=c("template_cdate_classes"),u=c("template_mdate_classes"),m=c("template_selected_content_classes"),p=c("template_preview_replace_values"),d=c("template_replace_values"),h=c("templates"),g=c("template_cdate_format"),v=c("template_mdate_format"),f=c("content_style"),y=c("content_css_cors"),b=c("body_class"),_=(e,t)=>{if((e=""+e).length{const n="Sun Mon Tue Wed Thu Fri Sat Sun".split(" "),r="Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(" "),s="Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),l="January February March April May June July August September October November December".split(" ");return(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=t.replace("%D","%m/%d/%Y")).replace("%r","%I:%M:%S %p")).replace("%Y",""+a.getFullYear())).replace("%y",""+a.getYear())).replace("%m",_(a.getMonth()+1,2))).replace("%d",_(a.getDate(),2))).replace("%H",""+_(a.getHours(),2))).replace("%M",""+_(a.getMinutes(),2))).replace("%S",""+_(a.getSeconds(),2))).replace("%I",""+((a.getHours()+11)%12+1))).replace("%p",a.getHours()<12?"AM":"PM")).replace("%B",""+e.translate(l[a.getMonth()]))).replace("%b",""+e.translate(s[a.getMonth()]))).replace("%A",""+e.translate(r[a.getDay()]))).replace("%a",""+e.translate(n[a.getDay()]))).replace("%%","%")};class T{constructor(e,t){this.tag=e,this.value=t}static some(e){return new T(!0,e)}static none(){return T.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?T.some(e(this.value)):T.none()}bind(e){return this.tag?e(this.value):T.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:T.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return null==e?T.none():T.some(e)}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}T.singletonNone=new T(!1);const S=Object.hasOwnProperty;var x=tinymce.util.Tools.resolve("tinymce.html.Serializer");const C={'"':""","<":"<",">":">","&":"&","'":"'"},w=e=>e.replace(/["'<>&]/g,(e=>{return(t=C,a=e,((e,t)=>S.call(e,t))(t,a)?T.from(t[a]):T.none()).getOr(e);var t,a})),O=(e,t,a)=>((a,n)=>{for(let n=0,s=a.length;nx({validate:!0},e.schema).serialize(e.parser.parse(t,{insert:!0})),D=(e,t)=>(o.each(t,((t,a)=>{s(t)&&(t=t(a)),e=e.replace(new RegExp("\\{\\$"+a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"\\}","g"),t)})),e),N=(e,t)=>{const a=e.dom,n=d(e);o.each(a.select("*",t),(e=>{o.each(n,((t,n)=>{a.hasClass(e,n)&&s(t)&&t(e)}))}))},I=(e,t,a)=>{const n=e.dom,r=e.selection.getContent();a=D(a,d(e));let s=n.create("div",{},A(e,a));const l=n.select(".mceTmpl",s);l&&l.length>0&&(s=n.create("div"),s.appendChild(l[0].cloneNode(!0))),o.each(n.select("*",s),(t=>{O(n,t,i(e))&&(t.innerHTML=M(e,g(e))),O(n,t,u(e))&&(t.innerHTML=M(e,v(e))),O(n,t,m(e))&&(t.innerHTML=r)})),N(e,s),e.execCommand("mceInsertContent",!1,s.innerHTML),e.addVisual()};var E=tinymce.util.Tools.resolve("tinymce.Env");const k=(e,t)=>{const a=(e,t)=>((e,t,a)=>{for(let n=0,r=e.length;ne.text===t),l),n=t=>{e.windowManager.alert("Could not load the specified template.",(()=>t.focus("template")))},r=e=>e.value.url.fold((()=>Promise.resolve(e.value.content.getOr(""))),(e=>fetch(e).then((e=>e.ok?e.text():Promise.reject())))),s=(e,t)=>(s,l)=>{if("template"===l.name){const l=s.getData().template;a(e,l).each((e=>{s.block("Loading..."),r(e).then((a=>{t(s,e,a)})).catch((()=>{t(s,e,""),s.setEnabled("save",!1),n(s)}))}))}},c=t=>s=>{const l=s.getData();a(t,l.template).each((t=>{r(t).then((t=>{e.execCommand("mceInsertTemplate",!1,t),s.close()})).catch((()=>{s.setEnabled("save",!1),n(s)}))}))};(()=>{if(!t||0===t.length){const t=e.translate("No templates defined.");return e.notificationManager.open({text:t,type:"info"}),T.none()}return T.from(o.map(t,((e,t)=>{const a=e=>void 0!==e.url;return{selected:0===t,text:e.title,value:{url:a(e)?T.from(e.url):T.none(),content:a(e)?T.none():T.from(e.content),description:e.description}}})))})().each((t=>{const a=(e=>((e,t)=>{const a=e.length,n=new Array(a);for(let t=0;t({title:"Insert Template",size:"large",body:{type:"panel",items:e},initialData:a,buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],onSubmit:c(t),onChange:s(t,i)}),i=(t,n,r)=>{const s=((e,t)=>{var a;let n=A(e,t);if(-1===t.indexOf("")){let t="";const r=null!==(a=f(e))&&void 0!==a?a:"",s=y(e)?' crossorigin="anonymous"':"";o.each(e.contentCSS,(a=>{t+='"})),r&&(t+='");const l=b(e),c=e.dom.encode,i=' + + diff --git a/worklenz-frontend/jest.config.js b/worklenz-frontend/jest.config.js new file mode 100644 index 00000000..f3d2d027 --- /dev/null +++ b/worklenz-frontend/jest.config.js @@ -0,0 +1,7 @@ +module.exports = { + setupFilesAfterEnv: ['/src/setupTests.ts'], + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + '\\.(jpg|jpeg|png|gif|webp|svg)$': '/__mocks__/fileMock.js', + }, +}; diff --git a/worklenz-frontend/karma.conf.js b/worklenz-frontend/karma.conf.js deleted file mode 100644 index 01f192a4..00000000 --- a/worklenz-frontend/karma.conf.js +++ /dev/null @@ -1,44 +0,0 @@ -// Karma configuration file, see link for more information -// https://karma-runner.github.io/1.0/config/configuration-file.html - -module.exports = function (config) { - config.set({ - basePath: '', - frameworks: ['jasmine', '@angular-devkit/build-angular'], - plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('karma-coverage'), - require('@angular-devkit/build-angular/plugins/karma') - ], - client: { - jasmine: { - // you can add configuration options for Jasmine here - // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html - // for example, you can disable the random execution with `random: false` - // or set a specific seed with `seed: 4321` - }, - clearContext: false // leave Jasmine Spec Runner output visible in browser - }, - jasmineHtmlReporter: { - suppressAll: true // removes the duplicated traces - }, - coverageReporter: { - dir: require('path').join(__dirname, './coverage/worklenz'), - subdir: '.', - reporters: [ - { type: 'html' }, - { type: 'text-summary' } - ] - }, - reporters: ['progress', 'kjhtml'], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - browsers: ['Chrome'], - singleRun: false, - restartOnFileChange: true - }); -}; diff --git a/worklenz-frontend/ngsw-config.json b/worklenz-frontend/ngsw-config.json deleted file mode 100644 index 74dd2706..00000000 --- a/worklenz-frontend/ngsw-config.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "./node_modules/@angular/service-worker/config/schema.json", - "index": "/", - "assetGroups": [ - { - "name": "app", - "installMode": "prefetch", - "resources": { - "files": [ - "/favicon.ico", - "/", - "/manifest.webmanifest", - "/*.css", - "/*.js" - ] - } - }, - { - "name": "assets", - "installMode": "lazy", - "updateMode": "prefetch", - "resources": { - "files": [ - "/assets/**", - "/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)" - ] - } - } - ] -} diff --git a/worklenz-frontend/package-lock.json b/worklenz-frontend/package-lock.json index ebdfba0b..f75f2fce 100644 --- a/worklenz-frontend/package-lock.json +++ b/worklenz-frontend/package-lock.json @@ -8,1278 +8,199 @@ "name": "worklenz", "version": "1.0.0", "dependencies": { - "@angular/animations": "^16.2.0", - "@angular/cdk": "^16.2.0", - "@angular/common": "^16.2.0", - "@angular/compiler": "^16.2.0", - "@angular/core": "^16.2.0", - "@angular/forms": "^16.2.0", - "@angular/platform-browser": "^16.2.0", - "@angular/platform-browser-dynamic": "^16.2.0", - "@angular/router": "^16.2.0", - "@angular/service-worker": "^16.2.0", - "@rx-angular/cdk": "^16.0.0", - "@rx-angular/template": "^16.0.2", - "@tinymce/tinymce-angular": "^7.0.0", - "antd": "^5.8.2", - "bootstrap": "^5.3.1", - "chart.js": "^4.3.3", + "@ant-design/colors": "^7.1.0", + "@ant-design/compatible": "^5.1.4", + "@ant-design/icons": "^5.4.0", + "@ant-design/pro-components": "^2.7.19", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@emotion/react": "^11.14.0", + "@paddle/paddle-js": "^1.3.3", + "@reduxjs/toolkit": "^2.2.7", + "@tanstack/react-table": "^8.20.6", + "@tanstack/react-virtual": "^3.11.2", + "@tinymce/tinymce-react": "^5.1.1", + "antd": "^5.24.1", + "axios": "^1.7.9", + "chart.js": "^4.4.7", "chartjs-plugin-datalabels": "^2.2.0", - "chartjs-to-image": "^1.2.2", + "date-fns": "^4.1.0", + "dompurify": "^3.2.4", + "gantt-task-react": "^0.3.9", "html2canvas": "^1.4.1", - "jquery": "^3.7.0", - "jspdf": "^2.5.1", - "moment": "^2.29.4", - "moment-timezone": "^0.5.43", - "ng-zorro-antd": "^16.1.0", - "ng2-charts": "^5.0.3", - "ngx-doc-viewer": "^15.0.1", - "ngx-socket-io": "^4.5.1", - "rxjs": "~7.4.0", - "tinymce": "^6.8.3", - "tslib": "^2.6.1", - "zone.js": "^0.13.1" + "i18next": "^23.16.8", + "i18next-browser-languagedetector": "^8.0.3", + "i18next-http-backend": "^2.7.3", + "jspdf": "^3.0.0", + "mixpanel-browser": "^2.56.0", + "primereact": "^10.8.4", + "re-resizable": "^6.10.3", + "react": "^18.3.1", + "react-chartjs-2": "^5.2.0", + "react-dom": "^18.3.1", + "react-i18next": "^15.0.1", + "react-perfect-scrollbar": "^1.5.8", + "react-redux": "^9.2.0", + "react-responsive": "^10.0.0", + "react-router-dom": "^6.28.1", + "react-timer-hook": "^3.0.8", + "react-window": "^1.8.11", + "socket.io-client": "^4.8.1", + "tinymce": "^7.7.2", + "web-vitals": "^4.2.4" }, "devDependencies": { - "@angular-devkit/build-angular": "^16.2.0", - "@angular-eslint/builder": "^16.1.0", - "@angular-eslint/eslint-plugin": "^16.0.3", - "@angular-eslint/eslint-plugin-template": "^16.0.3", - "@angular-eslint/schematics": "^16.1.0", - "@angular-eslint/template-parser": "^16.1.0", - "@angular/cli": "^16.2.0", - "@angular/compiler-cli": "^16.2.0", - "@types/file-saver": "^2.0.5", - "@types/jasmine": "~3.10.0", - "@types/jquery": "^3.5.16", - "@types/node": "^12.20.55", - "@types/quill": "^2.0.10", - "@typescript-eslint/eslint-plugin": "5.48.2", - "@typescript-eslint/parser": "5.48.2", - "eslint": "^8.46.0", - "jasmine-core": "~3.10.0", - "karma": "~6.3.0", - "karma-chrome-launcher": "~3.1.0", - "karma-coverage": "~2.1.0", - "karma-jasmine": "~4.0.0", - "karma-jasmine-html-reporter": "~1.7.0", - "typescript": "~5.0.4" - }, - "engines": { - "node": ">=16.13.0", - "npm": ">= 8.5.5", - "yarn": "WARNING: Please use npm package manager instead of yarn" + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/chart.js": "^2.9.41", + "@types/dompurify": "^3.0.5", + "@types/jest": "^27.5.2", + "@types/lodash": "^4.17.15", + "@types/mixpanel-browser": "^2.50.2", + "@types/node": "^20.8.4", + "@types/react": "19.0.0", + "@types/react-dom": "19.0.0", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "postcss": "^8.5.2", + "prettier-plugin-tailwindcss": "^0.6.8", + "tailwindcss": "^3.4.17", + "terser": "^5.39.0", + "typescript": "^5.7.3", + "vite": "^6.2.5", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.0.5" } }, - "custom_modules/angular-draggable-droppable": { - "version": "7.0.0", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@mattlewis92/dom-autoscroller": "^2.4.2", - "tslib": "^2.6.1" - }, - "peerDependencies": { - "@angular/core": ">=14.0.0" - } - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "node_modules/@adobe/css-tools": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.2.tgz", + "integrity": "sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==", "dev": true, + "license": "MIT" + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, - "node_modules/@angular-devkit/architect": { - "version": "0.1602.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1602.0.tgz", - "integrity": "sha512-ZRmUTBeD+uGr605eOHnsovEn6f1mOBI+kxP64DRvagNweX5TN04s3iyQ8jmLSAHQD9ush31LFxv3dVNxv3ceXQ==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "16.2.0", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/architect/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@angular-devkit/build-angular": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-16.2.0.tgz", - "integrity": "sha512-miylwjOqvlKmYrzS84bjRaJrecZxOXH9xsPVvQE8VBe8UKePJjRAL6yyOqXUOGtzlch2YmT98RAnuni7y0FEAw==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "2.2.1", - "@angular-devkit/architect": "0.1602.0", - "@angular-devkit/build-webpack": "0.1602.0", - "@angular-devkit/core": "16.2.0", - "@babel/core": "7.22.9", - "@babel/generator": "7.22.9", - "@babel/helper-annotate-as-pure": "7.22.5", - "@babel/helper-split-export-declaration": "7.22.6", - "@babel/plugin-proposal-async-generator-functions": "7.20.7", - "@babel/plugin-transform-async-to-generator": "7.22.5", - "@babel/plugin-transform-runtime": "7.22.9", - "@babel/preset-env": "7.22.9", - "@babel/runtime": "7.22.6", - "@babel/template": "7.22.5", - "@discoveryjs/json-ext": "0.5.7", - "@ngtools/webpack": "16.2.0", - "@vitejs/plugin-basic-ssl": "1.0.1", - "ansi-colors": "4.1.3", - "autoprefixer": "10.4.14", - "babel-loader": "9.1.3", - "babel-plugin-istanbul": "6.1.1", - "browserslist": "^4.21.5", - "chokidar": "3.5.3", - "copy-webpack-plugin": "11.0.0", - "critters": "0.0.20", - "css-loader": "6.8.1", - "esbuild-wasm": "0.18.17", - "fast-glob": "3.3.1", - "guess-parser": "0.4.22", - "https-proxy-agent": "5.0.1", - "inquirer": "8.2.4", - "jsonc-parser": "3.2.0", - "karma-source-map-support": "1.4.0", - "less": "4.1.3", - "less-loader": "11.1.0", - "license-webpack-plugin": "4.0.2", - "loader-utils": "3.2.1", - "magic-string": "0.30.1", - "mini-css-extract-plugin": "2.7.6", - "mrmime": "1.0.1", - "open": "8.4.2", - "ora": "5.4.1", - "parse5-html-rewriting-stream": "7.0.0", - "picomatch": "2.3.1", - "piscina": "4.0.0", - "postcss": "8.4.27", - "postcss-loader": "7.3.3", - "resolve-url-loader": "5.0.0", - "rxjs": "7.8.1", - "sass": "1.64.1", - "sass-loader": "13.3.2", - "semver": "7.5.4", - "source-map-loader": "4.0.1", - "source-map-support": "0.5.21", - "terser": "5.19.2", - "text-table": "0.2.0", - "tree-kill": "1.2.2", - "tslib": "2.6.1", - "vite": "4.4.7", - "webpack": "5.88.2", - "webpack-dev-middleware": "6.1.1", - "webpack-dev-server": "4.15.1", - "webpack-merge": "5.9.0", - "webpack-subresource-integrity": "5.1.0" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "optionalDependencies": { - "esbuild": "0.18.17" - }, - "peerDependencies": { - "@angular/compiler-cli": "^16.0.0", - "@angular/localize": "^16.0.0", - "@angular/platform-server": "^16.0.0", - "@angular/service-worker": "^16.0.0", - "jest": "^29.5.0", - "jest-environment-jsdom": "^29.5.0", - "karma": "^6.3.0", - "ng-packagr": "^16.0.0", - "protractor": "^7.0.0", - "tailwindcss": "^2.0.0 || ^3.0.0", - "typescript": ">=4.9.3 <5.2" - }, - "peerDependenciesMeta": { - "@angular/localize": { - "optional": true - }, - "@angular/platform-server": { - "optional": true - }, - "@angular/service-worker": { - "optional": true - }, - "jest": { - "optional": true - }, - "jest-environment-jsdom": { - "optional": true - }, - "karma": { - "optional": true - }, - "ng-packagr": { - "optional": true - }, - "protractor": { - "optional": true - }, - "tailwindcss": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@babel/core": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.9.tgz", - "integrity": "sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.9", - "@babel/helper-compilation-targets": "^7.22.9", - "@babel/helper-module-transforms": "^7.22.9", - "@babel/helpers": "^7.22.6", - "@babel/parser": "^7.22.7", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.8", - "@babel/types": "^7.22.5", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/android-arm": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.17.tgz", - "integrity": "sha512-wHsmJG/dnL3OkpAcwbgoBTTMHVi4Uyou3F5mf58ZtmUyIKfcdA7TROav/6tCzET4A3QW2Q2FC+eFneMU+iyOxg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/android-arm64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.17.tgz", - "integrity": "sha512-9np+YYdNDed5+Jgr1TdWBsozZ85U1Oa3xW0c7TWqH0y2aGghXtZsuT8nYRbzOMcl0bXZXjOGbksoTtVOlWrRZg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/android-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.17.tgz", - "integrity": "sha512-O+FeWB/+xya0aLg23hHEM2E3hbfwZzjqumKMSIqcHbNvDa+dza2D0yLuymRBQQnC34CWrsJUXyH2MG5VnLd6uw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/darwin-arm64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.17.tgz", - "integrity": "sha512-M9uJ9VSB1oli2BE/dJs3zVr9kcCBBsE883prage1NWz6pBS++1oNn/7soPNS3+1DGj0FrkSvnED4Bmlu1VAE9g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/darwin-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.17.tgz", - "integrity": "sha512-XDre+J5YeIJDMfp3n0279DFNrGCXlxOuGsWIkRb1NThMZ0BsrWXoTg23Jer7fEXQ9Ye5QjrvXpxnhzl3bHtk0g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.17.tgz", - "integrity": "sha512-cjTzGa3QlNfERa0+ptykyxs5A6FEUQQF0MuilYXYBGdBxD3vxJcKnzDlhDCa1VAJCmAxed6mYhA2KaJIbtiNuQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/freebsd-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.17.tgz", - "integrity": "sha512-sOxEvR8d7V7Kw8QqzxWc7bFfnWnGdaFBut1dRUYtu+EIRXefBc/eIsiUiShnW0hM3FmQ5Zf27suDuHsKgZ5QrA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-arm": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.17.tgz", - "integrity": "sha512-2d3Lw6wkwgSLC2fIvXKoMNGVaeY8qdN0IC3rfuVxJp89CRfA3e3VqWifGDfuakPmp90+ZirmTfye1n4ncjv2lg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-arm64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.17.tgz", - "integrity": "sha512-c9w3tE7qA3CYWjT+M3BMbwMt+0JYOp3vCMKgVBrCl1nwjAlOMYzEo+gG7QaZ9AtqZFj5MbUc885wuBBmu6aADQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-ia32": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.17.tgz", - "integrity": "sha512-1DS9F966pn5pPnqXYz16dQqWIB0dmDfAQZd6jSSpiT9eX1NzKh07J6VKR3AoXXXEk6CqZMojiVDSZi1SlmKVdg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-loong64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.17.tgz", - "integrity": "sha512-EvLsxCk6ZF0fpCB6w6eOI2Fc8KW5N6sHlIovNe8uOFObL2O+Mr0bflPHyHwLT6rwMg9r77WOAWb2FqCQrVnwFg==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-mips64el": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.17.tgz", - "integrity": "sha512-e0bIdHA5p6l+lwqTE36NAW5hHtw2tNRmHlGBygZC14QObsA3bD4C6sXLJjvnDIjSKhW1/0S3eDy+QmX/uZWEYQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-ppc64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.17.tgz", - "integrity": "sha512-BAAilJ0M5O2uMxHYGjFKn4nJKF6fNCdP1E0o5t5fvMYYzeIqy2JdAP88Az5LHt9qBoUa4tDaRpfWt21ep5/WqQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-riscv64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.17.tgz", - "integrity": "sha512-Wh/HW2MPnC3b8BqRSIme/9Zhab36PPH+3zam5pqGRH4pE+4xTrVLx2+XdGp6fVS3L2x+DrsIcsbMleex8fbE6g==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-s390x": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.17.tgz", - "integrity": "sha512-j/34jAl3ul3PNcK3pfI0NSlBANduT2UO5kZ7FCaK33XFv3chDhICLY8wJJWIhiQ+YNdQ9dxqQctRg2bvrMlYgg==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/linux-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.17.tgz", - "integrity": "sha512-QM50vJ/y+8I60qEmFxMoxIx4de03pGo2HwxdBeFd4nMh364X6TIBZ6VQ5UQmPbQWUVWHWws5MmJXlHAXvJEmpQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/netbsd-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.17.tgz", - "integrity": "sha512-/jGlhWR7Sj9JPZHzXyyMZ1RFMkNPjC6QIAan0sDOtIo2TYk3tZn5UDrkE0XgsTQCxWTTOcMPf9p6Rh2hXtl5TQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/openbsd-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.17.tgz", - "integrity": "sha512-rSEeYaGgyGGf4qZM2NonMhMOP/5EHp4u9ehFiBrg7stH6BYEEjlkVREuDEcQ0LfIl53OXLxNbfuIj7mr5m29TA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/sunos-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.17.tgz", - "integrity": "sha512-Y7ZBbkLqlSgn4+zot4KUNYst0bFoO68tRgI6mY2FIM+b7ZbyNVtNbDP5y8qlu4/knZZ73fgJDlXID+ohY5zt5g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/win32-arm64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.17.tgz", - "integrity": "sha512-bwPmTJsEQcbZk26oYpc4c/8PvTY3J5/QK8jM19DVlEsAB41M39aWovWoHtNm78sd6ip6prilxeHosPADXtEJFw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/win32-ia32": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.17.tgz", - "integrity": "sha512-H/XaPtPKli2MhW+3CQueo6Ni3Avggi6hP/YvgkEe1aSaxw+AeO8MFjq8DlgfTd9Iz4Yih3QCZI6YLMoyccnPRg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/win32-x64": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.17.tgz", - "integrity": "sha512-fGEb8f2BSA3CW7riJVurug65ACLuQAzKq0SSqkY2b2yHHH0MzDfbLyKIGzHwOI/gkHcxM/leuSW6D5w/LMNitA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@types/node": { - "version": "20.4.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.8.tgz", - "integrity": "sha512-0mHckf6D2DiIAzh8fM8f3HQCvMKDpK94YQ0DSVkfWTG9BZleYIWudw9cJxX8oCk9bM+vAkDyujDV6dmKHbvQpg==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@angular-devkit/build-angular/node_modules/@vitejs/plugin-basic-ssl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.0.1.tgz", - "integrity": "sha512-pcub+YbFtFhaGRTo1832FQHQSHvMrlb43974e2eS8EKleR3p1cDdkJFPci1UhwkEf1J9Bz+wKBSzqpKp7nNj2A==", - "dev": true, - "engines": { - "node": ">=14.6.0" - }, - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/esbuild": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.17.tgz", - "integrity": "sha512-1GJtYnUxsJreHYA0Y+iQz2UEykonY66HNWOb0yXYZi9/kNrORUEHVg87eQsCtqh59PEJ5YVZJO98JHznMJSWjg==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.18.17", - "@esbuild/android-arm64": "0.18.17", - "@esbuild/android-x64": "0.18.17", - "@esbuild/darwin-arm64": "0.18.17", - "@esbuild/darwin-x64": "0.18.17", - "@esbuild/freebsd-arm64": "0.18.17", - "@esbuild/freebsd-x64": "0.18.17", - "@esbuild/linux-arm": "0.18.17", - "@esbuild/linux-arm64": "0.18.17", - "@esbuild/linux-ia32": "0.18.17", - "@esbuild/linux-loong64": "0.18.17", - "@esbuild/linux-mips64el": "0.18.17", - "@esbuild/linux-ppc64": "0.18.17", - "@esbuild/linux-riscv64": "0.18.17", - "@esbuild/linux-s390x": "0.18.17", - "@esbuild/linux-x64": "0.18.17", - "@esbuild/netbsd-x64": "0.18.17", - "@esbuild/openbsd-x64": "0.18.17", - "@esbuild/sunos-x64": "0.18.17", - "@esbuild/win32-arm64": "0.18.17", - "@esbuild/win32-ia32": "0.18.17", - "@esbuild/win32-x64": "0.18.17" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/vite": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.7.tgz", - "integrity": "sha512-6pYf9QJ1mHylfVh39HpuSfMPojPSKVxZvnclX1K1FyZ1PXDOcLBibdq5t1qxJSnL63ca8Wf4zts6mD8u8oc9Fw==", - "dev": true, - "dependencies": { - "esbuild": "^0.18.10", - "postcss": "^8.4.26", - "rollup": "^3.25.2" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "@types/node": ">= 14", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@angular-devkit/build-webpack": { - "version": "0.1602.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1602.0.tgz", - "integrity": "sha512-KdSr6iAcO30i/LIGL8mYi+d1buVXuDCp2dptzEJ4vxReOMFJca90KLwb+tVHEqqnDb0WkNfWm8Ii2QYh2FrNyA==", - "dev": true, - "dependencies": { - "@angular-devkit/architect": "0.1602.0", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "webpack": "^5.30.0", - "webpack-dev-server": "^4.0.0" - } - }, - "node_modules/@angular-devkit/build-webpack/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@angular-devkit/core": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.0.tgz", - "integrity": "sha512-l1k6Rqm3YM16BEn3CWyQKrk9xfu+2ux7Bw3oS+h1TO4/RoxO2PgHj8LLRh/WNrYVarhaqO7QZ5ePBkXNMkzJ1g==", - "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.0", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/core/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@angular-devkit/schematics": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.2.0.tgz", - "integrity": "sha512-QMDJXPE0+YQJ9Ap3MMzb0v7rx6ZbBEokmHgpdIjN3eILYmbAdsSGE8HTV8NjS9nKmcyE9OGzFCMb7PFrDTlTAw==", - "dependencies": { - "@angular-devkit/core": "16.2.0", - "jsonc-parser": "3.2.0", - "magic-string": "0.30.1", - "ora": "5.4.1", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@angular-eslint/builder": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-16.1.0.tgz", - "integrity": "sha512-KIkE2SI1twFKoCiF/k2VR3ojOcc7TD1xPyY4kbUrx/Gxp+XEzar7O29I/ztzL4eHPBM+Uh3/NwS/jvjjBxjgAg==", - "dev": true, - "dependencies": { - "@nx/devkit": "16.5.1", - "nx": "16.5.1" - }, - "peerDependencies": { - "eslint": "^7.20.0 || ^8.0.0", - "typescript": "*" - } - }, - "node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-16.1.0.tgz", - "integrity": "sha512-5EFAWXuFJADr3imo/ZYshY8s0K7U7wyysnE2LXnpT9PAi5rmkzt70UNZNRuamCbXr4tdIiu+fXWOj7tUuJKnnw==", - "dev": true - }, - "node_modules/@angular-eslint/eslint-plugin": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-16.1.0.tgz", - "integrity": "sha512-BFzzJJlgQgWc8avdSBkaDWAzNSUqcwWy0L1iZSBdXGoIOxj72kLbwe99emb8M+rUfCveljQkeM2pcYu8XLbJIA==", - "dev": true, - "dependencies": { - "@angular-eslint/utils": "16.1.0", - "@typescript-eslint/utils": "5.62.0" - }, - "peerDependencies": { - "eslint": "^7.20.0 || ^8.0.0", - "typescript": "*" - } - }, - "node_modules/@angular-eslint/eslint-plugin-template": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-16.1.0.tgz", - "integrity": "sha512-wQHWR5vqWGgO7mqoG5ixXeplIlz/OmxBJE9QMLPTZE8GdaTx8+F/5J37OWh84zCpD3mOa/FHYZxBDm2MfUmA1Q==", - "dev": true, - "dependencies": { - "@angular-eslint/bundled-angular-compiler": "16.1.0", - "@angular-eslint/utils": "16.1.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "aria-query": "5.3.0", - "axobject-query": "3.1.1" - }, - "peerDependencies": { - "eslint": "^7.20.0 || ^8.0.0", - "typescript": "*" - } - }, - "node_modules/@angular-eslint/schematics": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-16.1.0.tgz", - "integrity": "sha512-L1tmP3R2krHyveaRXAvn/SeDoBFNpS1VtPPrzZm1NYr1qPcAxf3NtG2nnoyVFu6WZGt59ZGHNQ/dZxnXvm0UGg==", - "dev": true, - "dependencies": { - "@angular-eslint/eslint-plugin": "16.1.0", - "@angular-eslint/eslint-plugin-template": "16.1.0", - "@nx/devkit": "16.5.1", - "ignore": "5.2.4", - "nx": "16.5.1", - "strip-json-comments": "3.1.1", - "tmp": "0.2.1" - }, - "peerDependencies": { - "@angular/cli": ">= 16.0.0 < 17.0.0" - } - }, - "node_modules/@angular-eslint/template-parser": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-16.1.0.tgz", - "integrity": "sha512-DOQtzVehtbO7+BQ+FMOXRsxGRjHb3ve6M+S4qASKTiI+twtONjRODcHezD3N4PDkjpKPbOnk7YnFsHur5csUNw==", - "dev": true, - "dependencies": { - "@angular-eslint/bundled-angular-compiler": "16.1.0", - "eslint-scope": "^7.0.0" - }, - "peerDependencies": { - "eslint": "^7.20.0 || ^8.0.0", - "typescript": "*" - } - }, - "node_modules/@angular-eslint/utils": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-16.1.0.tgz", - "integrity": "sha512-u5XscYUq1F/7RuwyVIV2a280QL27lyQz434VYR+Np/oO21NGj5jxoRKb55xhXT9EFVs5Sy4JYeEUp6S75J/cUw==", - "dev": true, - "dependencies": { - "@angular-eslint/bundled-angular-compiler": "16.1.0", - "@typescript-eslint/utils": "5.62.0" - }, - "peerDependencies": { - "eslint": "^7.20.0 || ^8.0.0", - "typescript": "*" - } - }, - "node_modules/@angular/animations": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-16.2.0.tgz", - "integrity": "sha512-SgOjldgRlU6XL1f6OUmFa+1iiy1OCWXH8i7q7g0yGCeQ4XAlvNRjDj++xxvUwDhE2pLKJLPYDJmCH98mvjKZcA==", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0" - }, - "peerDependencies": { - "@angular/core": "16.2.0" - } - }, - "node_modules/@angular/cdk": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-16.2.0.tgz", - "integrity": "sha512-pOIXP15uQkl3bf7t0i25+0uBjkHkVmBgwOMlqE9imY4gGq7UswbZRYHaGudJITin2ASFqKDgKvwNRCBqfmjO4A==", - "dependencies": { - "tslib": "^2.3.0" - }, - "optionalDependencies": { - "parse5": "^7.1.2" - }, - "peerDependencies": { - "@angular/common": "^16.0.0 || ^17.0.0", - "@angular/core": "^16.0.0 || ^17.0.0", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/@angular/cli": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-16.2.0.tgz", - "integrity": "sha512-xT8vJOyw6Rc2364XDW2jHagLgKu7342ktd/lt+c0u6R+AB2XVFMePR7VceLohX9N/vRUsbQ0nVSZr+ru/hA+HA==", - "dev": true, - "dependencies": { - "@angular-devkit/architect": "0.1602.0", - "@angular-devkit/core": "16.2.0", - "@angular-devkit/schematics": "16.2.0", - "@schematics/angular": "16.2.0", - "@yarnpkg/lockfile": "1.1.0", - "ansi-colors": "4.1.3", - "ini": "4.1.1", - "inquirer": "8.2.4", - "jsonc-parser": "3.2.0", - "npm-package-arg": "10.1.0", - "npm-pick-manifest": "8.0.1", - "open": "8.4.2", - "ora": "5.4.1", - "pacote": "15.2.0", - "resolve": "1.22.2", - "semver": "7.5.4", - "symbol-observable": "4.0.0", - "yargs": "17.7.2" - }, - "bin": { - "ng": "bin/ng.js" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular/cli/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@angular/cli/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@angular/cli/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@angular/common": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-16.2.0.tgz", - "integrity": "sha512-ByrDLsTBarzqRmq4GS841Ku0lvB4L2wfOCfGEIw2ZuiNbZlDA5O/qohQgJnHR5d9meVJnu9NgdbeyMzk90xZNg==", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0" - }, - "peerDependencies": { - "@angular/core": "16.2.0", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/@angular/compiler": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-16.2.0.tgz", - "integrity": "sha512-Ai0CKRUDlMY6iFCeoRsC+soVFTU7eyMDmNzeakdmNvGYMdLdjH8WvgaNukesi6WX7YBIQIKTPJVral8fXBQroQ==", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0" - }, - "peerDependencies": { - "@angular/core": "16.2.0" - }, - "peerDependenciesMeta": { - "@angular/core": { - "optional": true - } - } - }, - "node_modules/@angular/compiler-cli": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-16.2.0.tgz", - "integrity": "sha512-IGRpEJwbzOLFsLj2qgTHpZ6nNcRjKDYaaAnVx+B1CfK4DP31PIsZLgsWcEcYt7KbF/FUlrCNwdBxrqE7rDxZaw==", - "dev": true, - "dependencies": { - "@babel/core": "7.22.5", - "@jridgewell/sourcemap-codec": "^1.4.14", - "chokidar": "^3.0.0", - "convert-source-map": "^1.5.1", - "reflect-metadata": "^0.1.2", - "semver": "^7.0.0", - "tslib": "^2.3.0", - "yargs": "^17.2.1" - }, - "bin": { - "ng-xi18n": "bundles/src/bin/ng_xi18n.js", - "ngc": "bundles/src/bin/ngc.js", - "ngcc": "bundles/ngcc/index.js" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0" - }, - "peerDependencies": { - "@angular/compiler": "16.2.0", - "typescript": ">=4.9.3 <5.2" - } - }, - "node_modules/@angular/core": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-16.2.0.tgz", - "integrity": "sha512-iwUWFw+JmRxw0chcNoqhXVR8XUTE+Rszhy22iSCkK0Jo8IJqEad1d2dQoFu1QfqOVdPMZtpJDmC/ppQ/f5c5aA==", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0" - }, - "peerDependencies": { - "rxjs": "^6.5.3 || ^7.4.0", - "zone.js": "~0.13.0" - } - }, - "node_modules/@angular/forms": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.2.0.tgz", - "integrity": "sha512-Z/IFw319ZSgGbJFkR5Ba0sRIIqDxQDVH4I+vnVoOYqq2NxuHYfLJDHAB9uHln9GWj86b1SrJBZe8qiS7Sxb7yQ==", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0" - }, - "peerDependencies": { - "@angular/common": "16.2.0", - "@angular/core": "16.2.0", - "@angular/platform-browser": "16.2.0", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/@angular/platform-browser": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.2.0.tgz", - "integrity": "sha512-6xjZFnSD0C8ylDbzKpsxCJ4pLJDRvippr9Wj9RCeDQvAzMibsqIjpbesyOccw3hO+jheJQRhM/rZeO1ubZU94w==", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0" - }, - "peerDependencies": { - "@angular/animations": "16.2.0", - "@angular/common": "16.2.0", - "@angular/core": "16.2.0" - }, - "peerDependenciesMeta": { - "@angular/animations": { - "optional": true - } - } - }, - "node_modules/@angular/platform-browser-dynamic": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-16.2.0.tgz", - "integrity": "sha512-kLxgR+ichWb6dNA1JUAh0JB+iSrObkomd10porGQWVxAGmHqg1eiB3bBaSAgcaLftsrmEguIH8O9AEfq+HLfrA==", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0" - }, - "peerDependencies": { - "@angular/common": "16.2.0", - "@angular/compiler": "16.2.0", - "@angular/core": "16.2.0", - "@angular/platform-browser": "16.2.0" - } - }, - "node_modules/@angular/router": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-16.2.0.tgz", - "integrity": "sha512-bFOaE7PNF0UHgVhl8BvyHiZHizTRZO7w3V29VqsdXUMMugBR4kr1/FXGzXTaz+9/eK7LokUwN9pjKKENNmhdyg==", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0" - }, - "peerDependencies": { - "@angular/common": "16.2.0", - "@angular/core": "16.2.0", - "@angular/platform-browser": "16.2.0", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/@angular/service-worker": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-16.2.0.tgz", - "integrity": "sha512-TO2F0NQHTyd/A3LkXYVML81zKsT5W1Bexu4w2BPbp7FG4yt1Ad29wYf2EJnPLYXctftxtA0t+P9Vsr3mN3IZbA==", - "dependencies": { - "tslib": "^2.3.0" - }, - "bin": { - "ngsw-config": "ngsw-config.js" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0" - }, - "peerDependencies": { - "@angular/common": "16.2.0", - "@angular/core": "16.2.0" - } - }, "node_modules/@ant-design/colors": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.0.0.tgz", - "integrity": "sha512-iVm/9PfGCbC0dSMBrz7oiEXZaaGH7ceU40OJEfKmyuzR9R5CRimJYPlRiFtMQGQcbNMea/ePcoIebi4ASGYXtg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.0.tgz", + "integrity": "sha512-bjTObSnZ9C/O8MB/B4OUtd/q9COomuJAR2SYfhxLyHvCKn4EKwCN3e+fWGMo7H5InAyV0wL17jdE9ALrdOW/6A==", + "license": "MIT", "dependencies": { - "@ctrl/tinycolor": "^3.4.0" + "@ant-design/fast-color": "^2.0.6" } }, + "node_modules/@ant-design/compatible": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@ant-design/compatible/-/compatible-5.1.4.tgz", + "integrity": "sha512-sykrus7W8SDtDE2BefzMlQpug18O4haIqZW/zrhTSSYUKO77yIOqL5BFMgs5QSvgghSzjMRNNKzn4dX43I0TQg==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.6.1", + "classnames": "^2.2.6", + "dayjs": "^1.11.4", + "lodash.camelcase": "^4.3.0", + "lodash.upperfirst": "^4.3.1", + "rc-animate": "^3.1.1", + "rc-form": "^2.4.12", + "rc-util": "^5.24.5" + }, + "peerDependencies": { + "antd": "^5.0.1", + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/css-animation": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@ant-design/css-animation/-/css-animation-1.7.3.tgz", + "integrity": "sha512-LrX0OGZtW+W6iLnTAqnTaoIsRelYeuLZWsrmBJFUXDALQphPsN8cE5DCsmoSlL0QYb94BQxINiuS70Ar/8BNgA==", + "license": "MIT" + }, "node_modules/@ant-design/cssinjs": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.16.1.tgz", - "integrity": "sha512-KKVB5Or6BDC1Bo3Y4KMlOkyQU0P+6GTodubrQ9YfrtXG1TgO4wpaEfg9I4ZA49R7M+Ij2KKNwb+5abvmXy6K8w==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.23.0.tgz", + "integrity": "sha512-7GAg9bD/iC9ikWatU9ym+P9ugJhi/WbsTWzcKN6T4gU0aehsprtke1UAaaSxxkjjmkJb3llet/rbUSLPgwlY4w==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.1", "@emotion/hash": "^0.8.0", "@emotion/unitless": "^0.7.5", "classnames": "^2.3.1", - "csstype": "^3.0.10", + "csstype": "^3.1.3", "rc-util": "^5.35.0", - "stylis": "^4.0.13" + "stylis": "^4.3.4" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.3.tgz", + "integrity": "sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^1.21.0", + "@babel/runtime": "^7.23.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz", + "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + } + }, "node_modules/@ant-design/icons": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.2.5.tgz", - "integrity": "sha512-9Jc59v5fl5dzmxqLWtRev3dJwU7Ya9ZheoI6XmZjZiQ7PRtk77rC+Rbt7GJzAPPg43RQ4YO53RE1u8n+Et97vQ==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", "dependencies": { "@ant-design/colors": "^7.0.0", - "@ant-design/icons-svg": "^4.3.0", - "@babel/runtime": "^7.11.2", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", "classnames": "^2.2.6", - "lodash.camelcase": "^4.3.0", "rc-util": "^5.31.1" }, "engines": { @@ -1290,30 +211,334 @@ "react-dom": ">=16.0.0" } }, - "node_modules/@ant-design/icons-angular": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@ant-design/icons-angular/-/icons-angular-16.0.0.tgz", - "integrity": "sha512-KWBmWZl2so49R/MdAT7aG+xaBlMKl9SArR3Du/iPA0Am9GI1i9R89KgnnLWz+gkzHTye15S1IBXpgts4GPPU/w==", + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "license": "MIT" + }, + "node_modules/@ant-design/pro-card": { + "version": "2.9.7", + "resolved": "https://registry.npmjs.org/@ant-design/pro-card/-/pro-card-2.9.7.tgz", + "integrity": "sha512-uDDYowmYH1ldRfG8Mb4QOwcEEz6ptRBQDLO1tkVADCRkdOMwz82xlZneR4uVuFyKcuNmgHzarYNncozBKhFuaA==", + "license": "MIT", "dependencies": { - "@ant-design/colors": "^7.0.0", + "@ant-design/cssinjs": "^1.21.1", + "@ant-design/icons": "^5.0.0", + "@ant-design/pro-provider": "2.15.4", + "@ant-design/pro-utils": "2.17.0", + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.4.0" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "react": ">=17.0.0" + } + }, + "node_modules/@ant-design/pro-components": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/@ant-design/pro-components/-/pro-components-2.8.7.tgz", + "integrity": "sha512-QhibkPsUJryEjI1QmwUn+XCngGHidu0ekvricL6TIEvPgP+AUAca29XutN5+Mmn8Xfja1ca9HFTHTgFoV74Z7Q==", + "license": "MIT", + "dependencies": { + "@ant-design/pro-card": "2.9.7", + "@ant-design/pro-descriptions": "2.6.7", + "@ant-design/pro-field": "3.0.4", + "@ant-design/pro-form": "2.31.7", + "@ant-design/pro-layout": "7.22.4", + "@ant-design/pro-list": "2.6.7", + "@ant-design/pro-provider": "2.15.4", + "@ant-design/pro-skeleton": "2.2.1", + "@ant-design/pro-table": "3.19.0", + "@ant-design/pro-utils": "2.17.0", + "@babel/runtime": "^7.16.3" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@ant-design/pro-descriptions": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/@ant-design/pro-descriptions/-/pro-descriptions-2.6.7.tgz", + "integrity": "sha512-fgn2d0kDWUODGDWKpgziZuuqPlmIoKxQFJY9Yg4nbaRp8GDDKZeSSqgvW+OxjpYM8dxq31fiz1dZlZnOPoYKpg==", + "license": "MIT", + "dependencies": { + "@ant-design/pro-field": "3.0.4", + "@ant-design/pro-form": "2.31.7", + "@ant-design/pro-provider": "2.15.4", + "@ant-design/pro-skeleton": "2.2.1", + "@ant-design/pro-utils": "2.17.0", + "@babel/runtime": "^7.18.0", + "rc-resize-observer": "^0.2.3", + "rc-util": "^5.0.6" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "react": ">=17.0.0" + } + }, + "node_modules/@ant-design/pro-descriptions/node_modules/rc-resize-observer": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-0.2.6.tgz", + "integrity": "sha512-YX6nYnd6fk7zbuvT6oSDMKiZjyngjHoy+fz+vL3Tez38d/G5iGdaDJa2yE7345G6sc4Mm1IGRUIwclvltddhmA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-util": "^5.0.0", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/pro-field": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@ant-design/pro-field/-/pro-field-3.0.4.tgz", + "integrity": "sha512-nJSng/6/pPZFdiFeTtZcBQLNrHg9tIeiKFR1+zzbnQbI3qBOFP9aBZS/+LwkQZcI2G71vrRgz2x5OhHb7AX0wQ==", + "license": "MIT", + "dependencies": { + "@ant-design/icons": "^5.0.0", + "@ant-design/pro-provider": "2.15.4", + "@ant-design/pro-utils": "2.17.0", + "@babel/runtime": "^7.18.0", + "@chenshuai2144/sketch-color": "^1.0.8", + "classnames": "^2.3.2", + "dayjs": "^1.11.10", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "rc-util": "^5.4.0", + "swr": "^2.0.0" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "react": ">=17.0.0" + } + }, + "node_modules/@ant-design/pro-form": { + "version": "2.31.7", + "resolved": "https://registry.npmjs.org/@ant-design/pro-form/-/pro-form-2.31.7.tgz", + "integrity": "sha512-0TCtIC/ynbLPoes8sLBFwFbi0tkeNmSU6the2EcyKIKDLfWHDbfkLM1OSFrzv3QD+H8OgFWMkTSOjhMOKSsOBg==", + "license": "MIT", + "dependencies": { + "@ant-design/icons": "^5.0.0", + "@ant-design/pro-field": "3.0.4", + "@ant-design/pro-provider": "2.15.4", + "@ant-design/pro-utils": "2.17.0", + "@babel/runtime": "^7.18.0", + "@chenshuai2144/sketch-color": "^1.0.7", + "@umijs/use-params": "^1.0.9", + "classnames": "^2.3.2", + "dayjs": "^1.11.10", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "rc-resize-observer": "^1.1.0", + "rc-util": "^5.0.6" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "rc-field-form": ">=1.22.0", + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@ant-design/pro-layout": { + "version": "7.22.4", + "resolved": "https://registry.npmjs.org/@ant-design/pro-layout/-/pro-layout-7.22.4.tgz", + "integrity": "sha512-X2WO4L2itXemX4zhS+0NG+8kXQD5SX9sG+zjx/15BmIO3FvsUGqOHgoCg0vhd424EiyPj7WtdMZJ39G1xdgDwA==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^1.21.1", + "@ant-design/icons": "^5.0.0", + "@ant-design/pro-provider": "2.15.4", + "@ant-design/pro-utils": "2.17.0", + "@babel/runtime": "^7.18.0", + "@umijs/route-utils": "^4.0.0", + "@umijs/use-params": "^1.0.9", + "classnames": "^2.3.2", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "path-to-regexp": "8.2.0", + "rc-resize-observer": "^1.1.0", + "rc-util": "^5.0.6", + "swr": "^2.0.0", + "warning": "^4.0.3" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@ant-design/pro-list": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/@ant-design/pro-list/-/pro-list-2.6.7.tgz", + "integrity": "sha512-6k/En7pioMgepho/1HMf2DAnkSTZiat1lDg2ggCok2lhSgqXzir7x22ewJQRgPvEiVb6/qqaFQNd7a8dnrFj1w==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^1.21.1", + "@ant-design/icons": "^5.0.0", + "@ant-design/pro-card": "2.9.7", + "@ant-design/pro-field": "3.0.4", + "@ant-design/pro-table": "3.19.0", + "@ant-design/pro-utils": "2.17.0", + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "dayjs": "^1.11.10", + "rc-resize-observer": "^1.0.0", + "rc-util": "^4.19.0" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@ant-design/pro-list/node_modules/rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "license": "MIT", + "dependencies": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" + } + }, + "node_modules/@ant-design/pro-list/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/@ant-design/pro-provider": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@ant-design/pro-provider/-/pro-provider-2.15.4.tgz", + "integrity": "sha512-DBX0JNUNOYXAucVqd/zTdqtXckCDqr2Lo85KIku2YzWdhptDPDZRTNqL04JShjGejDl8fzwQ8yREHgVUfzn6Gg==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^1.21.1", + "@babel/runtime": "^7.18.0", + "@ctrl/tinycolor": "^3.4.0", + "dayjs": "^1.11.10", + "rc-util": "^5.0.1", + "swr": "^2.0.0" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@ant-design/pro-skeleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ant-design/pro-skeleton/-/pro-skeleton-2.2.1.tgz", + "integrity": "sha512-3M2jNOZQZWEDR8pheY00OkHREfb0rquvFZLCa6DypGmiksiuuYuR9Y4iA82ZF+mva2FmpHekdwbje/GpbxqBeg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@ant-design/pro-table": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@ant-design/pro-table/-/pro-table-3.19.0.tgz", + "integrity": "sha512-nL25734d5q5oqtmG7Apn2TNJUnJE8m9dkopXMQdoNZnv8qeRQLBH+i5cZT1yh7FIO8z6QLXleg+KnR/cI7VRRw==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^1.21.1", + "@ant-design/icons": "^5.0.0", + "@ant-design/pro-card": "2.9.7", + "@ant-design/pro-field": "3.0.4", + "@ant-design/pro-form": "2.31.7", + "@ant-design/pro-provider": "2.15.4", + "@ant-design/pro-utils": "2.17.0", + "@babel/runtime": "^7.18.0", + "@dnd-kit/core": "^6.0.8", + "@dnd-kit/modifiers": "^6.0.1", + "@dnd-kit/sortable": "^7.0.2", + "@dnd-kit/utilities": "^3.2.1", + "classnames": "^2.3.2", + "dayjs": "^1.11.10", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.0.1" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "rc-field-form": ">=1.22.0", + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@ant-design/pro-table/node_modules/@dnd-kit/modifiers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-6.0.1.tgz", + "integrity": "sha512-rbxcsg3HhzlcMHVHWDuh9LCjpOVAgqbV78wLGI8tziXY3+qcMQ61qVXIvNKQFuhj75dSfD+o+PYZQ/NUk2A23A==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.1", "tslib": "^2.0.0" }, "peerDependencies": { - "@angular/common": "^16.0.0", - "@angular/core": "^16.0.0", - "@angular/platform-browser": "^16.0.0", - "rxjs": "^6.4.0 || ^7.4.0" + "@dnd-kit/core": "^6.0.6", + "react": ">=16.8.0" } }, - "node_modules/@ant-design/icons-svg": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.3.0.tgz", - "integrity": "sha512-WOgvdH/1Wl8Z7VXigRbCa5djO14zxrNTzvrAQzhWiBQtEKT0uTc8K1ltjKZ8U1gPn/wXhMA8/jE39SJl0WNxSg==" + "node_modules/@ant-design/pro-table/node_modules/@dnd-kit/sortable": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-7.0.2.tgz", + "integrity": "sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.0", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.0.7", + "react": ">=16.8.0" + } + }, + "node_modules/@ant-design/pro-utils": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@ant-design/pro-utils/-/pro-utils-2.17.0.tgz", + "integrity": "sha512-hHKUISjMEoS+E5ltJWyvNTrlEA3IimZNxtDrEhorRIbgVYAlmEN5Mj/ESSofzDM3+UlxiI5+A/Y6IHkByTfDEA==", + "license": "MIT", + "dependencies": { + "@ant-design/icons": "^5.0.0", + "@ant-design/pro-provider": "2.15.4", + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "dayjs": "^1.11.10", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "rc-util": "^5.0.6", + "safe-stable-stringify": "^2.4.3", + "swr": "^2.0.0" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } }, "node_modules/@ant-design/react-slick": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.0.2.tgz", - "integrity": "sha512-Wj8onxL/T8KQLFFiCA4t8eIRGpRR+UPgOdac2sYzonv+i0n3kXHmvHLLiOYL655DQx2Umii9Y9nNgL7ssu5haQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.1.2.tgz", + "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.4", "classnames": "^2.2.5", @@ -1325,55 +550,52 @@ "react": ">=16.9.0" } }, - "node_modules/@assemblyscript/loader": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.10.1.tgz", - "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", - "dev": true - }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", - "dev": true, + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", - "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", - "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "dev": true, + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helpers": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5", - "convert-source-map": "^1.7.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -1383,239 +605,69 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, - "bin": { - "semver": "bin/semver.js" - } + "license": "MIT" }, "node_modules/@babel/generator": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.9.tgz", - "integrity": "sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==", - "dev": true, + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", + "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.10.tgz", - "integrity": "sha512-Av0qubwDQxC56DoUReVDeLfMEjYYSN1nZrTUrWkXd7hpU73ymRANkbuDm3yni9npkn+RXy9nNbEJZEzXr7xrfQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.10" + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz", - "integrity": "sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", + "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.5", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.26.8", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.10.tgz", - "integrity": "sha512-5IBb77txKYQPpOEdUdIhBx8VrZyDCQ+H82H0+5dX1TmuscP5vJKEE3cKurjtIw/vFwzbVH48VweE78kVDBrqjA==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz", - "integrity": "sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "regexpu-core": "^5.3.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz", - "integrity": "sha512-k0qnnOqHn5dK9pZpfD5XXZ9SojAITdCKRn2Lp6rnDGzIbaP0rHyMPk/4wsSxVBVz4RfN0q6VpXWP2pDGIoQ7hw==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name/node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", - "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", - "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", - "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.5" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1624,171 +676,66 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz", - "integrity": "sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-wrap-function": "^7.22.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz", - "integrity": "sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", - "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.9.tgz", - "integrity": "sha512-sZ+QzfauuUEfxSEjKFmi3qDSHgLsTPK/pEpoD/qonZKOtTPTLbf59oabPQ4rKekt9lFcj/hTZaOhWwFYrgjk+Q==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" - }, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz", - "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.6", - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", - "dev": true, + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.0" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -1796,48 +743,14 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz", - "integrity": "sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==", + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz", - "integrity": "sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", - "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1846,65 +759,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1913,1400 +775,727 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", - "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", - "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", - "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.10.tgz", - "integrity": "sha512-eueE8lvKVzq5wIObKK/7dvoeKJ+xc6TvRn6aysIjS6pSCeLy7S/eVi7pEQknZqyqvzaNKdDtem8nUNTBgDVR2g==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", - "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", - "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.10.tgz", - "integrity": "sha512-1+kVpGAOOI1Albt6Vse7c8pHzcZQdQKW+wJH+g8mCaszOdDVwRXa/slHPqIw+oJAJANTKDMuM2cBdV0Dg618Vg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", - "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz", - "integrity": "sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz", - "integrity": "sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", - "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.10.tgz", - "integrity": "sha512-dPJrL0VOyxqLM9sritNbMSGx/teueHF/htMKrPT7DNxccXxRDPYqlgPFFdr8u+F+qUZOkZoXue/6rL5O5GduEw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", - "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", - "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz", - "integrity": "sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", - "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", - "dev": true, - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz", - "integrity": "sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz", - "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", - "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz", - "integrity": "sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", - "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz", - "integrity": "sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", - "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", - "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz", - "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz", - "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==", - "dev": true, - "dependencies": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", - "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", - "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz", - "integrity": "sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz", - "integrity": "sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz", - "integrity": "sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", - "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz", - "integrity": "sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.10.tgz", - "integrity": "sha512-MMkQqZAZ+MGj+jGTG3OTuhKeBpNcO+0oCEbrGNEaOmiEn+1MzRyQlYsruGiU8RTK3zV6XwrVJTmwiDOyYK6J9g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz", - "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", - "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz", - "integrity": "sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", - "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz", - "integrity": "sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "regenerator-transform": "^0.15.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", - "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.9.tgz", - "integrity": "sha512-9KjBH61AGJetCPYp/IEyLEp47SyybZb0nDRpBvmtEkm+rUIwxdlKpyNHI1TmsGkeuLclJdleQHRZ8XLBnnh8CQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.4", - "babel-plugin-polyfill-corejs3": "^0.8.2", - "babel-plugin-polyfill-regenerator": "^0.5.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", - "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", - "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", - "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", - "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", - "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz", - "integrity": "sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", - "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", - "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", - "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.9.tgz", - "integrity": "sha512-wNi5H/Emkhll/bqPjsjQorSykrlfY5OWakd6AulLvMEytpKasMVUpVy8RL4qBIBs5Ac6/5i0/Rv0b/Fg6Eag/g==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-compilation-targets": "^7.22.9", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.22.5", - "@babel/plugin-syntax-import-attributes": "^7.22.5", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.22.5", - "@babel/plugin-transform-async-generator-functions": "^7.22.7", - "@babel/plugin-transform-async-to-generator": "^7.22.5", - "@babel/plugin-transform-block-scoped-functions": "^7.22.5", - "@babel/plugin-transform-block-scoping": "^7.22.5", - "@babel/plugin-transform-class-properties": "^7.22.5", - "@babel/plugin-transform-class-static-block": "^7.22.5", - "@babel/plugin-transform-classes": "^7.22.6", - "@babel/plugin-transform-computed-properties": "^7.22.5", - "@babel/plugin-transform-destructuring": "^7.22.5", - "@babel/plugin-transform-dotall-regex": "^7.22.5", - "@babel/plugin-transform-duplicate-keys": "^7.22.5", - "@babel/plugin-transform-dynamic-import": "^7.22.5", - "@babel/plugin-transform-exponentiation-operator": "^7.22.5", - "@babel/plugin-transform-export-namespace-from": "^7.22.5", - "@babel/plugin-transform-for-of": "^7.22.5", - "@babel/plugin-transform-function-name": "^7.22.5", - "@babel/plugin-transform-json-strings": "^7.22.5", - "@babel/plugin-transform-literals": "^7.22.5", - "@babel/plugin-transform-logical-assignment-operators": "^7.22.5", - "@babel/plugin-transform-member-expression-literals": "^7.22.5", - "@babel/plugin-transform-modules-amd": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.22.5", - "@babel/plugin-transform-modules-systemjs": "^7.22.5", - "@babel/plugin-transform-modules-umd": "^7.22.5", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.22.5", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5", - "@babel/plugin-transform-numeric-separator": "^7.22.5", - "@babel/plugin-transform-object-rest-spread": "^7.22.5", - "@babel/plugin-transform-object-super": "^7.22.5", - "@babel/plugin-transform-optional-catch-binding": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.6", - "@babel/plugin-transform-parameters": "^7.22.5", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.5", - "@babel/plugin-transform-property-literals": "^7.22.5", - "@babel/plugin-transform-regenerator": "^7.22.5", - "@babel/plugin-transform-reserved-words": "^7.22.5", - "@babel/plugin-transform-shorthand-properties": "^7.22.5", - "@babel/plugin-transform-spread": "^7.22.5", - "@babel/plugin-transform-sticky-regex": "^7.22.5", - "@babel/plugin-transform-template-literals": "^7.22.5", - "@babel/plugin-transform-typeof-symbol": "^7.22.5", - "@babel/plugin-transform-unicode-escapes": "^7.22.5", - "@babel/plugin-transform-unicode-property-regex": "^7.22.5", - "@babel/plugin-transform-unicode-regex": "^7.22.5", - "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.4", - "babel-plugin-polyfill-corejs3": "^0.8.2", - "babel-plugin-polyfill-regenerator": "^0.5.1", - "core-js-compat": "^3.31.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6.tgz", - "integrity": "sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", - "dev": true - }, "node_modules/@babel/runtime": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", - "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "license": "MIT", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", - "dev": true, + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", - "dev": true, + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", - "dev": true, + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "engines": { - "node": ">=0.1.90" + "node_modules/@chenshuai2144/sketch-color": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@chenshuai2144/sketch-color/-/sketch-color-1.0.9.tgz", + "integrity": "sha512-obzSy26cb7Pm7OprWyVpgMpIlrZpZ0B7vbrU0RMbvRg0YAI890S5Xy02Aj1Nhl4+KTbi1lVYHt6HQP8Hm9s+1w==", + "license": "MIT", + "dependencies": { + "reactcss": "^1.2.3", + "tinycolor2": "^1.4.2" + }, + "peerDependencies": { + "react": ">=16.12.0" } }, "node_modules/@ctrl/tinycolor": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.0.tgz", - "integrity": "sha512-/Z3l6pXthq0JvMYdUFyX9j0MaCltlIn6mfh9jLyQwg5aPKxkyNa0PTHtU1AlFXLNk55ZuAeJRcpvq+tmLfKmaQ==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", "engines": { "node": ">=10" } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "dev": true, - "engines": { - "node": ">=10.0.0" + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" } }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/modifiers": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-9.0.0.tgz", + "integrity": "sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/babel-plugin/node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache/node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, "node_modules/@emotion/hash": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", - "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/serialize/node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/serialize/node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" }, "node_modules/@emotion/unitless": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "react": ">=16.8.0" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", - "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", + "cpu": [ + "ppc64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.1.tgz", - "integrity": "sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==", + "node_modules/@esbuild/android-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@esbuild/android-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/@esbuild/android-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/@eslint/js": { - "version": "8.46.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.46.0.tgz", - "integrity": "sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=18" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", - "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=10.10.0" + "node": ">=18" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=18" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -3319,204 +1508,77 @@ "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", - "dev": true, + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, "node_modules/@kurkle/color": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", - "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" - }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", - "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", - "dev": true - }, - "node_modules/@ngtools/webpack": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-16.2.0.tgz", - "integrity": "sha512-c9jv4r7GnLTpnPOeF+a9yAm/3/2wwl9lMBU32i9hlY+q/Hqde4PiL95bUOLnRRL1I64DV7BFTlSZqSPgDpFXZQ==", - "dev": true, - "engines": { - "node": "^16.14.0 || >=18.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "@angular/compiler-cli": "^16.0.0", - "typescript": ">=4.9.3 <5.2", - "webpack": "^5.54.0" - } + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -3529,6 +1591,8 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -3537,6 +1601,8 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -3545,389 +1611,45 @@ "node": ">= 8" } }, - "node_modules/@npmcli/fs": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", - "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==", - "dev": true, - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/git": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz", - "integrity": "sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==", - "dev": true, - "dependencies": { - "@npmcli/promise-spawn": "^6.0.0", - "lru-cache": "^7.4.4", - "npm-pick-manifest": "^8.0.0", - "proc-log": "^3.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@npmcli/git/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/installed-package-contents": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz", - "integrity": "sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ==", - "dev": true, - "dependencies": { - "npm-bundled": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "bin": { - "installed-package-contents": "lib/index.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/node-gyp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", - "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/promise-spawn": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", - "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", - "dev": true, - "dependencies": { - "which": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/run-script": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.2.tgz", - "integrity": "sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==", - "dev": true, - "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/promise-spawn": "^6.0.0", - "node-gyp": "^9.0.0", - "read-package-json-fast": "^3.0.0", - "which": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/run-script/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@nrwl/devkit": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-16.5.1.tgz", - "integrity": "sha512-NB+DE/+AFJ7lKH/WBFyatJEhcZGj25F24ncDkwjZ6MzEiSOGOJS0LaV/R+VUsmS5EHTPXYOpn3zHWWAcJhyOmA==", - "dev": true, - "dependencies": { - "@nx/devkit": "16.5.1" - } - }, - "node_modules/@nrwl/tao": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-16.5.1.tgz", - "integrity": "sha512-x+gi/fKdM6uQNIti9exFlm3V5LBP3Y8vOEziO42HdOigyrXa0S0HD2WMpccmp6PclYKhwEDUjKJ39xh5sdh4Ig==", - "dev": true, - "dependencies": { - "nx": "16.5.1" - }, - "bin": { - "tao": "index.js" - } - }, - "node_modules/@nx/devkit": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-16.5.1.tgz", - "integrity": "sha512-T1acZrVVmJw/sJ4PIGidCBYBiBqlg/jT9e8nIGXLSDS20xcLvfo4zBQf8UZLrmHglnwwpDpOWuVJCp2rYA5aDg==", - "dev": true, - "dependencies": { - "@nrwl/devkit": "16.5.1", - "ejs": "^3.1.7", - "ignore": "^5.0.4", - "semver": "7.5.3", - "tmp": "~0.2.1", - "tslib": "^2.3.0" - }, - "peerDependencies": { - "nx": ">= 15 <= 17" - } - }, - "node_modules/@nx/nx-darwin-arm64": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-16.5.1.tgz", - "integrity": "sha512-q98TFI4B/9N9PmKUr1jcbtD4yAFs1HfYd9jUXXTQOlfO9SbDjnrYJgZ4Fp9rMNfrBhgIQ4x1qx0AukZccKmH9Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nx/nx-darwin-x64": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-16.5.1.tgz", - "integrity": "sha512-j9HmL1l8k7EVJ3eOM5y8COF93gqrydpxCDoz23ZEtsY+JHY77VAiRQsmqBgEx9GGA2dXi9VEdS67B0+1vKariw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nx/nx-freebsd-x64": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-16.5.1.tgz", - "integrity": "sha512-CXSPT01aVS869tvCCF2tZ7LnCa8l41wJ3mTVtWBkjmRde68E5Up093hklRMyXb3kfiDYlfIKWGwrV4r0eH6x1A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nx/nx-linux-arm-gnueabihf": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-16.5.1.tgz", - "integrity": "sha512-BhrumqJSZCWFfLFUKl4CAUwR0Y0G2H5EfFVGKivVecEQbb+INAek1aa6c89evg2/OvetQYsJ+51QknskwqvLsA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nx/nx-linux-arm64-gnu": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-16.5.1.tgz", - "integrity": "sha512-x7MsSG0W+X43WVv7JhiSq2eKvH2suNKdlUHEG09Yt0vm3z0bhtym1UCMUg3IUAK7jy9hhLeDaFVFkC6zo+H/XQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nx/nx-linux-arm64-musl": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-16.5.1.tgz", - "integrity": "sha512-J+/v/mFjOm74I0PNtH5Ka+fDd+/dWbKhpcZ2R1/6b9agzZk+Ff/SrwJcSYFXXWKbPX+uQ4RcJoytT06Zs3s0ow==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nx/nx-linux-x64-gnu": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-16.5.1.tgz", - "integrity": "sha512-igooWJ5YxQ94Zft7IqgL+Lw0qHaY15Btw4gfK756g/YTYLZEt4tTvR1y6RnK/wdpE3sa68bFTLVBNCGTyiTiDQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nx/nx-linux-x64-musl": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-16.5.1.tgz", - "integrity": "sha512-zF/exnPqFYbrLAduGhTmZ7zNEyADid2bzNQiIjJkh8Y6NpDwrQIwVIyvIxqynsjMrIs51kBH+8TUjKjj2Jgf5A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nx/nx-win32-arm64-msvc": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-16.5.1.tgz", - "integrity": "sha512-qtqiLS9Y9TYyAbbpq58kRoOroko4ZXg5oWVqIWFHoxc5bGPweQSJCROEqd1AOl2ZDC6BxfuVHfhDDop1kK05WA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nx/nx-win32-x64-msvc": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-16.5.1.tgz", - "integrity": "sha512-kUJBLakK7iyA9WfsGGQBVennA4jwf5XIgm0lu35oMOphtZIluvzItMt0EYBmylEROpmpEIhHq0P6J9FA+WH0Rg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@parcel/watcher": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.0.4.tgz", - "integrity": "sha512-cTDi+FUDBIUOBKEtj+nhiJ71AZVlkAsQFuGQTun5tV9mwQBQgZvhCzG+URPQc8myeN32yRVZEfVAPCs1RW+Jvg==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "node-addon-api": "^3.2.1", - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + "node_modules/@paddle/paddle-js": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@paddle/paddle-js/-/paddle-js-1.4.0.tgz", + "integrity": "sha512-pX6Yx+RswB1rHMuYl8RKcAAVZhVJ6nd5f8w8l4kVM63pM3HNeQ5/Xuk4sK/X9P5fUE2dmN0mTti7+gZ8cZtqvg==", + "license": "Apache-2.0" }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=14" } }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" + "node_modules/@rc-component/async-validator": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz", + "integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" } }, "node_modules/@rc-component/color-picker": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-1.4.1.tgz", - "integrity": "sha512-vh5EWqnsayZa/JwUznqDaPJz39jznx/YDbyBuVJntv735tKXKwEUZZb2jYEldOg+NKWZwtALjGMrNeGBmqFoEw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-2.0.1.tgz", + "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.10.1", - "@ctrl/tinycolor": "^3.6.0", + "@ant-design/fast-color": "^2.0.6", + "@babel/runtime": "^7.23.6", "classnames": "^2.2.6", - "rc-util": "^5.30.0" + "rc-util": "^5.38.1" }, "peerDependencies": { "react": ">=16.9.0", @@ -3935,9 +1657,10 @@ } }, "node_modules/@rc-component/context": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-1.3.0.tgz", - "integrity": "sha512-6QdaCJ7Wn5UZLJs15IEfqy4Ru3OaL5ctqpQYWd5rlfV9wwzrzdt6+kgAQZV/qdB0MUPN4nhyBfRembQCIvBf+w==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-1.4.0.tgz", + "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", "rc-util": "^5.27.0" @@ -3951,6 +1674,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz", "integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.0" }, @@ -3959,9 +1683,10 @@ } }, "node_modules/@rc-component/mutate-observer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-1.0.0.tgz", - "integrity": "sha512-okqRJSfNisXdI6CUeOLZC5ukBW/8kir2Ii4PJiKpUt+3+uS7dxwJUMxsUZquxA1rQuL8YcEmKVp/TCnR+yUdZA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz", + "integrity": "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.0", "classnames": "^2.3.2", @@ -3979,6 +1704,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz", "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.0", "classnames": "^2.3.2", @@ -3992,14 +1718,33 @@ "react-dom": ">=16.9.0" } }, + "node_modules/@rc-component/qrcode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.0.0.tgz", + "integrity": "sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, "node_modules/@rc-component/tour": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.8.1.tgz", - "integrity": "sha512-CsrQnfKgNArxx2j1RNHVLZgVA+rLrEj06lIsl4KSynMqADsqz8eKvVkr0F3p9PA10948M6WEEZt5a/FGAbGR2A==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.15.1.tgz", + "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.0", "@rc-component/portal": "^1.0.0-9", - "@rc-component/trigger": "^1.3.6", + "@rc-component/trigger": "^2.0.0", "classnames": "^2.3.2", "rc-util": "^5.24.4" }, @@ -4012,17 +1757,17 @@ } }, "node_modules/@rc-component/trigger": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-1.15.1.tgz", - "integrity": "sha512-U1F9WsIMLXB2JLjLSEa6uWifmTX2vxQ1r0RQCLnor8d/83e3U7TuclNbcWcM/eGcgrT2YUZid3TLDDKbDOHmLg==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.2.6.tgz", + "integrity": "sha512-/9zuTnWwhQ3S3WT1T8BubuFTT46kvnXgaERR9f4BTKyn61/wpf/BvbImzYBubzJibU707FxwbKszLlHjcLiv1Q==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.18.3", + "@babel/runtime": "^7.23.2", "@rc-component/portal": "^1.1.0", "classnames": "^2.3.2", - "rc-align": "^4.0.0", "rc-motion": "^2.0.0", "rc-resize-observer": "^1.3.1", - "rc-util": "^5.33.0" + "rc-util": "^5.44.0" }, "engines": { "node": ">=8.x" @@ -4032,1021 +1777,883 @@ "react-dom": ">=16.9.0" } }, - "node_modules/@rx-angular/cdk": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@rx-angular/cdk/-/cdk-16.0.0.tgz", - "integrity": "sha512-/phlUZWvFoH67DHlKwVzUIoN3xJQIKTiyuOkVdknb6NhS1ofrEWF1YY0oPD0Cf5kCjug9qIJo7frrhgTwslTxQ==", + "node_modules/@reduxjs/toolkit": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.6.1.tgz", + "integrity": "sha512-SSlIqZNYhqm/oMkXbtofwZSt9lrncblzo6YcZ9zoX+zLngRBrCOjK4lNLdkNucJF58RHOWrD9txT3bT3piH7Zw==", + "license": "MIT", "dependencies": { - "ng-morph": "^2.2.3", - "tslib": "^2.4.1" + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" }, "peerDependencies": { - "@angular/core": "^16.0.0", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/@rx-angular/template": { - "version": "16.0.2", - "resolved": "https://registry.npmjs.org/@rx-angular/template/-/template-16.0.2.tgz", - "integrity": "sha512-W+vh/nCviT7WtAA7lZnBZzshiRbmfxTjeHCJTabq0w5vQVvlnGL+sgasjBLMPhp8KPM3UZ7cgQncxwMPsoVjYw==", - "dependencies": { - "ng-morph": "^2.2.3", - "tslib": "^2.4.1" + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, - "peerDependencies": { - "@angular/core": "^16.0.0", - "@rx-angular/cdk": "^16.0.0", - "rxjs": "^6.5.3 || ^7.4.0" + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } } }, - "node_modules/@schematics/angular": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-16.2.0.tgz", - "integrity": "sha512-Ib0/ZCkjWt7a5p3209JVwEWwf41v03K3ylvlxLIEo1ZGijAZAlrBj4GrA5YQ+TmPm2hRyt+owss7x91/x+i0Gw==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "16.2.0", - "@angular-devkit/schematics": "16.2.0", - "jsonc-parser": "3.2.0" - }, + "node_modules/@remix-run/router": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", + "license": "MIT", "engines": { - "node": "^16.14.0 || >=18.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "node": ">=14.0.0" } }, - "node_modules/@sigstore/bundle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-1.0.0.tgz", - "integrity": "sha512-yLvrWDOh6uMOUlFCTJIZEnwOT9Xte7NPXUqVexEKGSF5XtBAuSg5du0kn3dRR0p47a4ah10Y0mNt8+uyeQXrBQ==", + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.39.0.tgz", + "integrity": "sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "@sigstore/protobuf-specs": "^0.2.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@sigstore/protobuf-specs": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.2.0.tgz", - "integrity": "sha512-8ZhZKAVfXjIspDWwm3D3Kvj0ddbJ0HqDZ/pOs5cx88HpT8mVsotFrg7H1UMnXOuDHz6Zykwxn4mxG3QLuN+RUg==", + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.39.0.tgz", + "integrity": "sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==", + "cpu": [ + "arm64" + ], "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@sigstore/tuf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-1.0.3.tgz", - "integrity": "sha512-2bRovzs0nJZFlCN3rXirE4gwxCn97JNjMmwpecqlbgV9WcxX7WRuIrgzx/X7Ib7MYRbyUTpBYE0s2x6AmZXnlg==", + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.39.0.tgz", + "integrity": "sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@sigstore/protobuf-specs": "^0.2.0", - "tuf-js": "^1.1.7" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.39.0.tgz", + "integrity": "sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.39.0.tgz", + "integrity": "sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.39.0.tgz", + "integrity": "sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.39.0.tgz", + "integrity": "sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.39.0.tgz", + "integrity": "sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.39.0.tgz", + "integrity": "sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.39.0.tgz", + "integrity": "sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.39.0.tgz", + "integrity": "sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.39.0.tgz", + "integrity": "sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.39.0.tgz", + "integrity": "sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.39.0.tgz", + "integrity": "sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.39.0.tgz", + "integrity": "sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.39.0.tgz", + "integrity": "sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.39.0.tgz", + "integrity": "sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.39.0.tgz", + "integrity": "sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.39.0.tgz", + "integrity": "sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.39.0.tgz", + "integrity": "sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rrweb/types": { + "version": "2.0.0-alpha.18", + "resolved": "https://registry.npmjs.org/@rrweb/types/-/types-2.0.0-alpha.18.tgz", + "integrity": "sha512-iMH3amHthJZ9x3gGmBPmdfim7wLGygC2GciIkw2A6SO8giSn8PHYtRT8OKNH4V+k3SZ6RSnYHcTQxBA7pSWZ3Q==", + "license": "MIT" + }, + "node_modules/@rrweb/utils": { + "version": "2.0.0-alpha.18", + "resolved": "https://registry.npmjs.org/@rrweb/utils/-/utils-2.0.0-alpha.18.tgz", + "integrity": "sha512-qV8azQYo9RuwW4NGRtOiQfTBdHNL1B0Q//uRLMbCSjbaKqJYd88Js17Bdskj65a0Vgp2dwTLPIZ0gK47dfjfaA==", + "license": "MIT" }, "node_modules/@socket.io/component-emitter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" }, - "node_modules/@tinymce/tinymce-angular": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@tinymce/tinymce-angular/-/tinymce-angular-7.0.0.tgz", - "integrity": "sha512-IKNaG/ihlxE1XCfq6lzULbnsqZO9KNJtlpu5jo6JDJDL9zcFzj/N2A16Kk7rTj1yfmDoB1IXAk/BpMOvgDY8cg==", + "node_modules/@tanstack/react-table": { + "version": "8.21.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.2.tgz", + "integrity": "sha512-11tNlEDTdIhMJba2RBH+ecJ9l1zgS2kjmexDPAraulc8jeNA4xocSNeyzextT0XJyASil4XsCYlJmf5jEWAtYg==", + "license": "MIT", "dependencies": { - "tinymce": "^6.0.0 || ^5.5.0", - "tslib": "^2.3.0" - }, - "peerDependencies": { - "@angular/common": ">=14.0.0", - "@angular/core": ">=14.0.0", - "@angular/forms": ">=14.0.0" - } - }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@ts-morph/common": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.9.2.tgz", - "integrity": "sha512-IPyg+c3Am0EBoa63W0f/AKeLrJhvzMzQ4BIvD1baxLopmiHOj1HFTXYxC6e8iTZ+UYtN+/WFM9UyGRnoA20b8g==", - "dependencies": { - "fast-glob": "^3.2.5", - "minimatch": "^3.0.4", - "mkdirp": "^1.0.4", - "path-browserify": "^1.0.1" - } - }, - "node_modules/@ts-morph/common/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" + "@tanstack/table-core": "8.21.2" }, "engines": { - "node": ">=10" - } - }, - "node_modules/@tufjs/canonical-json": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz", - "integrity": "sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@tufjs/models": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-1.0.4.tgz", - "integrity": "sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A==", - "dev": true, - "dependencies": { - "@tufjs/canonical-json": "1.0.0", - "minimatch": "^9.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@tufjs/models/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dev": true, - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bonjour": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", - "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", - "integrity": "sha512-4x5FkPpLipqwthjPsF7ZRbOv3uoLUFkTA9G9v583qi4pACvq0uTELrB8OLUzPWUI4IJIyvM85vzkV1nyiI2Lig==", - "dev": true, - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "node_modules/@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" - }, - "node_modules/@types/cors": { - "version": "2.8.13", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", - "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/eslint": { - "version": "8.44.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", - "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", - "dev": true - }, - "node_modules/@types/express": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", - "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", - "dev": true, - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.35", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", - "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/file-saver": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.5.tgz", - "integrity": "sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ==", - "dev": true - }, - "node_modules/@types/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==", - "dev": true - }, - "node_modules/@types/http-proxy": { - "version": "1.17.11", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.11.tgz", - "integrity": "sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/jasmine": { - "version": "3.10.11", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.10.11.tgz", - "integrity": "sha512-tAiqDJrwRKyjpCgJE07OXFsXsXQWDhoJhyRwzl+yfEToy72s0LhHAfquMi2s4T4Iq3nanKOfZ8/PZFaL/0pQmA==", - "dev": true - }, - "node_modules/@types/jquery": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.16.tgz", - "integrity": "sha512-bsI7y4ZgeMkmpG9OM710RRzDFp+w4P1RGiIt30C1mSBT+ExCleeh4HObwgArnDFELmRrOpXgSYN9VF1hj+f1lw==", - "dev": true, - "dependencies": { - "@types/sizzle": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", - "dev": true - }, - "node_modules/@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", - "dev": true - }, - "node_modules/@types/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==" - }, - "node_modules/@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" - }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true - }, - "node_modules/@types/quill": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@types/quill/-/quill-2.0.10.tgz", - "integrity": "sha512-L6OHONEj2v4NRbWQOsn7j1N0SyzhRR3M4g1M6j/uuIwIsIW2ShWHhwbqNvH8hSmVktzqu0lITfdnqVOQ4qkrhA==", - "dev": true, - "dependencies": { - "parchment": "^1.1.2", - "quill-delta": "^4.0.1" - } - }, - "node_modules/@types/raf": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.0.tgz", - "integrity": "sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw==", - "optional": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true - }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "dev": true - }, - "node_modules/@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", - "dev": true - }, - "node_modules/@types/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", - "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", - "dev": true, - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", - "dev": true, - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", - "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", - "dev": true, - "dependencies": { - "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" - } - }, - "node_modules/@types/sizzle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", - "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", - "dev": true - }, - "node_modules/@types/sockjs": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", - "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/ws": { - "version": "8.5.5", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", - "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz", - "integrity": "sha512-sR0Gja9Ky1teIq4qJOl0nC+Tk64/uYdX+mi+5iB//MH8gwyx8e3SOyhEzeLZEFEEfCaLf8KJq+Bd/6je1t+CAg==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.48.2", - "@typescript-eslint/type-utils": "5.48.2", - "@typescript-eslint/utils": "5.48.2", - "debug": "^4.3.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.2.tgz", - "integrity": "sha512-QVWx7J5sPMRiOMJp5dYshPxABRoZV1xbRirqSk8yuIIsu0nvMTZesKErEA3Oix1k+uvsk8Cs8TGJ6kQ0ndAcew==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "5.48.2", - "@typescript-eslint/utils": "5.48.2", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.2.tgz", - "integrity": "sha512-2h18c0d7jgkw6tdKTlNaM7wyopbLRBiit8oAxoP89YnuBOzCZ8g8aBCaCqq7h208qUTroL7Whgzam7UY3HVLow==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.48.2", - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/typescript-estree": "5.48.2", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.2.tgz", - "integrity": "sha512-38zMsKsG2sIuM5Oi/olurGwYJXzmtdsHhn5mI/pQogP+BjYVkK5iRazCQ8RGS0V+YLk282uWElN70zAAUmaYHw==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.48.2", - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/typescript-estree": "5.48.2", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.2.tgz", - "integrity": "sha512-zEUFfonQid5KRDKoI3O+uP1GnrFd4tIHlvs+sTJXiWuypUWMuDaottkJuR612wQfOkjYbsaskSIURV9xo4f+Fw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/visitor-keys": "5.48.2" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.2.tgz", - "integrity": "sha512-hE7dA77xxu7ByBc6KCzikgfRyBCTst6dZQpwaTy25iMYOnbNljDT4hjhrGEJJ0QoMjrfqrx+j1l1B9/LtKeuqA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.2.tgz", - "integrity": "sha512-bibvD3z6ilnoVxUBFEgkO0k0aFvUc4Cttt0dAreEr+nrAHhWzkO83PEVVuieK3DqcgL6VAK5dkzK8XUVja5Zcg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/visitor-keys": "5.48.2", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.2.tgz", - "integrity": "sha512-z9njZLSkwmjFWUelGEwEbdf4NwKvfHxvGC0OcGN1Hp/XNDIcJ7D5DpPNPv6x6/mFvc1tQHsaWmpD/a4gOvvCJQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", - "dev": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", - "dev": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", - "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", - "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@wessberg/ts-evaluator": { - "version": "0.0.27", - "resolved": "https://registry.npmjs.org/@wessberg/ts-evaluator/-/ts-evaluator-0.0.27.tgz", - "integrity": "sha512-7gOpVm3yYojUp/Yn7F4ZybJRxyqfMNf0LXK5KJiawbPfL0XTsJV+0mgrEDjOIR6Bi0OYk2Cyg4tjFu1r8MCZaA==", - "deprecated": "this package has been renamed to ts-evaluator. Please install ts-evaluator instead", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "jsdom": "^16.4.0", - "object-path": "^0.11.5", - "tslib": "^2.0.3" - }, - "engines": { - "node": ">=10.1.0" + "node": ">=12" }, "funding": { "type": "github", - "url": "https://github.com/wessberg/ts-evaluator?sponsor=1" + "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "typescript": ">=3.2.x || >= 4.x" + "react": ">=16.8", + "react-dom": ">=16.8" } }, - "node_modules/@wessberg/ts-evaluator/node_modules/ansi-styles": { + "node_modules/@tanstack/react-virtual": { + "version": "3.13.6", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.6.tgz", + "integrity": "sha512-WT7nWs8ximoQ0CDx/ngoFP7HbQF9Q2wQe4nh2NB+u2486eX3nZRE40P9g6ccCVq7ZfTSH5gFOuCoVH5DLNS/aA==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.13.6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.21.2", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.2.tgz", + "integrity": "sha512-uvXk/U4cBiFMxt+p9/G7yUWI/UbHYbyghLCjlpWZ3mLeIZiUBSKcUnw9UnKkdRz7Z/N4UBuFLWQdJCjUe7HjvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.13.6", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.6.tgz", + "integrity": "sha512-cnQUeWnhNP8tJ4WsGcYiX24Gjkc9ALstLbHcBj1t3E7EimN6n6kHH+DPV4PpDnuw00NApQp+ViojMj1GRdwYQg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", + "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", + "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@tinymce/tinymce-react": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@tinymce/tinymce-react/-/tinymce-react-5.1.1.tgz", + "integrity": "sha512-DQ0wpvnf/9z8RsOEAmrWZ1DN1PKqcQHfU+DpM3llLze7FHmxVtzuN8O+FYh0oAAF4stzAXwiCIVacfqjMwRieQ==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.6.2", + "tinymce": "^7.0.0 || ^6.0.0 || ^5.5.1" + }, + "peerDependencies": { + "react": "^18.0.0 || ^17.0.1 || ^16.7.0", + "react-dom": "^18.0.0 || ^17.0.1 || ^16.7.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/chart.js": { + "version": "2.9.41", + "resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.41.tgz", + "integrity": "sha512-3dvkDvueckY83UyUXtJMalYoH6faOLkWQoaTlJgB4Djde3oORmNP0Jw85HtzTuXyliUHcdp704s0mZFQKio/KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "moment": "^2.10.2" + } + }, + "node_modules/@types/css-font-loading-module": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz", + "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==", + "license": "MIT" + }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jest": { + "version": "27.5.2", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz", + "integrity": "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-matcher-utils": "^27.0.0", + "pretty-format": "^27.0.0" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.16", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz", + "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mixpanel-browser": { + "version": "2.54.0", + "resolved": "https://registry.npmjs.org/@types/mixpanel-browser/-/mixpanel-browser-2.54.0.tgz", + "integrity": "sha512-7DMzIH0M9TlpCTMZidaeXris+aMUyAgMMEZtV1xeD6fSQgpCGklUKqyRgidq5hKPKuNEOWBp73549Gusig/xBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.17.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.30.tgz", + "integrity": "sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.0.tgz", + "integrity": "sha512-MY3oPudxvMYyesqs/kW1Bh8y9VqSmf+tzqw3ae8a9DZW68pUe3zAdHeI1jc6iAysuRdACnVknHP8AhwD4/dxtg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-1KfiQKsH1o00p9m5ag12axHQSb3FOU9H20UTrujVSkNhuCrRHiQWFqgEnTNK5ZNfnzZv8UWrnXVqCmCF9fgY3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@umijs/route-utils": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@umijs/route-utils/-/route-utils-4.0.1.tgz", + "integrity": "sha512-+1ixf1BTOLuH+ORb4x8vYMPeIt38n9q0fJDwhv9nSxrV46mxbLF0nmELIo9CKQB2gHfuC4+hww6xejJ6VYnBHQ==", + "license": "MIT" + }, + "node_modules/@umijs/use-params": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@umijs/use-params/-/use-params-1.0.9.tgz", + "integrity": "sha512-QlN0RJSBVQBwLRNxbxjQ5qzqYIGn+K7USppMoIOVlf7fxXHsnQZ2bEsa6Pm74bt6DVQxpUE8HqvdStn6Y9FV1w==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", + "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.1.tgz", + "integrity": "sha512-q/zjrW9lgynctNbwvFtQkGK9+vvHA5UzVi2V8APrp1C6fG6/MuYYkmlx4FubuqLycCeSdHD5aadWfua/Vr0EUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.1.1", + "@vitest/utils": "3.1.1", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.1.tgz", + "integrity": "sha512-bmpJJm7Y7i9BBELlLuuM1J1Q6EQ6K5Ye4wcyOpOMXMcePYKSIYlpcrCm4l/O6ja4VJA5G2aMJiuZkZdnxlC3SA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.1.1", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.1.tgz", + "integrity": "sha512-dg0CIzNx+hMMYfNmSqJlLSXEmnNhMswcn3sXO7Tpldr0LiGmg3eXdLLhwkv2ZqgHb/d5xg5F7ezNFRA1fA13yA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.1.tgz", + "integrity": "sha512-X/d46qzJuEDO8ueyjtKfxffiXraPRfmYasoC4i5+mlLEJ10UvPb0XH5M9C3gWuxd7BAQhpK42cJgJtq53YnWVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.1.1", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.1.tgz", + "integrity": "sha512-bByMwaVWe/+1WDf9exFxWWgAixelSdiwo2p33tpqIlM14vW7PRV5ppayVXtfycqze4Qhtwag5sVhX400MLBOOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.1.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.1.tgz", + "integrity": "sha512-+EmrUOOXbKzLkTDwlsc/xrwOlPDXyVk3Z6P6K4oiCndxz7YLpp/0R0UsWVOKT0IXWjjBJuSMk6D27qipaupcvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.1.tgz", + "integrity": "sha512-1XIjflyaU2k3HMArJ50bwSh3wKWPD6Q47wz/NUSmRV0zNywPc4w79ARjg/i/aNINHwA+mIALhUVqD9/aUvZNgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.1.1", + "loupe": "^3.1.3", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@xstate/fsm": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@xstate/fsm/-/fsm-1.6.5.tgz", + "integrity": "sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw==", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/add-dom-event-listener": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz", + "integrity": "sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==", + "license": "MIT", + "dependencies": { + "object-assign": "4.x" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -5057,411 +2664,61 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@wessberg/ts-evaluator/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@wessberg/ts-evaluator/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@wessberg/ts-evaluator/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@wessberg/ts-evaluator/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@wessberg/ts-evaluator/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/@yarnpkg/lockfile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "dev": true - }, - "node_modules/@yarnpkg/parsers": { - "version": "3.0.0-rc.46", - "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.46.tgz", - "integrity": "sha512-aiATs7pSutzda/rq8fnuPwTglyVwjM22bNnK2ZgjrpAjQHSSl3lztd2f9evst1W/qnC58DRz7T7QndUDumAR4Q==", - "dev": true, - "dependencies": { - "js-yaml": "^3.10.0", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=14.15.0" - } - }, - "node_modules/@zkochan/js-yaml": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz", - "integrity": "sha512-nzvgl3VfhcELQ8LyVrYOru+UtAy1nrygk2+AGbTm8a5YcO6o8lSjAT+pfg3vJWxIoZKOUhrK6UU7xW/+00kQrg==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@zkochan/js-yaml/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "dev": true - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", - "dev": true, - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/adjust-sourcemap-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", - "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", - "dev": true, - "dependencies": { - "loader-utils": "^2.0.0", - "regex-parser": "^2.2.11" - }, - "engines": { - "node": ">=8.9" - } - }, - "node_modules/adjust-sourcemap-loader/node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agentkeepalive": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", - "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", - "dev": true, - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "dev": true, - "engines": [ - "node >= 0.8.0" - ], - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/antd": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/antd/-/antd-5.8.2.tgz", - "integrity": "sha512-kkC2BSBde1JzJxk2wNYj/NXgNZQZ2yu6avJoCKleSi32nsjiadi7FFu1AyGxIzoJ9CrxoLacjGvrwbKJQ6kCvw==", + "version": "5.24.6", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.24.6.tgz", + "integrity": "sha512-xIlTa/1CTbgkZsdU/dOXkYvJXb9VoiMwsaCzpKFH2zAEY3xqOfwQ57/DdG7lAdrWP7QORtSld4UA6suxzuTHXw==", + "license": "MIT", "dependencies": { - "@ant-design/colors": "^7.0.0", - "@ant-design/cssinjs": "^1.16.0", - "@ant-design/icons": "^5.2.2", - "@ant-design/react-slick": "~1.0.0", - "@babel/runtime": "^7.18.3", - "@ctrl/tinycolor": "^3.6.0", - "@rc-component/color-picker": "~1.4.0", - "@rc-component/mutate-observer": "^1.0.0", - "@rc-component/tour": "~1.8.1", - "@rc-component/trigger": "^1.15.0", - "classnames": "^2.2.6", - "copy-to-clipboard": "^3.2.0", - "dayjs": "^1.11.1", - "qrcode.react": "^3.1.0", - "rc-cascader": "~3.14.0", - "rc-checkbox": "~3.1.0", - "rc-collapse": "~3.7.0", - "rc-dialog": "~9.1.0", - "rc-drawer": "~6.2.0", - "rc-dropdown": "~4.1.0", - "rc-field-form": "~1.36.0", - "rc-image": "~7.1.0", - "rc-input": "~1.1.0", - "rc-input-number": "~8.0.2", - "rc-mentions": "~2.5.0", - "rc-menu": "~9.10.0", - "rc-motion": "^2.7.3", - "rc-notification": "~5.0.4", - "rc-pagination": "~3.5.0", - "rc-picker": "~3.12.0", - "rc-progress": "~3.4.1", - "rc-rate": "~2.12.0", - "rc-resize-observer": "^1.2.0", - "rc-segmented": "~2.2.0", - "rc-select": "~14.7.1", - "rc-slider": "~10.1.0", + "@ant-design/colors": "^7.2.0", + "@ant-design/cssinjs": "^1.23.0", + "@ant-design/cssinjs-utils": "^1.1.3", + "@ant-design/fast-color": "^2.0.6", + "@ant-design/icons": "^5.6.1", + "@ant-design/react-slick": "~1.1.2", + "@babel/runtime": "^7.26.0", + "@rc-component/color-picker": "~2.0.1", + "@rc-component/mutate-observer": "^1.1.0", + "@rc-component/qrcode": "~1.0.0", + "@rc-component/tour": "~1.15.1", + "@rc-component/trigger": "^2.2.6", + "classnames": "^2.5.1", + "copy-to-clipboard": "^3.3.3", + "dayjs": "^1.11.11", + "rc-cascader": "~3.33.1", + "rc-checkbox": "~3.5.0", + "rc-collapse": "~3.9.0", + "rc-dialog": "~9.6.0", + "rc-drawer": "~7.2.0", + "rc-dropdown": "~4.2.1", + "rc-field-form": "~2.7.0", + "rc-image": "~7.11.1", + "rc-input": "~1.7.3", + "rc-input-number": "~9.4.0", + "rc-mentions": "~2.19.1", + "rc-menu": "~9.16.1", + "rc-motion": "^2.9.5", + "rc-notification": "~5.6.3", + "rc-pagination": "~5.1.0", + "rc-picker": "~4.11.3", + "rc-progress": "~4.0.0", + "rc-rate": "~2.13.1", + "rc-resize-observer": "^1.4.3", + "rc-segmented": "~2.7.0", + "rc-select": "~14.16.6", + "rc-slider": "~11.1.8", "rc-steps": "~6.0.1", "rc-switch": "~4.1.0", - "rc-table": "~7.32.1", - "rc-tabs": "~12.9.0", - "rc-textarea": "~1.3.3", - "rc-tooltip": "~6.0.0", - "rc-tree": "~5.7.6", - "rc-tree-select": "~5.11.0", - "rc-upload": "~4.3.0", - "rc-util": "^5.32.0", - "scroll-into-view-if-needed": "^3.0.3", - "throttle-debounce": "^5.0.0" + "rc-table": "~7.50.4", + "rc-tabs": "~15.5.1", + "rc-textarea": "~1.9.0", + "rc-tooltip": "~6.4.0", + "rc-tree": "~5.13.1", + "rc-tree-select": "~5.27.0", + "rc-upload": "~4.8.1", + "rc-util": "^5.44.4", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" }, "funding": { "type": "opencollective", @@ -5472,11 +2729,19 @@ "react-dom": ">=16.9.0" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "devOptional": true, + "dev": true, + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -5485,111 +2750,49 @@ "node": ">= 8" } }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true - }, - "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "dev": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } + "license": "MIT" }, "node_modules/aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, + "license": "Apache-2.0", "dependencies": { "dequal": "^2.0.3" } }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-differ": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", - "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", - "dev": true - }, - "node_modules/array-tree-filter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz", - "integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==" - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/arrify": { + "node_modules/assertion-error": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", - "dev": true - }, "node_modules/async-validator": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", - "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==" + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-1.11.5.tgz", + "integrity": "sha512-XNtCsMAeAH1pdLMEg1z8/Bb3a8cdCbui9QbJATRFHHHW5kT6+NPI3zSVQUXgikTFITzsg+kYY5NTWhM2Orwt9w==" }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" }, "node_modules/atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "license": "(MIT OR Apache-2.0)", "bin": { "atob": "bin/atob.js" }, @@ -5598,9 +2801,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.14", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", - "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", "dev": true, "funding": [ { @@ -5610,14 +2813,19 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "browserslist": "^4.21.5", - "caniuse-lite": "^1.0.30001464", - "fraction.js": "^4.2.0", + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -5630,297 +2838,112 @@ "postcss": "^8.1.0" } }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/axios": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", - "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, - "node_modules/axobject-query": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", - "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", - "dev": true, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", "dependencies": { - "deep-equal": "^2.0.5" - } - }, - "node_modules/babel-loader": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", - "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", - "dev": true, - "dependencies": { - "find-cache-dir": "^4.0.0", - "schema-utils": "^4.0.0" + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" }, "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0", - "webpack": ">=5" + "node": ">=10", + "npm": ">=6" } }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz", - "integrity": "sha512-19hwUH5FKl49JEsvyTcoHakh6BE0wgXLLptIyKZ3PijHc/Ci521wygORCUCCred+E/twuqRyAkE02BAWPmsHOg==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.2", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } + "node_modules/babel-runtime/node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true, + "license": "MIT" }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.3.tgz", - "integrity": "sha512-z41XaniZL26WLrvjy7soabMXrfPWARN25PZoriDEiLMxAp50AUW3t35BGQUMg5xK3UrpVTtagIDklxYa+MhiNA==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.2", - "core-js-compat": "^3.31.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.2.tgz", - "integrity": "sha512-tAlOptU0Xj34V1Y2PNTL4Y0FOJMDB6bZmoW39FeCQIhigGLkqu3Fj6uiXpxIf6Ij274ENdYx64y6Au+ZKlb1IA==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "license": "MIT" }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" }, "node_modules/base64-arraybuffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", "engines": { "node": ">= 0.6.0" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "engines": { - "node": "^4.5.0 || >= 5.9" - } - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "dev": true - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "devOptional": true, + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", "engines": { "node": ">=8" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/bonjour-service": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", - "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", - "dev": true, - "dependencies": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, - "node_modules/bootstrap": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.1.tgz", - "integrity": "sha512-jzwza3Yagduci2x0rr9MeFSORjcHpt0lRZukZPZQJT1Dth5qzV7XcgGqYzi39KGAVYR8QEDVoO0ubFKOxzMG+g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/twbs" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/bootstrap" - } - ], - "peerDependencies": { - "@popperjs/core": "^2.11.8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, "node_modules/browserslist": { - "version": "4.21.10", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", - "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "dev": true, "funding": [ { @@ -5936,11 +2959,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001517", - "electron-to-chromium": "^1.4.477", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.11" + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -5953,6 +2977,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "license": "(MIT OR Apache-2.0)", "bin": { "btoa": "bin/btoa.js" }, @@ -5960,120 +2985,59 @@ "node": ">= 0.4.0" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/builtins": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", - "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", "dev": true, - "dependencies": { - "semver": "^7.0.0" - } + "license": "MIT" }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/cacache": { - "version": "17.1.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.3.tgz", - "integrity": "sha512-jAdjGxmPxZh0IipMdR7fK/4sDSrHMLUV0+GvVUsjwyGNKHsh79kW/otg+GkbXwl6Uzvy9wsvHOX4nUoWldeZMg==", - "dev": true, - "dependencies": { - "@npmcli/fs": "^3.1.0", - "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/call-bind": { + "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.4" } }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 6" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001519", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001519.tgz", - "integrity": "sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg==", + "version": "1.0.30001709", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001709.tgz", + "integrity": "sha512-NgL3vUTnDrPCZ3zTahp4fsugQ4dc7EKTSzwQDPEel6DMoMnfH2jhry9n2Zm8onbSR+f/QtKHFOA+iAQu4kbtWA==", "dev": true, "funding": [ { @@ -6088,12 +3052,14 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/canvg": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", - "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", + "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", + "license": "MIT", "optional": true, "dependencies": { "@babel/runtime": "^7.12.5", @@ -6109,65 +3075,84 @@ "node": ">=10.0.0" } }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/canvg/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT", + "optional": true + }, + "node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=12" } }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } }, "node_modules/chart.js": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.3.3.tgz", - "integrity": "sha512-aTk7pBw+x6sQYhon/NR3ikfUJuym/LdgpTlgZRe2PaEhjUMKBKyNaFCMVRAyTEWYFNO7qRu7iQVqOw/OqzxZxQ==", + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.8.tgz", + "integrity": "sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==", + "license": "MIT", "dependencies": { "@kurkle/color": "^0.3.0" }, "engines": { - "pnpm": ">=7" + "pnpm": ">=8" } }, "node_modules/chartjs-plugin-datalabels": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.2.0.tgz", "integrity": "sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==", + "license": "MIT", "peerDependencies": { "chart.js": ">=3.0.0" } }, - "node_modules/chartjs-to-image": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/chartjs-to-image/-/chartjs-to-image-1.2.2.tgz", - "integrity": "sha512-qnYedDlNSPsrISQyRhJk4gWciKMtK8mlx2VWbFMJIPLVokSHJBEUuoxE6LLDFGnOhdvLd3K5E6lmGap7/phWFQ==", - "dependencies": { - "axios": "^1.6.0", - "javascript-stringify": "^2.1.0" + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "devOptional": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -6180,145 +3165,57 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, "engines": { - "node": ">=10" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "engines": { - "node": ">=6.0" + "node": ">= 6" } }, "node_modules/classnames": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", - "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", - "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/code-block-writer": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-10.1.1.tgz", - "integrity": "sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw==" + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" }, "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "1.1.3" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true + "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -6327,427 +3224,98 @@ } }, "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "dev": true - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "dev": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 6" } }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dev": true, - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, "node_modules/compute-scroll-into-view": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.0.3.tgz", - "integrity": "sha512-nadqwNxghAGTamwIqQSG433W6OADZx2vCo3UXHNrzTRHK/htu+7+L0zhjEoaeaQVNAi3YgqWDv8+tzf0hRfR+A==" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/connect-history-api-fallback": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", - "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true - }, - "node_modules/copy-anything": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", - "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", - "dev": true, - "dependencies": { - "is-what": "^3.14.1" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } + "license": "MIT" }, "node_modules/copy-to-clipboard": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", "dependencies": { "toggle-selection": "^1.0.6" } }, - "node_modules/copy-webpack-plugin": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", - "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", - "dev": true, - "dependencies": { - "fast-glob": "^3.2.11", - "glob-parent": "^6.0.1", - "globby": "^13.1.1", - "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/globby": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", - "dev": true, - "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/copy-webpack-plugin/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/core-js": { - "version": "3.32.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.32.0.tgz", - "integrity": "sha512-rd4rYZNlF3WuoYuRIDEmbR/ga9CeuWX9U05umAvgrrZoHY4Z++cp/xwPQMvUpBB4Ag6J8KfD80G0zwCyaSxDww==", + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.41.0.tgz", + "integrity": "sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==", "hasInstallScript": true, + "license": "MIT", + "optional": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" } }, - "node_modules/core-js-compat": { - "version": "3.32.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.0.tgz", - "integrity": "sha512-7a9a3D1k4UCVKnLhrgALyFcP7YCsLOQIxPd0dKjf/6GuPcgyiGP70ewWdCGrSK7evyhymi0qO4EqCmSJofDeYw==", - "dev": true, - "dependencies": { - "browserslist": "^4.21.9" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/cosmiconfig": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", - "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", - "dev": true, + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", "dependencies": { + "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", "parse-json": "^5.0.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - } - }, - "node_modules/cosmiconfig/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/cosmiconfig/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/critters": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.20.tgz", - "integrity": "sha512-CImNRorKOl5d8TWcnAz5n5izQ6HFsvz29k327/ELy6UFcmbiZNOsinaKvzv16WZR0P6etfSWYzE47C4/56B3Uw==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "css-select": "^5.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.2", - "htmlparser2": "^8.0.2", - "postcss": "^8.4.23", - "pretty-bytes": "^5.3.0" - } - }, - "node_modules/critters/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/critters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "path-type": "^4.0.0", + "yaml": "^1.10.0" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/critters/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", "engines": { - "node": ">=7.0.0" + "node": ">= 6" } }, - "node_modules/critters/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "node_modules/create-react-class": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz", + "integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } }, - "node_modules/critters/node_modules/has-flag": { + "node_modules/cross-fetch": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/critters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "node-fetch": "^2.6.12" } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -6761,69 +3329,30 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", "dependencies": { "utrie": "^1.0.2" } }, - "node_modules/css-loader": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", - "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", - "dev": true, - "dependencies": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.21", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.3", - "postcss-modules-scope": "^3.0.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.3.8" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } + "node_modules/css-mediaquery": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz", + "integrity": "sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==", + "license": "BSD" }, - "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", "dev": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } + "license": "MIT" }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, + "license": "MIT", "bin": { "cssesc": "bin/cssesc" }, @@ -6831,90 +3360,35 @@ "node": ">=4" } }, - "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" - }, - "node_modules/custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", - "dev": true - }, - "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "engines": { - "node": ">=10" - } + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" }, "node_modules/date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, - "node_modules/date-format": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", - "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", - "dev": true, - "engines": { - "node": ">=4.0" + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" } }, "node_modules/dayjs": { - "version": "1.11.9", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", - "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -6925,797 +3399,294 @@ } } }, - "node_modules/decimal.js": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", - "dev": true - }, - "node_modules/deep-equal": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz", - "integrity": "sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==", + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.1", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.0", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "dev": true, - "dependencies": { - "execa": "^5.0.0" - }, + "license": "MIT", "engines": { - "node": ">= 10" - } - }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "dev": true, - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", "engines": { "node": ">=0.4.0" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", "dev": true, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true - }, - "node_modules/di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", - "dev": true - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, + "license": "Apache-2.0", + "optional": true, + "peer": true, "engines": { "node": ">=8" } }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", - "dev": true - }, - "node_modules/dns-packet": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.0.tgz", - "integrity": "sha512-rza3UH1LwdHh9qyPXp8lkwpjSNk/AMD3dPytUoRoqnypDUhY0xvbdmVhWOfxO68frEfV9BU8V12Ez7ZsHGZpCQ==", + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "dev": true, - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" - }, + "license": "Apache-2.0" + }, + "node_modules/diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "dev": true, + "license": "MIT" + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" } }, - "node_modules/docviewhelper": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/docviewhelper/-/docviewhelper-0.0.2.tgz", - "integrity": "sha512-4ETJdDT//5Q8qcEGY9UjYo6dZiFzn3CHXAoSli4w35uehVdkKschIoRjG3exesc5jyVc9Zrch3oZKZR/5i4ZQQ==" - }, - "node_modules/dom-align": { - "version": "1.12.4", - "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.4.tgz", - "integrity": "sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==" - }, - "node_modules/dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", - "dev": true, - "dependencies": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } + "node_modules/dom-scroll-into-view": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/dom-scroll-into-view/-/dom-scroll-into-view-1.2.1.tgz", + "integrity": "sha512-LwNVg3GJOprWDO+QhLL1Z9MMgWe/KAFLxVWKzjRTxNSPn8/LLDIfmuG71YHznXCqaqTjvHJDYO1MEAgX6XCNbQ==", + "license": "MIT" }, "node_modules/dompurify": { - "version": "2.4.7", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.7.tgz", - "integrity": "sha512-kxxKlPEDa6Nc5WJi+qRgPbOAbgTpSULL+vI3NUXsZMlkJxTqYI9wg5ZTay2sFrdZRWHPWNi+EdAhcJf81WtoMQ==", - "optional": true + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz", + "integrity": "sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } }, - "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", - "dev": true, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", - "dev": true, "engines": { - "node": ">=10" + "node": ">= 0.4" } }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true - }, - "node_modules/ejs": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", - "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", "dev": true, - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } + "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.4.485", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.485.tgz", - "integrity": "sha512-1ndQ5IBNEnFirPwvyud69GHL+31FkE09gH/CJ6m3KCbkx3i0EVOrjwz4UNxRmN9H8OVHbC6vMRZGN1yCvjSs9w==", - "dev": true + "version": "1.5.130", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.130.tgz", + "integrity": "sha512-Ou2u7L9j2XLZbhqzyX0jWDj6gA8D3jIfVzt4rikLf3cGBa0VdReuFimBKS9tQJA4+XpeCxj1NoWlfBXzbMa9IA==", + "dev": true, + "license": "ISC" }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/engine.io": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.2.tgz", - "integrity": "sha512-IXsMcGpw/xRfjra46sVZVHiSWo/nJ/3g1337q9KNXtS6YRzbW5yIzTCb9DjhrBe7r3GZQR0I4+nq+4ODk5g/cA==", - "dependencies": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.11.0" - }, - "engines": { - "node": ">=10.2.0" - } + "license": "MIT" }, "node_modules/engine.io-client": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.2.tgz", - "integrity": "sha512-CQZqbrpEYnrpGqC07a9dJDz4gePZUgTPMU3NKJPSeQOyw27Tst4Pl3FemKoFGAlHzgZmKjoRmiJvbWfhCXUlIg==", + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", - "ws": "~8.11.0", - "xmlhttprequest-ssl": "~2.0.0" + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/engine.io-parser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", - "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", "engines": { "node": ">=10.0.0" } }, - "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", - "dev": true - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "devOptional": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true - }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, - "optional": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, + "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } }, - "node_modules/es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" } }, "node_modules/es-module-lexer": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", - "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==", - "dev": true - }, - "node_modules/esbuild-wasm": { - "version": "0.18.17", - "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.18.17.tgz", - "integrity": "sha512-9OHGcuRzy+I8ziF9FzjfKLWAPbvi0e/metACVg9k6bK+SI4FFxeV6PcZsz8RIVaMD4YNehw+qj6UMR3+qj/EuQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true - }, "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint": { - "version": "8.46.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.46.0.tgz", - "integrity": "sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.1", - "@eslint/js": "^8.46.0", - "@humanwhocodes/config-array": "^0.11.10", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.2", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz", - "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -7723,576 +3694,78 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, + "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", + "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12.0.0" } }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter-asyncresource": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz", - "integrity": "sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==", - "dev": true - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/exponential-backoff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", - "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", - "dev": true - }, - "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "dev": true, - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true - }, - "node_modules/express/node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/express/node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/express/node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/external-editor/node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", "dependencies": { "reusify": "^1.0.4" } }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/fflate": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", - "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -8300,118 +3773,23 @@ "node": ">=8" } }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/finalhandler/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-cache-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", - "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", - "dev": true, - "dependencies": { - "common-path-prefix": "^3.0.0", - "pkg-dir": "^7.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" }, "node_modules/follow-redirects": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", - "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -8421,22 +3799,14 @@ } } }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, + "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -8447,99 +3817,41 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" }, "engines": { "node": ">= 6" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/fraction.js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", - "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "dev": true, + "license": "MIT", "engines": { "node": "*" }, "funding": { "type": "patreon", - "url": "https://www.patreon.com/infusion" + "url": "https://github.com/sponsors/rawify" } }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, - "node_modules/fs-extra": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", - "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/fs-minipass": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.2.tgz", - "integrity": "sha512-2GAfyfoaCDRrM6jaOS3UsBts8yJ55VioXdWcOL7dK9zdAuKT71+WBA4ifnNYqVjYv+4SsPxjK0JT4yIIn4cA/g==", - "dev": true, - "dependencies": { - "minipass": "^5.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/fs-monkey": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz", - "integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==", - "dev": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -8549,280 +3861,53 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "dev": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, + "node_modules/gantt-task-react": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/gantt-task-react/-/gantt-task-react-0.3.9.tgz", + "integrity": "sha512-ged2OGrAJJ+ATrfVVkd4/8cUu4jOu1mXi0NaBeKx4uoGP6YhyryL+Snr2OY48AXdFelUAg7BbqylSej9X6PCCQ==", + "license": "MIT", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=10" + }, + "peerDependencies": { + "react": "^18.0.0" } }, - "node_modules/gauge/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dev": true, + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "10.3.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.3.tgz", - "integrity": "sha512-92vPiMb/iqpmEgsOoIDvTjc50wf9CCCvMzsi6W0JLPeUKE8TWP1a73PgqSrqy7iAZxaSD1YdzU7QZR5LF51MJw==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.0.3", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/guess-parser": { - "version": "0.4.22", - "resolved": "https://registry.npmjs.org/guess-parser/-/guess-parser-0.4.22.tgz", - "integrity": "sha512-KcUWZ5ACGaBM69SbqwVIuWGoSAgD+9iJnchR9j/IarVI1jHVeXv+bUXBIMeqVMSKt3zrn0Dgf9UpcOEpPBLbSg==", - "dev": true, - "dependencies": { - "@wessberg/ts-evaluator": "0.0.27" - }, - "peerDependencies": { - "typescript": ">=3.7.5" - } - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -8830,11 +3915,96 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -8843,12 +4013,12 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -8857,136 +4027,47 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true - }, - "node_modules/hdr-histogram-js": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", - "integrity": "sha512-Hkn78wwzWHNCp2uarhzQ2SGFLU3JY8SBDDd3TAABK4fc30wm+MuPOrg5QVFVfkKOQd6Bfz3ukJEI+q9sXEkK1g==", - "dev": true, - "dependencies": { - "@assemblyscript/loader": "^0.10.1", - "base64-js": "^1.2.0", - "pako": "^1.0.3" - } - }, - "node_modules/hdr-histogram-percentiles-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz", - "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", - "dev": true - }, - "node_modules/hosted-git-info": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", - "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", - "dev": true, - "dependencies": { - "lru-cache": "^7.5.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hpack.js/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/hpack.js/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "dependencies": { - "whatwg-encoding": "^1.0.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/html-entities": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", - "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ] - }, - "node_modules/html-escaper": { + "node_modules/hasown": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } }, "node_modules/html2canvas": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", "dependencies": { "css-line-break": "^2.1.0", "text-segmentation": "^1.0.3" @@ -8995,263 +4076,68 @@ "node": ">=8.0.0" } }, - "node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "dev": true, + "node_modules/hyphenate-style-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", + "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", + "license": "BSD-3-Clause" + }, + "node_modules/i18next": { + "version": "23.16.8", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz", + "integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==", "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", { - "type": "github", - "url": "https://github.com/sponsors/fb55" + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" } ], + "license": "MIT", "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" + "@babel/runtime": "^7.23.2" } }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", - "dev": true - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, + "node_modules/i18next-browser-languagedetector": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.4.tgz", + "integrity": "sha512-f3frU3pIxD50/Tz20zx9TD9HobKYg47fmAETb117GKGPrhwcSSPJDoCposXlVycVebQ9GQohC3Efbpq7/nnJ5w==", + "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" + "@babel/runtime": "^7.23.2" } }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", - "dev": true - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, + "node_modules/i18next-http-backend": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.7.3.tgz", + "integrity": "sha512-FgZxrXdRA5u44xfYsJlEBL4/KH3f2IluBpgV/7riW0YW2VEyM8FzVt2XHAOi6id0Ppj7vZvCZVpp5LrGXnc8Ig==", + "license": "MIT", "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" + "cross-fetch": "4.0.0" } }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", - "dev": true, - "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/ignore-walk": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.3.tgz", - "integrity": "sha512-C7FfFoTA+bI10qfeydT8aZbvr91vAEU+2W5BZUlzPec47oNb07SsOfwYrtxuvOYdUApPP/Qlh4DtAO51Ekk2QA==", - "dev": true, - "dependencies": { - "minimatch": "^9.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/ignore-walk/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/ignore-walk/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "license": "MIT", "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/immer" } }, - "node_modules/image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", - "dev": true, - "optional": true, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/immutable": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.2.tgz", - "integrity": "sha512-oGXzbEDem9OOpDWZu88jGiYCvIsLHMvGw+8OXlpsvTFvIQplQbjg1B1cvKg8f7Hoch6+NGjpPsH1Fr+Mc2D1aA==", - "dev": true - }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -9263,244 +4149,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, "node_modules/indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/inquirer": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", - "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/inquirer/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/inquirer/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/inquirer/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/inquirer/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/inquirer/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", - "dev": true - }, - "node_modules/ipaddr.js": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", - "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "MIT" }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "devOptional": true, + "dev": true, + "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -9508,53 +4178,13 @@ "node": ">=8" } }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", - "dev": true, + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -9563,25 +4193,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -9591,6 +4208,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -9599,6 +4217,8 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -9606,400 +4226,32 @@ "node": ">=0.10.0" } }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true - }, - "node_modules/is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", - "dev": true, - "dependencies": { - "which-typed-array": "^1.1.11" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-what": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", - "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", - "dev": true - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "node_modules/isbinaryfile": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", - "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", - "dev": true, - "engines": { - "node": ">= 8.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/gjtorikian/" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "ISC" }, "node_modules/jackspeak": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.2.tgz", - "integrity": "sha512-mgNtVv4vUuaKA97yxUHoA3+FkuhtxkjdXEWOyB/N76fjy0FjezEt34oy3epBtvCvS+7DyKwqCFWx/oJLV5+kCg==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": ">=14" - }, "funding": { "url": "https://github.com/sponsors/isaacs" }, @@ -10007,318 +4259,87 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jake": { - "version": "10.8.7", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", - "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", - "dev": true, - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jake/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jake/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jake/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jake/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jake/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jake/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jasmine-core": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.10.1.tgz", - "integrity": "sha512-ooZWSDVAdh79Rrj4/nnfklL3NQVra0BcuhcuWoAwwi+znLDoUeH87AFfeX8s+YeYi6xlv5nveRyaA1v7CintfA==", - "dev": true - }, - "node_modules/javascript-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz", - "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==" - }, - "node_modules/jest-worker": { + "node_modules/jest-diff": { "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" }, "engines": { - "node": ">= 10.13.0" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/jest-matcher-utils": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "chalk": "^4.0.0", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jiti": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz", - "integrity": "sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==", + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, + "license": "MIT", "bin": { "jiti": "bin/jiti.js" } }, - "node_modules/jquery": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.0.tgz", - "integrity": "sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ==" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dev": true, - "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/jsdom/node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/jsdom/node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/jsdom/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, - "node_modules/jsdom/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "dev": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "license": "MIT" }, "node_modules/json2mq": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", "dependencies": { "string-convert": "^0.2.0" } @@ -10328,6 +4349,7 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, + "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -10335,570 +4357,323 @@ "node": ">=6" } }, - "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", - "dev": true, - "engines": [ - "node >= 0.2.0" - ] - }, "node_modules/jspdf": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", - "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.1.tgz", + "integrity": "sha512-qaGIxqxetdoNnFQQXxTKUD9/Z7AloLaw94fFsOiJMxbfYdBbrBuhWmbzI8TVjrw7s3jBY1PFHofBKMV/wZPapg==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.14.0", + "@babel/runtime": "^7.26.7", "atob": "^2.1.2", "btoa": "^1.2.1", - "fflate": "^0.4.8" + "fflate": "^0.8.1" }, "optionalDependencies": { - "canvg": "^3.0.6", + "canvg": "^3.0.11", "core-js": "^3.6.0", - "dompurify": "^2.2.0", + "dompurify": "^3.2.4", "html2canvas": "^1.0.0-rc.5" } }, - "node_modules/karma": { - "version": "6.3.20", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.20.tgz", - "integrity": "sha512-HRNQhMuKOwKpjYlWiJP0DUrJOh+QjaI/DTaD8b9rEm4Il3tJ8MijutVZH4ts10LuUFst/CedwTS6vieCN8yTSw==", + "node_modules/lightningcss": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.3.tgz", + "integrity": "sha512-GlOJwTIP6TMIlrTFsxTerwC0W6OpQpCGuX1ECRLBUVRh6fpJH3xTqjCjRgQHTb4ZXexH9rtHou1Lf03GKzmhhQ==", "dev": true, + "license": "MPL-2.0", + "optional": true, + "peer": true, "dependencies": { - "@colors/colors": "1.5.0", - "body-parser": "^1.19.0", - "braces": "^3.0.2", - "chokidar": "^3.5.1", - "connect": "^3.7.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.1", - "glob": "^7.1.7", - "graceful-fs": "^4.2.6", - "http-proxy": "^1.18.1", - "isbinaryfile": "^4.0.8", - "lodash": "^4.17.21", - "log4js": "^6.4.1", - "mime": "^2.5.2", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.5", - "qjobs": "^1.2.0", - "range-parser": "^1.2.1", - "rimraf": "^3.0.2", - "socket.io": "^4.4.1", - "source-map": "^0.6.1", - "tmp": "^0.2.1", - "ua-parser-js": "^0.7.30", - "yargs": "^16.1.1" - }, - "bin": { - "karma": "bin/karma" + "detect-libc": "^2.0.3" }, "engines": { - "node": ">= 10" - } - }, - "node_modules/karma-chrome-launcher": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.1.tgz", - "integrity": "sha512-hsIglcq1vtboGPAN+DGCISCFOxW+ZVnIqhDQcCMqqCp+4dmJ0Qpq5QAjkbA0X2L9Mi6OBkHi2Srrbmm7pUKkzQ==", - "dev": true, - "dependencies": { - "which": "^1.2.1" - } - }, - "node_modules/karma-chrome-launcher/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/karma-coverage": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.1.1.tgz", - "integrity": "sha512-oxeOSBVK/jdZsiX03LhHQkO4eISSQb5GbHi6Nsw3Mw7G4u6yUgacBAftnO7q+emPBLMsrNbz1pGIrj+Jb3z17A==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.1", - "istanbul-reports": "^3.0.5", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/karma-coverage/node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/karma-coverage/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/karma-jasmine": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-4.0.2.tgz", - "integrity": "sha512-ggi84RMNQffSDmWSyyt4zxzh2CQGwsxvYYsprgyR1j8ikzIduEdOlcLvXjZGwXG/0j41KUXOWsUCBfbEHPWP9g==", - "dev": true, - "dependencies": { - "jasmine-core": "^3.6.0" - }, - "engines": { - "node": ">= 10" - }, - "peerDependencies": { - "karma": "*" - } - }, - "node_modules/karma-jasmine-html-reporter": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.7.0.tgz", - "integrity": "sha512-pzum1TL7j90DTE86eFt48/s12hqwQuiD+e5aXx2Dc9wDEn2LfGq6RoAxEZZjFiN0RDSCOnosEKRZWxbQ+iMpQQ==", - "dev": true, - "peerDependencies": { - "jasmine-core": ">=3.8", - "karma": ">=0.9", - "karma-jasmine": ">=1.1" - } - }, - "node_modules/karma-source-map-support": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", - "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", - "dev": true, - "dependencies": { - "source-map-support": "^0.5.5" - } - }, - "node_modules/karma/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/karma/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/karma/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/karma/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/klona": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", - "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/launch-editor": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.0.tgz", - "integrity": "sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==", - "dev": true, - "dependencies": { - "picocolors": "^1.0.0", - "shell-quote": "^1.7.3" - } - }, - "node_modules/less": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", - "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", - "dev": true, - "dependencies": { - "copy-anything": "^2.0.1", - "parse-node-version": "^1.0.1", - "tslib": "^2.3.0" - }, - "bin": { - "lessc": "bin/lessc" - }, - "engines": { - "node": ">=6" - }, - "optionalDependencies": { - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "make-dir": "^2.1.0", - "mime": "^1.4.1", - "needle": "^3.1.0", - "source-map": "~0.6.0" - } - }, - "node_modules/less-loader": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.1.0.tgz", - "integrity": "sha512-C+uDBV7kS7W5fJlUjq5mPBeBVhYpTIm5gB09APT9o3n/ILeaXVsiSFTbZpTJCJwQ/Crczfn3DmfQFwxYusWFug==", - "dev": true, - "dependencies": { - "klona": "^2.0.4" - }, - "engines": { - "node": ">= 14.15.0" + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/webpack" + "url": "https://opencollective.com/parcel" }, - "peerDependencies": { - "less": "^3.5.0 || ^4.0.0", - "webpack": "^5.0.0" + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.29.3", + "lightningcss-darwin-x64": "1.29.3", + "lightningcss-freebsd-x64": "1.29.3", + "lightningcss-linux-arm-gnueabihf": "1.29.3", + "lightningcss-linux-arm64-gnu": "1.29.3", + "lightningcss-linux-arm64-musl": "1.29.3", + "lightningcss-linux-x64-gnu": "1.29.3", + "lightningcss-linux-x64-musl": "1.29.3", + "lightningcss-win32-arm64-msvc": "1.29.3", + "lightningcss-win32-x64-msvc": "1.29.3" } }, - "node_modules/less/node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "node_modules/lightningcss-darwin-arm64": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.3.tgz", + "integrity": "sha512-fb7raKO3pXtlNbQbiMeEu8RbBVHnpyqAoxTyTRMEWFQWmscGC2wZxoHzZ+YKAepUuKT9uIW5vL2QbFivTgprZg==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MPL-2.0", "optional": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, + "os": [ + "darwin" + ], + "peer": true, "engines": { - "node": ">=6" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/less/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "node_modules/lightningcss-darwin-x64": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.3.tgz", + "integrity": "sha512-KF2XZ4ZdmDGGtEYmx5wpzn6u8vg7AdBHaEOvDKu8GOs7xDL/vcU2vMKtTeNe1d4dogkDdi3B9zC77jkatWBwEQ==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MPL-2.0", "optional": true, - "bin": { - "mime": "cli.js" - }, + "os": [ + "darwin" + ], + "peer": true, "engines": { - "node": ">=4" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/less/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "node_modules/lightningcss-freebsd-x64": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.3.tgz", + "integrity": "sha512-VUWeVf+V1UM54jv9M4wen9vMlIAyT69Krl9XjI8SsRxz4tdNV/7QEPlW6JASev/pYdiynUCW0pwaFquDRYdxMw==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MPL-2.0", "optional": true, - "bin": { - "semver": "bin/semver" + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/less/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.3.tgz", + "integrity": "sha512-UhgZ/XVNfXQVEJrMIWeK1Laj8KbhjbIz7F4znUk7G4zeGw7TRoJxhb66uWrEsonn1+O45w//0i0Fu0wIovYdYg==", + "cpu": [ + "arm" + ], "dev": true, + "license": "MPL-2.0", "optional": true, + "os": [ + "linux" + ], + "peer": true, "engines": { - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.3.tgz", + "integrity": "sha512-Pqau7jtgJNmQ/esugfmAT1aCFy/Gxc92FOxI+3n+LbMHBheBnk41xHDhc0HeYlx9G0xP5tK4t0Koy3QGGNqypw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, "engines": { - "node": ">= 0.8.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/license-webpack-plugin": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", - "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.3.tgz", + "integrity": "sha512-dxakOk66pf7KLS7VRYFO7B8WOJLecE5OPL2YOk52eriFd/yeyxt2Km5H0BjLfElokIaR+qWi33gB8MQLrdAY3A==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "webpack-sources": "^3.0.0" + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-sources": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.3.tgz", + "integrity": "sha512-ySZTNCpbfbK8rqpKJeJR2S0g/8UqqV3QnzcuWvpI60LWxnFN91nxpSSwCbzfOXkzKfar9j5eOuOplf+klKtINg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.3.tgz", + "integrity": "sha512-3pVZhIzW09nzi10usAXfIGTTSTYQ141dk88vGFNCgawIzayiIzZQxEcxVtIkdvlEq2YuFsL9Wcj/h61JHHzuFQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.3.tgz", + "integrity": "sha512-VRnkAvtIkeWuoBJeGOTrZxsNp4HogXtcaaLm8agmbYtLDOhQdpgxW6NjZZjDXbvGF+eOehGulXZ3C1TiwHY4QQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.3.tgz", + "integrity": "sha512-IszwRPu2cPnDQsZpd7/EAr0x2W7jkaWqQ1SwCVIZ/tSbZVXPLt6k8s6FkcyBjViCzvB5CW0We0QbbP7zp2aBjQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/lines-and-columns": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.3.tgz", - "integrity": "sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/loader-utils": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", - "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", - "dev": true, - "engines": { - "node": ">= 12.13.0" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "license": "MIT" }, "node_modules/lodash-es": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", - "dev": true - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/log4js": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", - "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", - "dev": true, - "dependencies": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "flatted": "^3.2.7", - "rfdc": "^1.3.0", - "streamroller": "^3.1.5" - }, - "engines": { - "node": ">=8.0" - } + "node_modules/lodash.upperfirst": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", + "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", + "license": "MIT" }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "peer": true, + "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -10906,139 +4681,97 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^3.0.2" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", - "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, - "node_modules/make-fetch-happen": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", - "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", - "dev": true, + "node_modules/matchmediaquery": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.4.2.tgz", + "integrity": "sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==", + "license": "MIT", "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "css-mediaquery": "^0.1.2" } }, - "node_modules/make-fetch-happen/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">= 0.4" } }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memfs": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", - "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", - "dev": true, - "dependencies": { - "fs-monkey": "^1.0.4" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { "node": ">=8.6" } }, - "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -11047,6 +4780,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -11054,357 +4788,96 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/mini-css-extract-plugin": { - "version": "2.7.6", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz", - "integrity": "sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==", - "dev": true, - "dependencies": { - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/minimalistic-assert": { + "node_modules/min-indent": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, + "node": ">=16 || 14 >=14.17" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, + "license": "ISC", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mixpanel-browser": { + "version": "2.63.0", + "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.63.0.tgz", + "integrity": "sha512-h7M0J/LR/5YLWCVuvPaYuzwV7CgV9jkJz0m94uaTDPebWkhNQPEir63rf/ZpBZgntyvYjO1yMZp2pIpwQ1sBMQ==", + "license": "Apache-2.0", "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-collect/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-collect/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/minipass-fetch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.3.tgz", - "integrity": "sha512-n5ITsTkDqYkYJZjcRWzZt9qnZKCT7nKCosJhHoj7S7zD+BP4jVbWs+odsniw5TA3E0sLomhTKOKjF86wf11PuQ==", - "dev": true, - "dependencies": { - "minipass": "^5.0.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-flush/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/minipass-json-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", - "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", - "dev": true, - "dependencies": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - } - }, - "node_modules/minipass-json-stream/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-json-stream/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" + "rrweb": "2.0.0-alpha.18" } }, "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "devOptional": true, + "license": "MIT", "engines": { "node": "*" } }, - "node_modules/moment-timezone": { - "version": "0.5.43", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz", - "integrity": "sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==", - "dependencies": { - "moment": "^2.29.4" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mrmime": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", - "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, - "node_modules/multicast-dns": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "dev": true, + "license": "MIT", "dependencies": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" } }, - "node_modules/multimatch": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", - "integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==", - "dependencies": { - "@types/minimatch": "^3.0.3", - "array-differ": "^3.0.0", - "array-union": "^2.1.0", - "arrify": "^2.0.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true, + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -11412,317 +4885,39 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "node_modules/needle": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", - "integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==", - "dev": true, - "optional": true, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", "dependencies": { - "debug": "^3.2.6", - "iconv-lite": "^0.6.3", - "sax": "^1.2.4" - }, - "bin": { - "needle": "bin/needle" + "whatwg-url": "^5.0.0" }, "engines": { - "node": ">= 4.4.x" - } - }, - "node_modules/needle/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "optional": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/needle/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/ng-morph": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/ng-morph/-/ng-morph-2.2.5.tgz", - "integrity": "sha512-plxrHfcz7aNRcTCxS9tUxI1F5Vfx5CZAAw8NAnJCyCb41Js6S+EyPDvn2v7H2QyfZzXU1H0BAqtiRQv/rE2zTA==", - "dependencies": { - "jsonc-parser": "3.0.0", - "minimatch": "3.0.5", - "multimatch": "5.0.0", - "ts-morph": "10.0.2" + "node": "4.x || >=6.0.0" }, "peerDependencies": { - "@angular-devkit/core": ">=11.0.0", - "@angular-devkit/schematics": ">=11.0.0" - } - }, - "node_modules/ng-morph/node_modules/jsonc-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", - "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==" - }, - "node_modules/ng-morph/node_modules/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", - "dependencies": { - "brace-expansion": "^1.1.7" + "encoding": "^0.1.0" }, - "engines": { - "node": "*" - } - }, - "node_modules/ng-zorro-antd": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/ng-zorro-antd/-/ng-zorro-antd-16.1.0.tgz", - "integrity": "sha512-+KjXoA0+v/liTtVIHswmOAzB9UaGADrO1tL9AOZsTLq5sZM8+DmhtixGRoSMD8HkkhpMFhsgEIxoHlkxtn1SXg==", - "dependencies": { - "@angular/cdk": "^16.0.0", - "@ant-design/icons-angular": "^16.0.0", - "date-fns": "^2.16.1", - "tslib": "^2.3.0" - }, - "peerDependencies": { - "@angular/animations": "^16.0.0", - "@angular/common": "^16.0.0", - "@angular/core": "^16.0.0", - "@angular/forms": "^16.0.0", - "@angular/platform-browser": "^16.0.0", - "@angular/router": "^16.0.0" - } - }, - "node_modules/ng2-charts": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-5.0.3.tgz", - "integrity": "sha512-/lTY64tiCN/pJPx+oIWRWOhtCk+ZbAU9yAUDNnRJwhe+a8ajcO5yS0tVOm5k7pj3doVp9+UdBRahyt6woJ95Rw==", - "dependencies": { - "lodash-es": "^4.17.15", - "tslib": "^2.3.0" - }, - "peerDependencies": { - "@angular/cdk": ">=16.0.0", - "@angular/common": ">=16.0.0", - "@angular/core": ">=16.0.0", - "@angular/platform-browser": ">=16.0.0", - "chart.js": "^3.4.0 || ^4.0.0", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/ngx-doc-viewer": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/ngx-doc-viewer/-/ngx-doc-viewer-15.0.1.tgz", - "integrity": "sha512-8FtA79/cIQUVagSU+IAno00VKzkfs/ep3p4rzsa7RXCbEGzXhbtCFOaqzuJN18PTaV0h9q3gpCcs2YRQ9ppSQw==", - "funding": [ - { - "type": "individual", - "url": "https://www.patreon.com/marcelh1983" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/marcelh1983" + "peerDependenciesMeta": { + "encoding": { + "optional": true } - ], - "dependencies": { - "docviewhelper": ">= 0.0.1", - "tslib": "^2.3.0" - }, - "peerDependencies": { - "@angular/common": ">= 10.0.0", - "@angular/core": ">= 10.0.0", - "@angular/platform-browser": ">= 10.0.0" - } - }, - "node_modules/ngx-socket-io": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/ngx-socket-io/-/ngx-socket-io-4.5.1.tgz", - "integrity": "sha512-PNIXmL2NpBwytJLlsyERrf7bUni6ZYtir2LacHMXHFseUDOEnNE7G53kXR+6IKLsVGJlG5RbnplQujRcfMOVxA==", - "dependencies": { - "core-js": "^3.0.0", - "reflect-metadata": "^0.1.10", - "socket.io": "^4.5.1", - "socket.io-client": "^4.5.1", - "tslib": "^2.3.0", - "zone.js": "~0.11.4" - }, - "peerDependencies": { - "@angular/common": "^16.0.0", - "@angular/core": "^16.0.0", - "rxjs": "^7.0.0" - } - }, - "node_modules/ngx-socket-io/node_modules/zone.js": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.8.tgz", - "integrity": "sha512-82bctBg2hKcEJ21humWIkXRlLBBmrc3nN7DFh5LGGhcyycO2S7FN8NmdvlcKaGFDNVL4/9kFLmwmInTavdJERA==", - "dependencies": { - "tslib": "^2.3.0" - } - }, - "node_modules/nice-napi": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", - "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "!win32" - ], - "dependencies": { - "node-addon-api": "^3.0.0", - "node-gyp-build": "^4.2.2" - } - }, - "node_modules/node-addon-api": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", - "dev": true - }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "dev": true, - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/node-gyp": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.0.tgz", - "integrity": "sha512-dMXsYP6gc9rRbejLXmTbVRYjAHw7ppswsKyMxuxJxxOHzluIO1rGp9TOQgjFJ+2MCqcOcQTOPB/8Xwhr+7s4Eg==", - "dev": true, - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^11.0.3", - "nopt": "^6.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^12.13 || ^14.13 || >=16" - } - }, - "node_modules/node-gyp-build": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", - "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", - "dev": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/node-gyp/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" } }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true - }, - "node_modules/nopt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", - "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true, - "dependencies": { - "abbrev": "^1.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/normalize-package-data": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", - "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", - "dev": true, - "dependencies": { - "hosted-git-info": "^6.0.0", - "is-core-module": "^2.8.1", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } + "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "devOptional": true, + "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -11732,721 +4927,42 @@ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/npm-bundled": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz", - "integrity": "sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==", - "dev": true, - "dependencies": { - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-install-checks": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.1.1.tgz", - "integrity": "sha512-dH3GmQL4vsPtld59cOn8uY0iOqRmqKvV+DLGwNXV/Q7MDgD2QfOADWd/mFXcIE5LVhYYGjA3baz6W9JneqnuCw==", - "dev": true, - "dependencies": { - "semver": "^7.1.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-normalize-package-bin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", - "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-package-arg": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", - "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^6.0.0", - "proc-log": "^3.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-packlist": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", - "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==", - "dev": true, - "dependencies": { - "ignore-walk": "^6.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-pick-manifest": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.1.tgz", - "integrity": "sha512-mRtvlBjTsJvfCCdmPtiu2bdlx8d/KXtF7yNXNWe7G0Z36qWA9Ny5zXsI2PfBZEv7SXgoxTmNaTzGSbbzDZChoA==", - "dev": true, - "dependencies": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^10.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-registry-fetch": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz", - "integrity": "sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==", - "dev": true, - "dependencies": { - "make-fetch-happen": "^11.0.0", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.1.2", - "npm-package-arg": "^10.0.0", - "proc-log": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "dev": true, - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/nwsapi": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", - "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", - "dev": true - }, - "node_modules/nx": { - "version": "16.5.1", - "resolved": "https://registry.npmjs.org/nx/-/nx-16.5.1.tgz", - "integrity": "sha512-I3hJRE4hG7JWAtncWwDEO3GVeGPpN0TtM8xH5ArZXyDuVeTth/i3TtJzdDzqXO1HHtIoAQN0xeq4n9cLuMil5g==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@nrwl/tao": "16.5.1", - "@parcel/watcher": "2.0.4", - "@yarnpkg/lockfile": "^1.1.0", - "@yarnpkg/parsers": "3.0.0-rc.46", - "@zkochan/js-yaml": "0.0.6", - "axios": "^1.0.0", - "chalk": "^4.1.0", - "cli-cursor": "3.1.0", - "cli-spinners": "2.6.1", - "cliui": "^7.0.2", - "dotenv": "~10.0.0", - "enquirer": "~2.3.6", - "fast-glob": "3.2.7", - "figures": "3.2.0", - "flat": "^5.0.2", - "fs-extra": "^11.1.0", - "glob": "7.1.4", - "ignore": "^5.0.4", - "js-yaml": "4.1.0", - "jsonc-parser": "3.2.0", - "lines-and-columns": "~2.0.3", - "minimatch": "3.0.5", - "npm-run-path": "^4.0.1", - "open": "^8.4.0", - "semver": "7.5.3", - "string-width": "^4.2.3", - "strong-log-transformer": "^2.1.0", - "tar-stream": "~2.2.0", - "tmp": "~0.2.1", - "tsconfig-paths": "^4.1.2", - "tslib": "^2.3.0", - "v8-compile-cache": "2.3.0", - "yargs": "^17.6.2", - "yargs-parser": "21.1.1" - }, - "bin": { - "nx": "bin/nx.js" - }, - "optionalDependencies": { - "@nx/nx-darwin-arm64": "16.5.1", - "@nx/nx-darwin-x64": "16.5.1", - "@nx/nx-freebsd-x64": "16.5.1", - "@nx/nx-linux-arm-gnueabihf": "16.5.1", - "@nx/nx-linux-arm64-gnu": "16.5.1", - "@nx/nx-linux-arm64-musl": "16.5.1", - "@nx/nx-linux-x64-gnu": "16.5.1", - "@nx/nx-linux-x64-musl": "16.5.1", - "@nx/nx-win32-arm64-msvc": "16.5.1", - "@nx/nx-win32-x64-msvc": "16.5.1" - }, - "peerDependencies": { - "@swc-node/register": "^1.4.2", - "@swc/core": "^1.2.173" - }, - "peerDependenciesMeta": { - "@swc-node/register": { - "optional": true - }, - "@swc/core": { - "optional": true - } - } - }, - "node_modules/nx/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/nx/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/nx/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/nx/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/nx/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/nx/node_modules/fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nx/node_modules/glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/nx/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/nx/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/nx/node_modules/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/nx/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "license": "MIT", + "engines": { + "node": ">= 6" } }, - "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-path": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.8.tgz", - "integrity": "sha512-YJjNZrlXJFM42wTBn6zgOJVar9KFJvzx6sTWDte8sWZF//cnjl0BxHNpfZx+ZffXX63A9q0b1zsFiBX4g4X5KA==", - "dev": true, - "engines": { - "node": ">= 10.12.0" - } - }, - "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "dev": true, - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ora/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/ora/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "dev": true, - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-retry/node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pacote": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.2.0.tgz", - "integrity": "sha512-rJVZeIwHTUta23sIZgEIM62WYwbmGbThdbnkt81ravBplQv+HjyroqnLRNH2+sLJHcGZmLRmhPwACqhfTcOmnA==", - "dev": true, - "dependencies": { - "@npmcli/git": "^4.0.0", - "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/promise-spawn": "^6.0.1", - "@npmcli/run-script": "^6.0.0", - "cacache": "^17.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^5.0.0", - "npm-package-arg": "^10.0.0", - "npm-packlist": "^7.0.0", - "npm-pick-manifest": "^8.0.0", - "npm-registry-fetch": "^14.0.0", - "proc-log": "^3.0.0", - "promise-retry": "^2.0.1", - "read-package-json": "^6.0.0", - "read-package-json-fast": "^3.0.0", - "sigstore": "^1.3.0", - "ssri": "^10.0.0", - "tar": "^6.1.11" - }, - "bin": { - "pacote": "lib/bin.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "node_modules/parchment": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz", - "integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==", - "dev": true + "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -12458,7 +4974,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -12472,96 +4988,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse-json/node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "devOptional": true, - "dependencies": { - "entities": "^4.4.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-html-rewriting-stream": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.0.0.tgz", - "integrity": "sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg==", - "dev": true, - "dependencies": { - "entities": "^4.3.0", - "parse5": "^7.0.0", - "parse5-sax-parser": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-sax-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", - "integrity": "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==", - "dev": true, - "dependencies": { - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -12570,64 +5002,91 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.0.tgz", - "integrity": "sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "engines": { - "node": "14 || >=16.14" - } + "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "dev": true + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/perfect-scrollbar": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.6.tgz", + "integrity": "sha512-rixgxw3SxyJbCaSpo1n35A/fwI1r2rdwMKOTCg/AcG+xOEyZcE8UHVjpZMFCVImzsFoCZeJTT+M/rdEIQYO2nw==", + "license": "MIT" + }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "optional": true + "license": "MIT" }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -12636,131 +5095,29 @@ } }, "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "dev": true, - "optional": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/piscina": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.0.0.tgz", - "integrity": "sha512-641nAmJS4k4iqpNUqfggqUBUMmlw0ZoM5VZKdQkV2e970Inn3Tk9kroCc1wpsYLD07vCwpys5iY0d3xI/9WkTg==", + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true, - "dependencies": { - "eventemitter-asyncresource": "^1.0.0", - "hdr-histogram-js": "^2.0.1", - "hdr-histogram-percentiles-obj": "^3.0.0" - }, - "optionalDependencies": { - "nice-napi": "^1.0.2" - } - }, - "node_modules/pkg-dir": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", - "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", - "dev": true, - "dependencies": { - "find-up": "^6.3.0" - }, + "license": "MIT", "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "dev": true, - "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "dev": true, - "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "dev": true, - "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/pkg-dir/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", - "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 6" } }, "node_modules/postcss": { - "version": "8.4.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz", - "integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==", - "dev": true, + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "funding": [ { "type": "opencollective", @@ -12775,101 +5132,122 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-loader": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.3.tgz", - "integrity": "sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA==", + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", "dev": true, + "license": "MIT", "dependencies": { - "cosmiconfig": "^8.2.0", - "jiti": "^1.18.2", - "semver": "^7.3.8" + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" }, "engines": { - "node": ">= 14.15.0" + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/webpack" + "url": "https://opencollective.com/postcss/" }, "peerDependencies": { - "postcss": "^7.0.0 || ^8.0.1", - "webpack": "^5.0.0" + "postcss": "^8.4.21" } }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", - "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" }, "engines": { - "node": "^10 || ^12 || >= 14" + "node": ">= 14" }, "peerDependencies": { - "postcss": "^8.1.0" + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "node_modules/postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.4" + "postcss-selector-parser": "^6.1.1" }, "engines": { - "node": "^10 || ^12 || >= 14" + "node": ">=12.0" }, "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "dev": true, - "dependencies": { - "icss-utils": "^5.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" + "postcss": "^8.2.14" } }, "node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -12882,153 +5260,191 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, - "engines": { - "node": ">= 0.8.0" - } + "license": "MIT" }, - "node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, "engines": { - "node": ">=6" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/proc-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", - "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.11", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.11.tgz", + "integrity": "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==", "dev": true, + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true - }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, + "license": "MIT", "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, + "license": "MIT" + }, + "node_modules/primereact": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/primereact/-/primereact-10.9.4.tgz", + "integrity": "sha512-GMrelh07Wd1cwKjHpay3LCpwP346D43qBVkt8H/anGYC3z7kv5/AP0pizZv+aGQs2Fg5ufTTf+SI7IKWmyzgGg==", + "license": "MIT", "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" + "@types/react-transition-group": "^4.4.1", + "react-transition-group": "^4.4.1" }, "engines": { - "node": ">= 0.10" + "node": ">=14.0.0" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "engines": { - "node": ">= 0.10" + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" } }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "dev": true, - "optional": true - }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", - "dev": true, - "engines": { - "node": ">=0.9" - } - }, - "node_modules/qrcode.react": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-3.1.0.tgz", - "integrity": "sha512-oyF+Urr3oAMUG/OiOuONL3HXM+53wvuH3mtIWQrYmsXoAq0DkvZp2RYUWFSMFtbdOpuS++9v+WAkzNVkMlNW6Q==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, "funding": [ { "type": "github", @@ -13042,88 +5458,60 @@ "type": "consulting", "url": "https://feross.org/support" } - ] - }, - "node_modules/quill-delta": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-4.2.2.tgz", - "integrity": "sha512-qjbn82b/yJzOjstBgkhtBjN2TNK+ZHP/BgUQO+j6bRhWQQdmj2lH6hXG7+nwwLF41Xgn//7/83lxs9n2BkTtTg==", - "dev": true, - "dependencies": { - "fast-diff": "1.2.0", - "lodash.clonedeep": "^4.5.0", - "lodash.isequal": "^4.5.0" - } + ], + "license": "MIT" }, "node_modules/raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "optional": true, + "license": "MIT", "dependencies": { "performance-now": "^2.1.0" } }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, + "node_modules/rc-animate": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-3.1.1.tgz", + "integrity": "sha512-8wg2Zg3EETy0k/9kYuis30NJNQg1D6/WSQwnCiz6SvyxQXNet/rVraRz3bPngwY6rcU2nlRvoShiYOorXyF7Sg==", + "license": "MIT", "dependencies": { - "safe-buffer": "^5.1.0" + "@ant-design/css-animation": "^1.7.2", + "classnames": "^2.2.6", + "raf": "^3.4.0", + "rc-util": "^4.15.3" } }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "engines": { - "node": ">= 0.6" + "node_modules/rc-animate/node_modules/rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "license": "MIT", + "dependencies": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" } }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/rc-align": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/rc-align/-/rc-align-4.0.15.tgz", - "integrity": "sha512-wqJtVH60pka/nOX7/IspElA8gjPNQKIx/ZqJ6heATCkXpe1Zg4cPVrMD2vC96wjsFFL8WsmhPbx9tdMo1qqlIA==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "dom-align": "^1.7.0", - "rc-util": "^5.26.0", - "resize-observer-polyfill": "^1.5.1" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } + "node_modules/rc-animate/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" }, "node_modules/rc-cascader": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.14.1.tgz", - "integrity": "sha512-fCsgjLIQqYZMhFj9UT+x2ZW4uobx7OP5yivcn6Xto5fuxHaldphsryzCeUVmreQOHEo0RP+032Ip9RDzrKVKJA==", + "version": "3.33.1", + "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.33.1.tgz", + "integrity": "sha512-Kyl4EJ7ZfCBuidmZVieegcbFw0RcU5bHHSbtEdmuLYd0fYHCAiYKZ6zon7fWAVyC6rWWOOib0XKdTSf7ElC9rg==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.12.5", - "array-tree-filter": "^2.1.0", + "@babel/runtime": "^7.25.7", "classnames": "^2.3.1", - "rc-select": "~14.7.0", - "rc-tree": "~5.7.0", - "rc-util": "^5.35.0" + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" }, "peerDependencies": { "react": ">=16.9.0", @@ -13131,9 +5519,10 @@ } }, "node_modules/rc-checkbox": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.1.0.tgz", - "integrity": "sha512-PAwpJFnBa3Ei+5pyqMMXdcKYKNBMS+TvSDiLdDnARnMJHC8ESxwPfm4Ao1gJiKtWLdmGfigascnCpwrHFgoOBQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.5.0.tgz", + "integrity": "sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.3.2", @@ -13145,9 +5534,10 @@ } }, "node_modules/rc-collapse": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.7.1.tgz", - "integrity": "sha512-N/7ejyiTf3XElNJBBpxqnZBUuMsQWEOPjB2QkfNvZ/Ca54eAvJXuOD1EGbCWCk2m7v/MSxku7mRpdeaLOCd4Gg==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.9.0.tgz", + "integrity": "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "2.x", @@ -13160,9 +5550,10 @@ } }, "node_modules/rc-dialog": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-9.1.0.tgz", - "integrity": "sha512-5ry+JABAWEbaKyYsmITtrJbZbJys8CtMyzV8Xn4LYuXMeUx5XVHNyJRoqLFE4AzBuXXzOWeaC49cg+XkxK6kHA==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-9.6.0.tgz", + "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/portal": "^1.0.0-8", @@ -13176,15 +5567,16 @@ } }, "node_modules/rc-drawer": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-6.2.0.tgz", - "integrity": "sha512-spPkZ3WvP0U0vy5dyzSwlUJ/+vLFtjP/cTwSwejhQRoDBaexSZHsBhELoCZcEggI7LQ7typmtG30lAue2HEhvA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.2.0.tgz", + "integrity": "sha512-9lOQ7kBekEJRdEpScHvtmEtXnAsy+NGDXiRWc2ZVC7QXAazNVbeT4EraQKYwCME8BJLa8Bxqxvs5swwyOepRwg==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.10.1", + "@babel/runtime": "^7.23.9", "@rc-component/portal": "^1.1.1", "classnames": "^2.2.6", "rc-motion": "^2.6.1", - "rc-util": "^5.21.2" + "rc-util": "^5.38.1" }, "peerDependencies": { "react": ">=16.9.0", @@ -13192,14 +5584,15 @@ } }, "node_modules/rc-dropdown": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.1.0.tgz", - "integrity": "sha512-VZjMunpBdlVzYpEdJSaV7WM7O0jf8uyDjirxXLZRNZ+tAC+NzD3PXPEtliFwGzVwBBdCmGuSqiS9DWcOLxQ9tw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.2.1.tgz", + "integrity": "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", - "@rc-component/trigger": "^1.7.0", + "@rc-component/trigger": "^2.0.0", "classnames": "^2.2.6", - "rc-util": "^5.17.0" + "rc-util": "^5.44.1" }, "peerDependencies": { "react": ">=16.11.0", @@ -13207,12 +5600,13 @@ } }, "node_modules/rc-field-form": { - "version": "1.36.2", - "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-1.36.2.tgz", - "integrity": "sha512-tCF/JjUsnxW80Gk4E4ZH74ONsaQMxVTRtui6XhQB8DJc4FHWLLa5pP8zwhxtPKC5NaO0QZ0Cv79JggDubn6n2g==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.7.0.tgz", + "integrity": "sha512-hgKsCay2taxzVnBPZl+1n4ZondsV78G++XVsMIJCAoioMjlMQR9YwAp7JZDIECzIu2Z66R+f4SFIRrO2DjDNAA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.0", - "async-validator": "^4.1.0", + "@rc-component/async-validator": "^5.0.3", "rc-util": "^5.32.2" }, "engines": { @@ -13223,15 +5617,55 @@ "react-dom": ">=16.9.0" } }, + "node_modules/rc-form": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/rc-form/-/rc-form-2.4.12.tgz", + "integrity": "sha512-sHfyWRrnjCHkeCYfYAGop2GQBUC6CKMPcJF9h/gL/vTmZB/RN6fNOGKjXrXjFbwFwKXUWBoPtIDDDmXQW9xNdw==", + "license": "MIT", + "dependencies": { + "async-validator": "~1.11.3", + "babel-runtime": "6.x", + "create-react-class": "^15.5.3", + "dom-scroll-into-view": "1.x", + "hoist-non-react-statics": "^3.3.0", + "lodash": "^4.17.4", + "rc-util": "^4.15.3", + "react-is": "^16.13.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "prop-types": "^15.0" + } + }, + "node_modules/rc-form/node_modules/rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "license": "MIT", + "dependencies": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" + } + }, + "node_modules/rc-form/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/rc-image": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.1.2.tgz", - "integrity": "sha512-Ah2MQ71Z+9SSOL2bK0AvJQ+BLYNk+Kb3k1U7dKY7+ziweDcJd3L5MqYXQXbNY/EEmgr50207rAzLHO8cn2DP6w==", + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.11.1.tgz", + "integrity": "sha512-XuoWx4KUXg7hNy5mRTy1i8c8p3K8boWg6UajbHpDXS5AlRVucNfTi5YxTtPBTBzegxAZpvuLfh3emXFt6ybUdA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.2", "@rc-component/portal": "^1.0.2", "classnames": "^2.2.6", - "rc-dialog": "~9.1.0", + "rc-dialog": "~9.6.0", "rc-motion": "^2.6.2", "rc-util": "^5.34.1" }, @@ -13241,9 +5675,10 @@ } }, "node_modules/rc-input": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.1.1.tgz", - "integrity": "sha512-NTR1Z4em681L8/ewb2KR80RykSmN8I2mzqzJDCoUmTrV1BB9Hk5d7ha4TnfgdEPPL148N+603sW2LExSXk1IbA==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.7.3.tgz", + "integrity": "sha512-A5w4egJq8+4JzlQ55FfQjDnPvOaAbzwC3VLOAdOytyek3TboSOP9qxN+Gifup+shVXfvecBLBbWBpWxmk02SWQ==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.1", "classnames": "^2.2.1", @@ -13255,15 +5690,16 @@ } }, "node_modules/rc-input-number": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-8.0.4.tgz", - "integrity": "sha512-TP+G5b7mZtbwXJ/YEZXF/OgbEZ6iqD4+RSuxZJ8VGKGXDcdt0FKIvpFoNQr/knspdFC4OxA0OfsWfFWfN4XSyA==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.4.0.tgz", + "integrity": "sha512-Tiy4DcXcFXAf9wDhN8aUAyMeCLHJUHA/VA/t7Hj8ZEx5ETvxG7MArDOSE6psbiSCo+vJPm4E3fGN710ITVn6GA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/mini-decimal": "^1.0.1", "classnames": "^2.2.5", - "rc-input": "~1.1.0", - "rc-util": "^5.28.0" + "rc-input": "~1.7.1", + "rc-util": "^5.40.1" }, "peerDependencies": { "react": ">=16.9.0", @@ -13271,17 +5707,18 @@ } }, "node_modules/rc-mentions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.5.0.tgz", - "integrity": "sha512-rERXsbUTNVrb5T/iDC0ki/SRGWJnOVraDy6O25Us3FSpuUZ3uq2TPZB4fRk0Hss5kyiEPzz2sprhkI4b+F4jUw==", + "version": "2.19.1", + "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.19.1.tgz", + "integrity": "sha512-KK3bAc/bPFI993J3necmaMXD2reZTzytZdlTvkeBbp50IGH1BDPDvxLdHDUrpQx2b2TGaVJsn+86BvYa03kGqA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.22.5", - "@rc-component/trigger": "^1.5.0", + "@rc-component/trigger": "^2.0.0", "classnames": "^2.2.6", - "rc-input": "~1.1.0", - "rc-menu": "~9.10.0", - "rc-textarea": "~1.3.0", - "rc-util": "^5.22.5" + "rc-input": "~1.7.1", + "rc-menu": "~9.16.0", + "rc-textarea": "~1.9.0", + "rc-util": "^5.34.1" }, "peerDependencies": { "react": ">=16.9.0", @@ -13289,12 +5726,13 @@ } }, "node_modules/rc-menu": { - "version": "9.10.0", - "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.10.0.tgz", - "integrity": "sha512-g27kpXaAoJh/fkPZF65/d4V+w4DhDeqomBdPcGnkFAcJnEM4o21TnVccrBUoDedLKzC7wJRw1Q7VTqEsfEufmw==", + "version": "9.16.1", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.16.1.tgz", + "integrity": "sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", - "@rc-component/trigger": "^1.6.2", + "@rc-component/trigger": "^2.0.0", "classnames": "2.x", "rc-motion": "^2.4.3", "rc-overflow": "^1.3.1", @@ -13306,13 +5744,14 @@ } }, "node_modules/rc-motion": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.7.3.tgz", - "integrity": "sha512-2xUvo8yGHdOHeQbdI8BtBsCIrWKchEmFEIskf0nmHtJsou+meLd/JE+vnvSX2JxcBrJtXY2LuBpxAOxrbY/wMQ==", + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.5.tgz", + "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.1", "classnames": "^2.2.1", - "rc-util": "^5.21.0" + "rc-util": "^5.44.0" }, "peerDependencies": { "react": ">=16.9.0", @@ -13320,13 +5759,14 @@ } }, "node_modules/rc-notification": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.0.5.tgz", - "integrity": "sha512-uEz2jggourwv/rR0obe7RHEa63UchqX4k+e+Qt2c3LaY7U9Tc+L6ANhzgCKYSA/afm0ebjmNZHoB5Cv47xEOcA==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.6.3.tgz", + "integrity": "sha512-42szwnn8VYQoT6GnjO00i1iwqV9D1TTMvxObWsuLwgl0TsOokzhkYiufdtQBsJMFjJravS1hfDKVMHLKLcPE4g==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "2.x", - "rc-motion": "^2.6.0", + "rc-motion": "^2.9.0", "rc-util": "^5.20.1" }, "engines": { @@ -13338,14 +5778,15 @@ } }, "node_modules/rc-overflow": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.3.1.tgz", - "integrity": "sha512-RY0nVBlfP9CkxrpgaLlGzkSoh9JhjJLu6Icqs9E7CW6Ewh9s0peF9OHIex4OhfoPsR92LR0fN6BlCY9Z4VoUtA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.4.1.tgz", + "integrity": "sha512-3MoPQQPV1uKyOMVNd6SZfONi+f3st0r8PksexIdBTeIYbMX0Jr+k7pHEDvsXtR4BpCv90/Pv2MovVNhktKrwvw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.1", "classnames": "^2.2.1", "rc-resize-observer": "^1.0.0", - "rc-util": "^5.19.2" + "rc-util": "^5.37.0" }, "peerDependencies": { "react": ">=16.9.0", @@ -13353,13 +5794,14 @@ } }, "node_modules/rc-pagination": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-3.5.0.tgz", - "integrity": "sha512-lUBVtVVUn7gGsq4mTyVpcZQr+AMcljbMiL/HcCmSdFrcsK0iZVKwwbXDxhz2IV0JXUs9Hzepr5sQFaF+9ad/pQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-5.1.0.tgz", + "integrity": "sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", - "classnames": "^2.2.1", - "rc-util": "^5.32.2" + "classnames": "^2.3.2", + "rc-util": "^5.38.0" }, "peerDependencies": { "react": ">=16.9.0", @@ -13367,14 +5809,17 @@ } }, "node_modules/rc-picker": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-3.12.0.tgz", - "integrity": "sha512-SsEhK4hbjAh3pvlqujIQaMcx6mLAwc0KN0TS9dJ0rtwGuUnSDa/mKgna/LjZlOT7U//b+dIH5BLSZttpklRG9A==", + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.11.3.tgz", + "integrity": "sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.10.1", - "@rc-component/trigger": "^1.5.0", + "@babel/runtime": "^7.24.7", + "@rc-component/trigger": "^2.0.0", "classnames": "^2.2.1", - "rc-util": "^5.30.0" + "rc-overflow": "^1.3.2", + "rc-resize-observer": "^1.4.0", + "rc-util": "^5.43.0" }, "engines": { "node": ">=8.x" @@ -13403,9 +5848,10 @@ } }, "node_modules/rc-progress": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-3.4.2.tgz", - "integrity": "sha512-iAGhwWU+tsayP+Jkl9T4+6rHeQTG9kDz8JAHZk4XtQOcYN5fj9H34NXNEdRdZx94VUDHMqCb1yOIvi8eJRh67w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-4.0.0.tgz", + "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.6", @@ -13417,9 +5863,10 @@ } }, "node_modules/rc-rate": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.12.0.tgz", - "integrity": "sha512-g092v5iZCdVzbjdn28FzvWebK2IutoVoiTeqoLTj9WM7SjA/gOJIw5/JFZMRyJYYVe1jLAU2UhAfstIpCNRozg==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.13.1.tgz", + "integrity": "sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.5", @@ -13434,13 +5881,14 @@ } }, "node_modules/rc-resize-observer": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.3.1.tgz", - "integrity": "sha512-iFUdt3NNhflbY3mwySv5CA1TC06zdJ+pfo0oc27xpf4PIOvfZwZGtD9Kz41wGYqC4SLio93RVAirSSpYlV/uYg==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz", + "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.20.7", "classnames": "^2.2.1", - "rc-util": "^5.27.0", + "rc-util": "^5.44.1", "resize-observer-polyfill": "^1.5.1" }, "peerDependencies": { @@ -13449,9 +5897,10 @@ } }, "node_modules/rc-segmented": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.2.2.tgz", - "integrity": "sha512-Mq52M96QdHMsNdE/042ibT5vkcGcD5jxKp7HgPC2SRofpia99P5fkfHy1pEaajLMF/kj0+2Lkq1UZRvqzo9mSA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.7.0.tgz", + "integrity": "sha512-liijAjXz+KnTRVnxxXG2sYDGd6iLL7VpGGdR8gwoxAXy2KglviKCxLWZdjKYJzYzGSUwKDSTdYk8brj54Bn5BA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.1", "classnames": "^2.2.1", @@ -13464,12 +5913,13 @@ } }, "node_modules/rc-select": { - "version": "14.7.3", - "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.7.3.tgz", - "integrity": "sha512-s0SQ6voafPXRYLSmHtB8GrkMJsXi2xS5vigzeaRDEgzHyj6xb2omUTinP7nrTCkBveEzrfy7eV/OillDzmcFTw==", + "version": "14.16.6", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.6.tgz", + "integrity": "sha512-YPMtRPqfZWOm2XGTbx5/YVr1HT0vn//8QS77At0Gjb3Lv+Lbut0IORJPKLWu1hQ3u4GsA0SrDzs7nI8JG7Zmyg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", - "@rc-component/trigger": "^1.5.0", + "@rc-component/trigger": "^2.1.1", "classnames": "2.x", "rc-motion": "^2.0.1", "rc-overflow": "^1.3.1", @@ -13485,13 +5935,14 @@ } }, "node_modules/rc-slider": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-10.1.1.tgz", - "integrity": "sha512-gn8oXazZISEhnmRinI89Z/JD/joAaM35jp+gDtIVSTD/JJMCCBqThqLk1SVJmvtfeiEF/kKaFY0+qt4SDHFUDw==", + "version": "11.1.8", + "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.8.tgz", + "integrity": "sha512-2gg/72YFSpKP+Ja5AjC5DPL1YnV8DEITDQrcc1eASrUYjl0esptaBVJBh5nLTXCCp15eD8EuGjwezVGSHhs9tQ==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.5", - "rc-util": "^5.27.0" + "rc-util": "^5.36.0" }, "engines": { "node": ">=8.x" @@ -13505,6 +5956,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-6.0.1.tgz", "integrity": "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.16.7", "classnames": "^2.2.3", @@ -13522,6 +5974,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-4.1.0.tgz", "integrity": "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.21.0", "classnames": "^2.2.1", @@ -13533,15 +5986,17 @@ } }, "node_modules/rc-table": { - "version": "7.32.1", - "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.32.1.tgz", - "integrity": "sha512-fHMQteKMocUC9I9Vex3eBLH7QsiaMR/qtzh3B1Ty2PoNGwVTwVdDFyRL05zch+JU3KnNNczgQeVvtf/p//gdrQ==", + "version": "7.50.4", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.50.4.tgz", + "integrity": "sha512-Y+YuncnQqoS5e7yHvfvlv8BmCvwDYDX/2VixTBEhkMDk9itS9aBINp4nhzXFKiBP/frG4w0pS9d9Rgisl0T1Bw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", - "@rc-component/context": "^1.3.0", + "@rc-component/context": "^1.4.0", "classnames": "^2.2.5", "rc-resize-observer": "^1.1.0", - "rc-util": "^5.27.1" + "rc-util": "^5.44.3", + "rc-virtual-list": "^3.14.2" }, "engines": { "node": ">=8.x" @@ -13552,17 +6007,18 @@ } }, "node_modules/rc-tabs": { - "version": "12.9.0", - "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-12.9.0.tgz", - "integrity": "sha512-2HnVowgMVrq0DfQtyu4mCd9E6pXlWNdM6VaDvOOHMsLYqPmpY+7zBqUC6YrrQ9xYXHciTS0e7TtjOHIvpVCHLQ==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.5.2.tgz", + "integrity": "sha512-Hbqf2IV6k/jPgfMjPtIDmPV0D0C9c/fN4B/fYcoh9qqaUzUZQoK0PYzsV3UaV+3UsmyoYt48p74m/HkLhGTw+w==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.2", "classnames": "2.x", - "rc-dropdown": "~4.1.0", - "rc-menu": "~9.10.0", + "rc-dropdown": "~4.2.0", + "rc-menu": "~9.16.0", "rc-motion": "^2.6.2", "rc-resize-observer": "^1.0.0", - "rc-util": "^5.16.0" + "rc-util": "^5.34.1" }, "engines": { "node": ">=8.x" @@ -13573,13 +6029,14 @@ } }, "node_modules/rc-textarea": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.3.4.tgz", - "integrity": "sha512-wn0YjTpvcVolcfXa0HtzL+jgV2QcwtfB29RwNAKj8hMgZOju1V24M3TfEDjABeQEAQbUGbjMbISREOX/YSVKhg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.9.0.tgz", + "integrity": "sha512-dQW/Bc/MriPBTugj2Kx9PMS5eXCCGn2cxoIaichjbNvOiARlaHdI99j4DTxLl/V8+PIfW06uFy7kjfUIDDKyxQ==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.1", - "rc-input": "~1.1.0", + "rc-input": "~1.7.1", "rc-resize-observer": "^1.0.0", "rc-util": "^5.27.0" }, @@ -13589,13 +6046,15 @@ } }, "node_modules/rc-tooltip": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-6.0.1.tgz", - "integrity": "sha512-MdvPlsD1fDSxKp9+HjXrc/CxLmA/s11QYIh1R7aExxfodKP7CZA++DG1AjrW80F8IUdHYcR43HAm0Y2BYPelHA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-6.4.0.tgz", + "integrity": "sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.2", - "@rc-component/trigger": "^1.0.4", - "classnames": "^2.3.1" + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.1", + "rc-util": "^5.44.3" }, "peerDependencies": { "react": ">=16.9.0", @@ -13603,9 +6062,10 @@ } }, "node_modules/rc-tree": { - "version": "5.7.9", - "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.7.9.tgz", - "integrity": "sha512-1hKkToz/EVjJlMVwmZnpXeLXt/1iQMsaAq9m+GNkUbK746gkc7QpJXSN/TzjhTI5Hi+LOSlrMaXLMT0bHPqILQ==", + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.13.1.tgz", + "integrity": "sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "2.x", @@ -13622,15 +6082,16 @@ } }, "node_modules/rc-tree-select": { - "version": "5.11.1", - "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.11.1.tgz", - "integrity": "sha512-EDG1rYFu1iD2Y8fg0yEmm0LV3XqWOy+SpgOMvO5396NgAZ67t0zVTNK6FQkIxzdXf5ri742BkB/B8+Ah6+0Kxw==", + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.27.0.tgz", + "integrity": "sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.10.1", + "@babel/runtime": "^7.25.7", "classnames": "2.x", - "rc-select": "~14.7.0", - "rc-tree": "~5.7.0", - "rc-util": "^5.16.1" + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" }, "peerDependencies": { "react": "*", @@ -13638,9 +6099,10 @@ } }, "node_modules/rc-upload": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.3.4.tgz", - "integrity": "sha512-uVbtHFGNjHG/RyAfm9fluXB6pvArAGyAx8z7XzXXyorEgVIWj6mOlriuDm0XowDHYz4ycNK0nE0oP3cbFnzxiQ==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.8.1.tgz", + "integrity": "sha512-toEAhwl4hjLAI1u8/CgKWt30BR06ulPa4iGQSMvSXoHzO88gPCslxqV/mnn4gJU7PDoltGIC9Eh+wkeudqgHyw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", "classnames": "^2.2.5", @@ -13652,41 +6114,59 @@ } }, "node_modules/rc-util": { - "version": "5.35.1", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.35.1.tgz", - "integrity": "sha512-TFB8FMe/lLB8Bv97PNRShfueayQ7oXqfKC9Y6wtxKvjSQvqlPbSG+xjUqZYppdvtMrukZfX4/xKbLV3ldwT1YQ==", + "version": "5.44.4", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz", + "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", - "react-is": "^16.12.0" + "react-is": "^18.2.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, + "node_modules/rc-util/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, "node_modules/rc-virtual-list": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.5.3.tgz", - "integrity": "sha512-rG6IuD4EYM8K6oZ8Shu2BC/CmcTdqng4yBWkc/5fjWhB20bl6QwR2Upyt7+MxvfscoVm8zOQY+tcpEO5cu4GaQ==", + "version": "3.18.5", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.18.5.tgz", + "integrity": "sha512-1FuxVSxhzTj3y8k5xMPbhXCB0t2TOiI3Tq+qE2Bu+GGV7f+ECVuQl4OUg6lZ2qT5fordTW7CBpr9czdzXCI7Pg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.20.0", "classnames": "^2.2.6", "rc-resize-observer": "^1.0.0", - "rc-util": "^5.15.0" + "rc-util": "^5.36.0" }, "engines": { "node": ">=8.x" }, "peerDependencies": { - "react": "*", - "react-dom": "*" + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/re-resizable": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.11.2.tgz", + "integrity": "sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "peer": true, + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" }, @@ -13694,88 +6174,221 @@ "node": ">=0.10.0" } }, + "node_modules/react-chartjs-2": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz", + "integrity": "sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==", + "license": "MIT", + "peerDependencies": { + "chart.js": "^4.1.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "peer": true, + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/read-package-json": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.4.tgz", - "integrity": "sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw==", - "dev": true, + "node_modules/react-i18next": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.4.1.tgz", + "integrity": "sha512-ahGab+IaSgZmNPYXdV1n+OYky95TGpFwnKRflX/16dY04DsYYKHtVLjeny7sBSCREEcoMbAgSkFiGLF5g5Oofw==", + "license": "MIT", "dependencies": { - "glob": "^10.2.2", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^5.0.0", - "npm-normalize-package-bin": "^3.0.0" + "@babel/runtime": "^7.25.0", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", + "license": "MIT" + }, + "node_modules/react-perfect-scrollbar": { + "version": "1.5.8", + "resolved": "https://registry.npmjs.org/react-perfect-scrollbar/-/react-perfect-scrollbar-1.5.8.tgz", + "integrity": "sha512-bQ46m70gp/HJtiBOF3gRzBISSZn8FFGNxznTdmTG8AAwpxG1bJCyn7shrgjEvGSQ5FJEafVEiosY+ccER11OSA==", + "license": "MIT", + "dependencies": { + "perfect-scrollbar": "^1.5.0", + "prop-types": "^15.6.1" + }, + "peerDependencies": { + "react": ">=16.3.3", + "react-dom": ">=16.3.3" + } + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-responsive": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-10.0.1.tgz", + "integrity": "sha512-OM5/cRvbtUWEX8le8RCT8scA8y2OPtb0Q/IViEyCEM5FBN8lRrkUOZnu87I88A6njxDldvxG+rLBxWiA7/UM9g==", + "license": "MIT", + "dependencies": { + "hyphenate-style-name": "^1.0.0", + "matchmediaquery": "^0.4.2", + "prop-types": "^15.6.1", + "shallow-equal": "^3.1.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8.0" } }, - "node_modules/read-package-json-fast": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", - "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", - "dev": true, + "node_modules/react-router": { + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz", + "integrity": "sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==", + "license": "MIT", "dependencies": { - "json-parse-even-better-errors": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" + "@remix-run/router": "1.23.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" } }, - "node_modules/read-package-json-fast/node_modules/json-parse-even-better-errors": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", - "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/read-package-json/node_modules/json-parse-even-better-errors": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", - "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/react-router-dom": { + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.0.tgz", + "integrity": "sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==", + "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "@remix-run/router": "1.23.0", + "react-router": "6.30.0" }, "engines": { - "node": ">= 6" + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-timer-hook": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/react-timer-hook/-/react-timer-hook-3.0.8.tgz", + "integrity": "sha512-bi2e7DhPBU1MRPU4ZHaVqBmgM9e2HK0ae8O2AIqwqjcPo4/qR7lVGQonOQLAKOZPQCJSYfV8F5aBWzOLXElzqQ==", + "license": "ISC", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/react-window": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.11.tgz", + "integrity": "sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.0.0", + "memoize-one": ">=3.1.1 <6" + }, + "engines": { + "node": ">8.0.0" + }, + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/reactcss": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", + "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", + "license": "MIT", + "dependencies": { + "lodash": "^4.0.1" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" } }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "devOptional": true, + "dev": true, + "license": "MIT", "dependencies": { "picomatch": "^2.2.1" }, @@ -13783,58 +6396,65 @@ "node": ">=8.10.0" } }, - "node_modules/reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "dev": true, + "license": "MIT", "dependencies": { - "regenerate": "^1.4.2" + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" }, - "node_modules/regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regex-parser": { - "version": "2.2.11", - "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", - "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", - "dev": true - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", - "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" }, "engines": { "node": ">= 0.4" @@ -13843,263 +6463,115 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", - "dev": true, - "dependencies": { - "@babel/regjsgen": "^0.8.0", - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", "engines": { "node": ">=4" } }, - "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "dev": true, - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "node_modules/resize-observer-polyfill": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", - "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" - }, - "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dev": true, - "dependencies": { - "is-core-module": "^2.11.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-url-loader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", - "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", - "dev": true, - "dependencies": { - "adjust-sourcemap-loader": "^4.0.0", - "convert-source-map": "^1.7.0", - "loader-utils": "^2.0.0", - "postcss": "^8.2.14", - "source-map": "0.6.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/resolve-url-loader/node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/resolve-url-loader/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/restore-cursor/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" } }, - "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, "node_modules/rgbcolor": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", "optional": true, "engines": { "node": ">= 0.8.15" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rollup": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.27.2.tgz", - "integrity": "sha512-YGwmHf7h2oUHkVBT248x0yt6vZkYQ3/rvE5iQuVBh3WO8GcJ6BNeOkpoX1yMHIiBm18EMLjBPIoUDkhgnyxGOQ==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.39.0.tgz", + "integrity": "sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==", "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, "bin": { "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=14.18.0", + "node": ">=18.0.0", "npm": ">=8.0.0" }, "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.39.0", + "@rollup/rollup-android-arm64": "4.39.0", + "@rollup/rollup-darwin-arm64": "4.39.0", + "@rollup/rollup-darwin-x64": "4.39.0", + "@rollup/rollup-freebsd-arm64": "4.39.0", + "@rollup/rollup-freebsd-x64": "4.39.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.39.0", + "@rollup/rollup-linux-arm-musleabihf": "4.39.0", + "@rollup/rollup-linux-arm64-gnu": "4.39.0", + "@rollup/rollup-linux-arm64-musl": "4.39.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.39.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.39.0", + "@rollup/rollup-linux-riscv64-gnu": "4.39.0", + "@rollup/rollup-linux-riscv64-musl": "4.39.0", + "@rollup/rollup-linux-s390x-gnu": "4.39.0", + "@rollup/rollup-linux-x64-gnu": "4.39.0", + "@rollup/rollup-linux-x64-musl": "4.39.0", + "@rollup/rollup-win32-arm64-msvc": "4.39.0", + "@rollup/rollup-win32-ia32-msvc": "4.39.0", + "@rollup/rollup-win32-x64-msvc": "4.39.0", "fsevents": "~2.3.2" } }, - "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true, - "engines": { - "node": ">=0.12.0" + "node_modules/rrdom": { + "version": "2.0.0-alpha.18", + "resolved": "https://registry.npmjs.org/rrdom/-/rrdom-2.0.0-alpha.18.tgz", + "integrity": "sha512-fSFzFFxbqAViITyYVA4Z0o5G6p1nEqEr/N8vdgSKie9Rn0FJxDSNJgjV0yiCIzcDs0QR+hpvgFhpbdZ6JIr5Nw==", + "license": "MIT", + "dependencies": { + "rrweb-snapshot": "^2.0.0-alpha.18" + } + }, + "node_modules/rrweb": { + "version": "2.0.0-alpha.18", + "resolved": "https://registry.npmjs.org/rrweb/-/rrweb-2.0.0-alpha.18.tgz", + "integrity": "sha512-1mjZcB+LVoGSx1+i9E2ZdAP90fS3MghYVix2wvGlZvrgRuLCbTCCOZMztFCkKpgp7/EeCdYM4nIHJkKX5J1Nmg==", + "license": "MIT", + "dependencies": { + "@rrweb/types": "^2.0.0-alpha.18", + "@rrweb/utils": "^2.0.0-alpha.18", + "@types/css-font-loading-module": "0.0.7", + "@xstate/fsm": "^1.4.0", + "base64-arraybuffer": "^1.0.1", + "mitt": "^3.0.0", + "rrdom": "^2.0.0-alpha.18", + "rrweb-snapshot": "^2.0.0-alpha.18" + } + }, + "node_modules/rrweb-snapshot": { + "version": "2.0.0-alpha.18", + "resolved": "https://registry.npmjs.org/rrweb-snapshot/-/rrweb-snapshot-2.0.0-alpha.18.tgz", + "integrity": "sha512-hBHZL/NfgQX6wO1D9mpwqFu1NJPpim+moIcKhFEjVTZVRUfCln+LOugRc4teVTCISYHN8Cw5e2iNTWCSm+SkoA==", + "license": "MIT", + "dependencies": { + "postcss": "^8.4.38" } }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "funding": [ { "type": "github", @@ -14114,396 +6586,66 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", - "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", - "dependencies": { - "tslib": "~2.1.0" - } - }, - "node_modules/rxjs/node_modules/tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/sass": { - "version": "1.64.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.64.1.tgz", - "integrity": "sha512-16rRACSOFEE8VN7SCgBu1MpYCyN7urj9At898tyzdXFhC+a+yOX5dXwAR7L8/IdPJ1NB8OYoXmD55DM30B2kEQ==", - "dev": true, - "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/sass-loader": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.2.tgz", - "integrity": "sha512-CQbKl57kdEv+KDLquhC+gE3pXt74LEAzm+tzywcA0/aHZuub8wTErbjAoNI57rPUWRYRNC5WUnNl8eGJNbDdwg==", - "dev": true, - "dependencies": { - "neo-async": "^2.6.2" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "fibers": ">= 3.1.0", - "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", - "sass": "^1.3.0", - "sass-embedded": "*", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "fibers": { - "optional": true - }, - "node-sass": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - } - } - }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true, - "optional": true - }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", "engines": { "node": ">=10" } }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "peer": true, + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" } }, - "node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/scroll-into-view-if-needed": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.0.10.tgz", - "integrity": "sha512-t44QCeDKAPf1mtQH3fYpWz8IM/DyvHLjs8wUvvwMYxk5moOqCzrMSxK6HQVD0QVmVjXFavoFIPRVrMuJPKAvtg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", "dependencies": { "compute-scroll-into-view": "^3.0.2" } }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "dev": true - }, - "node_modules/selfsigned": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", - "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", - "dev": true, - "dependencies": { - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" } }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } + "node_modules/shallow-equal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-3.1.0.tgz", + "integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==", + "license": "MIT" }, - "node_modules/semver/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/send/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/serve-index/node_modules/setprototypeof": { + "node_modules/shallowequal": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dev": true, - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -14516,38 +6658,24 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "ISC" }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, + "license": "ISC", "engines": { "node": ">=14" }, @@ -14555,86 +6683,43 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/sigstore": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.8.0.tgz", - "integrity": "sha512-ogU8qtQ3VFBawRJ8wjsBEX/vIFeHuGs1fm4jZtjWQwjo8pfAt7T/rh+udlAN4+QUe0IzA8qRSc/YZ7dHP6kh+w==", - "dev": true, - "dependencies": { - "@sigstore/bundle": "^1.0.0", - "@sigstore/protobuf-specs": "^0.2.0", - "@sigstore/tuf": "^1.0.3", - "make-fetch-happen": "^11.0.1" - }, - "bin": { - "sigstore": "bin/sigstore.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socket.io": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", - "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", - "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "cors": "~2.8.5", - "debug": "~4.3.2", - "engine.io": "~6.5.2", - "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/socket.io-adapter": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", - "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", - "dependencies": { - "ws": "~8.11.0" - } - }, "node_modules/socket.io-client": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz", - "integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", - "engine.io-client": "~6.5.2", + "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" }, "engines": { "node": ">=10.0.0" } }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/socket.io-parser": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" @@ -14643,91 +6728,37 @@ "node": ">=10.0.0" } }, - "node_modules/sockjs": { - "version": "0.3.24", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "dev": true, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, - "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", - "dev": true, - "dependencies": { - "ip": "^2.0.0", - "smart-buffer": "^4.2.0" + "ms": "^2.1.3" }, "engines": { - "node": ">= 10.13.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", - "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", - "dev": true, - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" + "node": ">=6.0" }, - "engines": { - "node": ">= 10" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/source-map-loader": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.1.tgz", - "integrity": "sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA==", - "dev": true, - "dependencies": { - "abab": "^2.0.6", - "iconv-lite": "^0.6.3", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.72.1" - } - }, - "node_modules/source-map-loader/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -14737,6 +6768,7 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -14747,191 +6779,57 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", - "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", - "dev": true - }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/ssri": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.4.tgz", - "integrity": "sha512-12+IR2CB2C28MMAw0Ncqwj5QbTcs0nGIhgJzYWzDkb21vWmfNI83KS4f3Ci6GI98WreIfG7o9UXp3C0qbpA8nQ==", - "dev": true, - "dependencies": { - "minipass": "^5.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } + "license": "MIT" }, "node_modules/stackblur-canvas": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.6.0.tgz", - "integrity": "sha512-8S1aIA+UoF6erJYnglGPug6MaHYGo1Ot7h5fuXx4fUPvcvQfcdw2o/ppCse63+eZf8PPidSu4v1JnmEVtEDnpg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "license": "MIT", "optional": true, "engines": { "node": ">=0.1.14" } }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "node_modules/std-env": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.1.tgz", + "integrity": "sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==", "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/stop-iteration-iterator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", - "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", - "dev": true, - "dependencies": { - "internal-slot": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/streamroller": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", - "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", - "dev": true, - "dependencies": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "fs-extra": "^8.1.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/streamroller/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/streamroller/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/streamroller/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } + "license": "MIT" }, "node_modules/string-convert": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", - "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==" + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT" }, "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, + "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/string-width-cjs": { @@ -14940,6 +6838,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -14949,10 +6848,19 @@ "node": ">=8" } }, - "node_modules/strip-ansi": { + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -14960,12 +6868,29 @@ "node": ">=8" } }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -14973,75 +6898,79 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=4" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/strong-log-transformer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz", - "integrity": "sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA==", + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "dev": true, + "license": "MIT", "dependencies": { - "duplexer": "^0.1.1", - "minimist": "^1.2.0", - "through": "^2.3.4" - }, - "bin": { - "sl-log-transformer": "bin/sl-log-transformer.js" + "min-indent": "^1.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/stylis": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz", - "integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==" + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" }, "engines": { - "node": ">=4" + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -15053,115 +6982,69 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "license": "MIT", "optional": true, "engines": { "node": ">=12.0.0" } }, - "node_modules/symbol-observable": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", - "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", - "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", - "dev": true, + "node_modules/swr": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.3.tgz", + "integrity": "sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==", + "license": "MIT", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "node_modules/tailwindcss": { + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "dev": true, + "license": "MIT", "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, "bin": { - "mkdirp": "bin/cmd.js" + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" }, "engines": { - "node": ">=10" + "node": ">=14.0.0" } }, - "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/terser": { - "version": "5.19.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz", - "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", + "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -15175,187 +7058,116 @@ "node": ">=10" } }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "license": "MIT" }, "node_modules/text-segmentation": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", "dependencies": { "utrie": "^1.0.2" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } }, "node_modules/throttle-debounce": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.0.tgz", - "integrity": "sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", "engines": { "node": ">=12.22" } }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" }, "node_modules/tinymce": { - "version": "6.8.3", - "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.8.3.tgz", - "integrity": "sha512-3fCHKAeqT+xNwBVESf6iDbDV0VNwZNmfrkx9c/6Gz5iB8piMfaO6s7FvoiTrj1hf1gVbfyLTnz1DooI6DhgINQ==" + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-7.7.2.tgz", + "integrity": "sha512-GX7Jd0ac9ph3QM2yei4uOoxytKX096CyG6VkkgQNikY39T6cDldoNgaqzHHlcm62WtdBMCd7Ch+PYaRnQo+NLA==", + "license": "GPL-2.0-or-later" }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "node_modules/tinypool": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8.17.0" + "node": "^18.0.0 || >=20.0.0" } }, - "node_modules/to-fast-properties": { + "node_modules/tinyrainbow": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" } }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -15366,286 +7178,74 @@ "node_modules/toggle-selection": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", - "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", - "dev": true, - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT" }, "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tsconfck": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.5.tgz", + "integrity": "sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==", + "dev": true, + "license": "MIT", "bin": { - "tree-kill": "cli.js" - } - }, - "node_modules/ts-morph": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-10.0.2.tgz", - "integrity": "sha512-TVuIfEqtr9dW25K3Jajqpqx7t/zLRFxKu2rXQZSDjTm4MO4lfmuj1hn8WEryjeDDBFcNOCi+yOmYUYR4HucrAg==", - "dependencies": { - "@ts-morph/common": "~0.9.0", - "code-block-writer": "^10.1.1" - } - }, - "node_modules/tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "dev": true, - "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" + "tsconfck": "bin/tsconfck.js" }, "engines": { - "node": ">=6" + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/tslib": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", - "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tuf-js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.7.tgz", - "integrity": "sha512-i3P9Kgw3ytjELUfpuKVDNBJvk4u5bXL6gskv572mcevPbSKCV3zt3djhmlEQ65yERjIbOSncy7U4cQJaB1CBCg==", - "dev": true, - "dependencies": { - "@tufjs/models": "1.0.4", - "debug": "^4.3.4", - "make-fetch-happen": "^11.1.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typed-assert": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", - "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", - "dev": true + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, "node_modules/typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=12.20" + "node": ">=14.17" } }, - "node_modules/ua-parser-js": { - "version": "0.7.35", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.35.tgz", - "integrity": "sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g==", + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - } - ], - "engines": { - "node": "*" - } - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unique-filename": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", - "dev": true, - "dependencies": { - "unique-slug": "^4.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/unique-slug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } + "license": "MIT" }, "node_modules/update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { @@ -15661,9 +7261,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -15672,323 +7273,379 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true, - "engines": { - "node": ">= 0.4.0" - } + "license": "MIT" }, "node_modules/utrie": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", "dependencies": { "base64-arraybuffer": "^1.0.2" } }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "node_modules/vite": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz", + "integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==", "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "postcss": "^8.5.3", + "rollup": "^4.30.1" + }, "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/validate-npm-package-name": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", - "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", - "dev": true, - "dependencies": { - "builtins": "^5.0.0" + "vite": "bin/vite.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "node_modules/vite-node": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.1.tgz", + "integrity": "sha512-V+IxPAE2FvXpTCHXyNem0M+gWm6J7eRyWPR6vYoG/Gl+IscNOjXzztUhimQgTxaAoUoj40Qqimaa0NLIOOAH4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.0", + "es-module-lexer": "^1.6.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, "engines": { - "node": ">= 0.8" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-tsconfig-paths": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", + "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.1.tgz", + "integrity": "sha512-kiZc/IYmKICeBAZr9DQ5rT7/6bD9G7uqQEki4fxazi1jdVl2mWGzedtBs5s6llz59yQhVb7FFY2MbHzHCnT79Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "3.1.1", + "@vitest/mocker": "3.1.1", + "@vitest/pretty-format": "^3.1.1", + "@vitest/runner": "3.1.1", + "@vitest/snapshot": "3.1.1", + "@vitest/spy": "3.1.1", + "@vitest/utils": "3.1.1", + "chai": "^5.2.0", + "debug": "^4.4.0", + "expect-type": "^1.2.0", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "std-env": "^3.8.1", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.1.1", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.1.1", + "@vitest/ui": "3.1.1", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } } }, "node_modules/void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", - "dev": true, + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", - "dev": true, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", "dependencies": { - "browser-process-hrtime": "^1.0.0" + "loose-envify": "^1.0.0" } }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, + "node_modules/web-vitals": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", + "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==", + "license": "Apache-2.0" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", "dependencies": { - "xml-name-validator": "^3.0.0" + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { "node": ">=10" - } - }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true, - "engines": { - "node": ">=10.4" - } - }, - "node_modules/webpack": { - "version": "5.88.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", - "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", - "dev": true, - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/webpack-dev-middleware": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.1.tgz", - "integrity": "sha512-y51HrHaFeeWir0YO4f0g+9GwZawuigzcAdRNon6jErXy/SqV/+O6eaVAzDqE6t3e3NpGeR5CS+cCDaTC+V3yEQ==", + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", "dependencies": { - "colorette": "^2.0.10", - "memfs": "^3.4.12", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - } + "node": ">=8" } }, - "node_modules/webpack-dev-server": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", - "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.5", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", - "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "launch-editor": "^2.6.0", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", - "ws": "^8.13.0" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-cli": { - "optional": true - } + "node": ">=8" } }, - "node_modules/webpack-dev-server/node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, - "dependencies": { - "colorette": "^2.0.10", - "memfs": "^3.4.3", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - }, + "license": "MIT", "engines": { - "node": ">= 12.13.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -16005,473 +7662,32 @@ } } }, - "node_modules/webpack-merge": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.9.0.tgz", - "integrity": "sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack-subresource-integrity": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", - "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", - "dev": true, - "dependencies": { - "typed-assert": "^1.0.8" - }, - "engines": { - "node": ">= 12" - }, - "peerDependencies": { - "html-webpack-plugin": ">= 5.0.0-beta.1 < 6", - "webpack": "^5.12.0" - }, - "peerDependenciesMeta": { - "html-webpack-plugin": { - "optional": true - } - } - }, - "node_modules/webpack/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/webpack/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/webpack/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/webpack/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "dependencies": { - "iconv-lite": "0.4.24" - } - }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "dev": true, - "dependencies": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", - "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, "node_modules/xmlhttprequest-ssl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", - "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", "engines": { "node": ">=0.4.0" } }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", + "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" }, "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zone.js": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.13.1.tgz", - "integrity": "sha512-+bIeDAFEBYuXRuU3qGQvzdPap+N1zjM4KkBAiiQuVVCrHrhjDuY6VkUhNa5+U27+9w0q3fbKiMCbpJ0XzMmSWA==", - "dependencies": { - "tslib": "^2.3.0" + "node": ">= 14" } } } diff --git a/worklenz-frontend/package.json b/worklenz-frontend/package.json index c900b4ff..527b3a82 100644 --- a/worklenz-frontend/package.json +++ b/worklenz-frontend/package.json @@ -1,77 +1,98 @@ { "name": "worklenz", "version": "1.0.0", - "scripts": { - "ng": "ng", - "start": "ng serve --proxy-config proxy.config.json --disable-host-check", - "start-docker": "ng serve --proxy-config proxy-docker.config.json --disable-host-check --host 0.0.0.0", - "build": "ng build --extract-licenses --common-chunk --delete-output-path --output-hashing=all", - "watch": "ng build --watch --configuration development", - "test": "ng test", - "lint": "ng lint" - }, - "engines": { - "node": ">=16.13.0", - "npm": ">= 8.5.5", - "yarn": "WARNING: Please use npm package manager instead of yarn" - }, "private": true, + "scripts": { + "start": "vite", + "prebuild": "node scripts/copy-tinymce.js", + "build": "node --max-old-space-size=4096 node_modules/.bin/vite build", + "dev-build": "vite build", + "serve": "vite preview", + "format": "prettier --write ." + }, "dependencies": { - "@angular/animations": "^16.2.0", - "@angular/cdk": "^16.2.0", - "@angular/common": "^16.2.0", - "@angular/compiler": "^16.2.0", - "@angular/core": "^16.2.0", - "@angular/forms": "^16.2.0", - "@angular/platform-browser": "^16.2.0", - "@angular/platform-browser-dynamic": "^16.2.0", - "@angular/router": "^16.2.0", - "@angular/service-worker": "^16.2.0", - "@rx-angular/cdk": "^16.0.0", - "@rx-angular/template": "^16.0.2", - "@tinymce/tinymce-angular": "^7.0.0", - "antd": "^5.8.2", - "bootstrap": "^5.3.1", - "chart.js": "^4.3.3", + "@ant-design/colors": "^7.1.0", + "@ant-design/compatible": "^5.1.4", + "@ant-design/icons": "^5.4.0", + "@ant-design/pro-components": "^2.7.19", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@emotion/react": "^11.14.0", + "@paddle/paddle-js": "^1.3.3", + "@reduxjs/toolkit": "^2.2.7", + "@tanstack/react-table": "^8.20.6", + "@tanstack/react-virtual": "^3.11.2", + "@tinymce/tinymce-react": "^5.1.1", + "antd": "^5.24.1", + "axios": "^1.7.9", + "chart.js": "^4.4.7", "chartjs-plugin-datalabels": "^2.2.0", - "chartjs-to-image": "^1.2.2", + "date-fns": "^4.1.0", + "dompurify": "^3.2.4", + "gantt-task-react": "^0.3.9", "html2canvas": "^1.4.1", - "jquery": "^3.7.0", - "jspdf": "^2.5.1", - "moment": "^2.29.4", - "moment-timezone": "^0.5.43", - "ng-zorro-antd": "^16.1.0", - "ng2-charts": "^5.0.3", - "ngx-doc-viewer": "^15.0.1", - "ngx-socket-io": "^4.5.1", - "rxjs": "~7.4.0", - "tinymce": "^6.8.3", - "tslib": "^2.6.1", - "zone.js": "^0.13.1" + "i18next": "^23.16.8", + "i18next-browser-languagedetector": "^8.0.3", + "i18next-http-backend": "^2.7.3", + "jspdf": "^3.0.0", + "mixpanel-browser": "^2.56.0", + "primereact": "^10.8.4", + "re-resizable": "^6.10.3", + "react": "^18.3.1", + "react-chartjs-2": "^5.2.0", + "react-dom": "^18.3.1", + "react-i18next": "^15.0.1", + "react-perfect-scrollbar": "^1.5.8", + "react-redux": "^9.2.0", + "react-responsive": "^10.0.0", + "react-router-dom": "^6.28.1", + "react-timer-hook": "^3.0.8", + "react-window": "^1.8.11", + "socket.io-client": "^4.8.1", + "tinymce": "^7.7.2", + "web-vitals": "^4.2.4" }, "devDependencies": { - "@angular-devkit/build-angular": "^16.2.0", - "@angular-eslint/builder": "^16.1.0", - "@angular-eslint/eslint-plugin": "^16.0.3", - "@angular-eslint/eslint-plugin-template": "^16.0.3", - "@angular-eslint/schematics": "^16.1.0", - "@angular-eslint/template-parser": "^16.1.0", - "@angular/cli": "^16.2.0", - "@angular/compiler-cli": "^16.2.0", - "@types/file-saver": "^2.0.5", - "@types/jasmine": "~3.10.0", - "@types/jquery": "^3.5.16", - "@types/node": "^12.20.55", - "@types/quill": "^2.0.10", - "@typescript-eslint/eslint-plugin": "5.48.2", - "@typescript-eslint/parser": "5.48.2", - "eslint": "^8.46.0", - "jasmine-core": "~3.10.0", - "karma": "~6.3.0", - "karma-chrome-launcher": "~3.1.0", - "karma-coverage": "~2.1.0", - "karma-jasmine": "~4.0.0", - "karma-jasmine-html-reporter": "~1.7.0", - "typescript": "~5.0.4" + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/chart.js": "^2.9.41", + "@types/dompurify": "^3.0.5", + "@types/jest": "^27.5.2", + "@types/lodash": "^4.17.15", + "@types/mixpanel-browser": "^2.50.2", + "@types/node": "^20.8.4", + "@types/react": "19.0.0", + "@types/react-dom": "19.0.0", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "postcss": "^8.5.2", + "prettier-plugin-tailwindcss": "^0.6.8", + "tailwindcss": "^3.4.17", + "terser": "^5.39.0", + "typescript": "^5.7.3", + "vite": "^6.2.5", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.0.5" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] } } diff --git a/worklenz-frontend/path/to/members-reports-drawer.tsx b/worklenz-frontend/path/to/members-reports-drawer.tsx new file mode 100644 index 00000000..b9671dc1 --- /dev/null +++ b/worklenz-frontend/path/to/members-reports-drawer.tsx @@ -0,0 +1,49 @@ +import MembersReportsTimeLogsTab from './members-reports-time-logs-tab'; + +type MembersReportsDrawerProps = { + memberId: string | null; + exportTimeLogs: () => void; +}; + +const MembersReportsDrawer = ({ memberId, exportTimeLogs }: MembersReportsDrawerProps) => { + return ( + + + {selectedMember.name} + + + + + + + + + + ) + } + > + {selectedMember && } + {selectedMember && } + {selectedMember && } + + ); +}; + +export default MembersReportsDrawer; \ No newline at end of file diff --git a/worklenz-frontend/path/to/members-reports-time-logs-tab.tsx b/worklenz-frontend/path/to/members-reports-time-logs-tab.tsx new file mode 100644 index 00000000..a86c66ba --- /dev/null +++ b/worklenz-frontend/path/to/members-reports-time-logs-tab.tsx @@ -0,0 +1,41 @@ +import React, { useState } from 'react'; +import { Flex, Skeleton } from 'antd'; +import { useTranslation } from 'react-i18next'; +import { useTimeLogs } from '../contexts/TimeLogsContext'; +import { BillableFilter } from './BillableFilter'; +import { TimeLogCard } from './TimeLogCard'; +import { EmptyListPlaceholder } from './EmptyListPlaceholder'; +import { TaskDrawer } from './TaskDrawer'; +import MembersReportsDrawer from './members-reports-drawer'; + +const MembersReportsTimeLogsTab: React.FC = () => { + const { t } = useTranslation(); + const { timeLogsData, billable, setBillable, exportTimeLogs, exporting } = useTimeLogs(); + + return ( + + + + + + + {timeLogsData.length > 0 ? ( + + {timeLogsData.map((logs, index) => ( + + ))} + + ) : ( + + )} + + + {createPortal(, document.body)} + + + ); +}; + +export default MembersReportsTimeLogsTab; \ No newline at end of file diff --git a/worklenz-frontend/postcss.config.js b/worklenz-frontend/postcss.config.js new file mode 100644 index 00000000..12a703d9 --- /dev/null +++ b/worklenz-frontend/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/worklenz-frontend/project-report-table.css b/worklenz-frontend/project-report-table.css new file mode 100644 index 00000000..3e67444c --- /dev/null +++ b/worklenz-frontend/project-report-table.css @@ -0,0 +1,17 @@ +/* Update or add these styles */ +.sticky-column { + position: sticky; + left: 0; + z-index: 2; + background-color: var(--background-default); +} + +/* Specific style for odd rows */ +.table-body-row:nth-child(odd) .sticky-column { + background-color: var(--background-alternate); /* or your specific odd row background color */ +} + +/* Maintain hover state */ +.table-body-row:hover .sticky-column { + background-color: var(--background-hover); +} \ No newline at end of file diff --git a/worklenz-frontend/proxy-docker.config.json b/worklenz-frontend/proxy-docker.config.json deleted file mode 100644 index 3d88dab7..00000000 --- a/worklenz-frontend/proxy-docker.config.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "/api": { - "target": "http://backend:3000/", - "headers": { - "language": "en", - "Accept": "application/json", - "Content-Type": "application/json; charset=utf-8" - }, - "secure": false, - "changeOrigin": true, - "logLevel": "debug" - }, - "/secure": { - "target": "http://backend:3000/", - "headers": { - "language": "en", - "Accept": "application/json", - "Content-Type": "application/json; charset=utf-8" - }, - "secure": false, - "changeOrigin": true, - "logLevel": "debug" - } -} diff --git a/worklenz-frontend/proxy.config.json b/worklenz-frontend/proxy.config.json deleted file mode 100644 index 6866fe17..00000000 --- a/worklenz-frontend/proxy.config.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "/api": { - "target": "http://127.0.0.1:3000/", - "headers": { - "language": "en", - "Accept": "application/json", - "Content-Type": "application/json; charset=utf-8" - }, - "secure": false, - "changeOrigin": true, - "logLevel": "debug" - }, - "/secure": { - "target": "http://127.0.0.1:3000/", - "headers": { - "language": "en", - "Accept": "application/json", - "Content-Type": "application/json; charset=utf-8" - }, - "secure": false, - "changeOrigin": true, - "logLevel": "debug" - } -} diff --git a/worklenz-frontend/src/assets/images/files/ai.png b/worklenz-frontend/public/file-types/ai.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/ai.png rename to worklenz-frontend/public/file-types/ai.png diff --git a/worklenz-frontend/src/assets/images/files/avi.png b/worklenz-frontend/public/file-types/avi.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/avi.png rename to worklenz-frontend/public/file-types/avi.png diff --git a/worklenz-frontend/src/assets/images/files/css.png b/worklenz-frontend/public/file-types/css.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/css.png rename to worklenz-frontend/public/file-types/css.png diff --git a/worklenz-frontend/src/assets/images/files/csv.png b/worklenz-frontend/public/file-types/csv.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/csv.png rename to worklenz-frontend/public/file-types/csv.png diff --git a/worklenz-frontend/src/assets/images/files/doc.png b/worklenz-frontend/public/file-types/doc.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/doc.png rename to worklenz-frontend/public/file-types/doc.png diff --git a/worklenz-frontend/src/assets/images/files/exe.png b/worklenz-frontend/public/file-types/exe.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/exe.png rename to worklenz-frontend/public/file-types/exe.png diff --git a/worklenz-frontend/src/assets/images/files/html.png b/worklenz-frontend/public/file-types/html.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/html.png rename to worklenz-frontend/public/file-types/html.png diff --git a/worklenz-frontend/src/assets/images/files/jpg.png b/worklenz-frontend/public/file-types/jpg.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/jpg.png rename to worklenz-frontend/public/file-types/jpg.png diff --git a/worklenz-frontend/src/assets/images/files/js.png b/worklenz-frontend/public/file-types/js.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/js.png rename to worklenz-frontend/public/file-types/js.png diff --git a/worklenz-frontend/src/assets/images/files/json.png b/worklenz-frontend/public/file-types/json.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/json.png rename to worklenz-frontend/public/file-types/json.png diff --git a/worklenz-frontend/src/assets/images/files/mp3.png b/worklenz-frontend/public/file-types/mp3.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/mp3.png rename to worklenz-frontend/public/file-types/mp3.png diff --git a/worklenz-frontend/src/assets/images/files/mp4.png b/worklenz-frontend/public/file-types/mp4.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/mp4.png rename to worklenz-frontend/public/file-types/mp4.png diff --git a/worklenz-frontend/src/assets/images/files/pdf.png b/worklenz-frontend/public/file-types/pdf.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/pdf.png rename to worklenz-frontend/public/file-types/pdf.png diff --git a/worklenz-frontend/src/assets/images/files/png.png b/worklenz-frontend/public/file-types/png.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/png.png rename to worklenz-frontend/public/file-types/png.png diff --git a/worklenz-frontend/src/assets/images/files/ppt.png b/worklenz-frontend/public/file-types/ppt.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/ppt.png rename to worklenz-frontend/public/file-types/ppt.png diff --git a/worklenz-frontend/src/assets/images/files/psd.png b/worklenz-frontend/public/file-types/psd.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/psd.png rename to worklenz-frontend/public/file-types/psd.png diff --git a/worklenz-frontend/src/assets/images/files/search.png b/worklenz-frontend/public/file-types/search.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/search.png rename to worklenz-frontend/public/file-types/search.png diff --git a/worklenz-frontend/src/assets/images/files/svg.png b/worklenz-frontend/public/file-types/svg.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/svg.png rename to worklenz-frontend/public/file-types/svg.png diff --git a/worklenz-frontend/src/assets/images/files/txt.png b/worklenz-frontend/public/file-types/txt.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/txt.png rename to worklenz-frontend/public/file-types/txt.png diff --git a/worklenz-frontend/src/assets/images/files/xls.png b/worklenz-frontend/public/file-types/xls.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/xls.png rename to worklenz-frontend/public/file-types/xls.png diff --git a/worklenz-frontend/src/assets/images/files/xml.png b/worklenz-frontend/public/file-types/xml.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/xml.png rename to worklenz-frontend/public/file-types/xml.png diff --git a/worklenz-frontend/src/assets/images/files/zip.png b/worklenz-frontend/public/file-types/zip.png similarity index 100% rename from worklenz-frontend/src/assets/images/files/zip.png rename to worklenz-frontend/public/file-types/zip.png diff --git a/worklenz-frontend/public/locales/en/404-page.json b/worklenz-frontend/public/locales/en/404-page.json new file mode 100644 index 00000000..a93627f1 --- /dev/null +++ b/worklenz-frontend/public/locales/en/404-page.json @@ -0,0 +1,4 @@ +{ + "doesNotExistText": "Sorry, the page you visited does not exist.", + "backHomeButton": "Back Home" +} diff --git a/worklenz-frontend/public/locales/en/account-setup.json b/worklenz-frontend/public/locales/en/account-setup.json new file mode 100644 index 00000000..5e71ca40 --- /dev/null +++ b/worklenz-frontend/public/locales/en/account-setup.json @@ -0,0 +1,31 @@ +{ + "continue": "Continue", + + "setupYourAccount": "Setup Your Worklenz Account.", + "organizationStepTitle": "Name Your Organization", + "organizationStepLabel": "Pick a name for your Worklenz account.", + + "projectStepTitle": "Create your first project", + "projectStepLabel": "What project are you working on right now?", + "projectStepPlaceholder": "e.g. Marketing Plan", + + "tasksStepTitle": "Create your first tasks", + "tasksStepLabel": "Type a few tasks that you are going to do in", + "tasksStepAddAnother": "Add another", + + "emailPlaceholder": "Email address", + "invalidEmail": "Please enter a valid email address", + "or": "or", + "templateButton": "Import from template", + "goBack": "Go Back", + "cancel": "Cancel", + "create": "Create", + "templateDrawerTitle": "Select from templates", + "step3InputLabel": "Invite with email", + "addAnother": "Add another", + "skipForNow": "Skip for now", + "formTitle": "Create your first task.", + "step3Title": "Invite your team to work with", + "maxMembers": " (You can invite up to 5 members)", + "maxTasks": " (You can create up to 5 tasks)" +} diff --git a/worklenz-frontend/public/locales/en/admin-center/current-bill.json b/worklenz-frontend/public/locales/en/admin-center/current-bill.json new file mode 100644 index 00000000..e5846b79 --- /dev/null +++ b/worklenz-frontend/public/locales/en/admin-center/current-bill.json @@ -0,0 +1,113 @@ +{ + "title": "Billings", + "currentBill": "Current Bill", + "configuration": "Configuration", + "currentPlanDetails": "Current Plan Details", + "upgradePlan": "Upgrade Plan", + "cardBodyText01": "Free trial", + "cardBodyText02": "(Your trial plan expires in 1 month 19 days)", + "redeemCode": "Redeem Code", + "accountStorage": "Account Storage", + "used": "Used:", + "remaining": "Remaining:", + "charges": "Charges", + "tooltip": "Charges for the current billing cycle", + "description": "Description", + "billingPeriod": "Billing Period", + "billStatus": "Bill Status", + "perUserValue": "Per User Value", + "users": "Users", + + "amount": "Amount", + "invoices": "Invoices", + "transactionId": "Transaction ID", + "transactionDate": "Transaction Date", + "paymentMethod": "Payment Method", + "status": "Status", + "ltdUsers": "You can add up to {{ltd_users}} users.", + + "totalSeats": "Total seats", + "availableSeats": "Available seats", + "addMoreSeats": "Add more seats", + + "drawerTitle": "Redeem Code", + "label": "Redeem Code", + "drawerPlaceholder": "Enter your redeem code", + "redeemSubmit": "Submit", + + "modalTitle": "Select the best plan for your team", + "seatLabel": "No of seats", + "freePlan": "Free Plan", + "startup": "Startup", + "business": "Business", + "tag": "Most Popular", + "enterprise": "Enterprise", + + "freeSubtitle": "free forever", + "freeUsers": "Best for personal use", + "freeText01": "100MB storage", + "freeText02": "3 projects", + "freeText03": "5 team members", + + "startupSubtitle": "FLAT RATE / month", + "startupUsers": "Upto 15 users", + "startupText01": "25GB storage", + "startupText02": "Unlimited active projects", + "startupText03": "Schedule", + "startupText04": "Reporting", + "startupText05": "Subscribe to projects", + + "businessSubtitle": "user / month", + "businessUsers": "16 - 200 users", + + "enterpriseUsers": "200 - 500+ users", + + "footerTitle": "Please provide us with a contact number we can use to reach you.", + "footerLabel": "Contact Number", + "footerButton": "Contact us", + + "redeemCodePlaceHolder": "Enter your redeem code", + "submit": "Submit", + + "trialPlan": "Free Trial", + "trialExpireDate": "Valid until {{trial_expire_date}}", + "trialExpired": "Your free trial expired {{trial_expire_string}}", + "trialInProgress": "Your free trial expires {{trial_expire_string}}", + + "required": "This field is required", + "invalidCode": "Invalid code", + + "selectPlan": "Select the best plan for your team", + "changeSubscriptionPlan": "Change your subscription plan", + "noOfSeats": "Number of seats", + "annualPlan": "Pro - Annual", + "monthlyPlan": "Pro - Monthly", + "freeForever": "Free Forever", + "bestForPersonalUse": "Best for personal use", + "storage": "Storage", + "projects": "Projects", + "teamMembers": "Team Members", + "unlimitedTeamMembers": "Unlimited Team Members", + "unlimitedActiveProjects": "Unlimited active projects", + "schedule": "Schedule", + "reporting": "Reporting", + "subscribeToProjects": "Subscribe to projects", + "billedAnnually": "Billed Annually", + "billedMonthly": "Billed Monthly", + + "pausePlan": "Pause Plan", + "resumePlan": "Resume Plan", + "changePlan": "Change Plan", + "cancelPlan": "Cancel Plan", + + "perMonthPerUser": "per user/month", + "viewInvoice": "View Invoice", + "switchToFreePlan": "Switch to Free Plan", + + "expirestoday": "today", + "expirestomorrow": "tomorrow", + "expiredDaysAgo": "{{days}} days ago", + + "continueWith": "Continue with {{plan}}", + "changeToPlan": "Change to {{plan}}" +} diff --git a/worklenz-frontend/public/locales/en/admin-center/overview.json b/worklenz-frontend/public/locales/en/admin-center/overview.json new file mode 100644 index 00000000..efc42855 --- /dev/null +++ b/worklenz-frontend/public/locales/en/admin-center/overview.json @@ -0,0 +1,8 @@ +{ + "overview": "Overview", + "name": "Organization Name", + "owner": "Organization Owner", + "admins": "Organization Admins", + "contactNumber": "Add Contact Number", + "edit": "Edit" +} diff --git a/worklenz-frontend/public/locales/en/admin-center/projects.json b/worklenz-frontend/public/locales/en/admin-center/projects.json new file mode 100644 index 00000000..4e491d73 --- /dev/null +++ b/worklenz-frontend/public/locales/en/admin-center/projects.json @@ -0,0 +1,12 @@ +{ + "membersCount": "Members Count", + "createdAt": "Created at", + "projectName": "Project Name", + "teamName": "Team Name", + "refreshProjects": "Refresh projects", + "searchPlaceholder": "Search by project name", + "deleteProject": "Are you sure you want to delete this project?", + "confirm": "Confirm", + "cancel": "Cancel", + "delete": "Delete Project" +} diff --git a/worklenz-frontend/public/locales/en/admin-center/sidebar.json b/worklenz-frontend/public/locales/en/admin-center/sidebar.json new file mode 100644 index 00000000..3b03d499 --- /dev/null +++ b/worklenz-frontend/public/locales/en/admin-center/sidebar.json @@ -0,0 +1,8 @@ +{ + "overview": "Overview", + "users": "Users", + "teams": "Teams", + "billing": "Billing", + "projects": "Projects", + "adminCenter": "Admin Center" +} diff --git a/worklenz-frontend/public/locales/en/admin-center/teams.json b/worklenz-frontend/public/locales/en/admin-center/teams.json new file mode 100644 index 00000000..e03f8515 --- /dev/null +++ b/worklenz-frontend/public/locales/en/admin-center/teams.json @@ -0,0 +1,33 @@ +{ + "title": "Teams", + "subtitle": "teams", + "tooltip": "Refresh teams", + "placeholder": "Search by name", + "addTeam": "Add Team", + "team": "Team", + "membersCount": "Members Count", + "members": "Members", + "drawerTitle": "Create New Team", + "label": "Team Name", + "drawerPlaceholder": "Name", + "create": "Create", + "delete": "Delete", + "settings": "Settings", + "popTitle": "Are you sure?", + "message": "Please enter a Name", + "teamSettings": "Team Settings", + "teamName": "Team Name", + "teamDescription": "Team Description", + "teamMembers": "Team Members", + "teamMembersCount": "Team Members Count", + "teamMembersPlaceholder": "Search by name", + "addMember": "Add Member", + "add": "Add", + "update": "Update", + "teamNamePlaceholder": "Name of the team", + "user": "User", + "role": "Role", + "owner": "Owner", + "admin": "Admin", + "member": "Member" +} diff --git a/worklenz-frontend/public/locales/en/admin-center/users.json b/worklenz-frontend/public/locales/en/admin-center/users.json new file mode 100644 index 00000000..7e462ef6 --- /dev/null +++ b/worklenz-frontend/public/locales/en/admin-center/users.json @@ -0,0 +1,9 @@ +{ + "title": "Users", + "subTitle": "users", + "placeholder": "Search by name", + "user": "User", + "email": "Email", + "lastActivity": "Last Activity", + "refresh": "Refresh users" +} diff --git a/worklenz-frontend/public/locales/en/all-project-list.json b/worklenz-frontend/public/locales/en/all-project-list.json new file mode 100644 index 00000000..308c414e --- /dev/null +++ b/worklenz-frontend/public/locales/en/all-project-list.json @@ -0,0 +1,23 @@ +{ + "name": "Name", + "client": "Client", + "category": "Category", + "status": "Status", + "tasksProgress": "Tasks Progress", + "updated_at": "Last Updated", + "members": "Members", + "setting": "Settings", + "projects": "Projects", + "refreshProjects": "Refresh projects", + "all": "All", + "favorites": "Favorites", + "archived": "Archived", + "placeholder": "Search by name", + "archive": "Archive", + "unarchive": "Unarchive", + "archiveConfirm": "Are you sure you want to archive this project?", + "unarchiveConfirm": "Are you sure you want to unarchive this project?", + "clickToFilter": "Click to filter by", + "noProjects": "No projects found", + "addToFavourites": "Add to favourites" +} diff --git a/worklenz-frontend/public/locales/en/auth/auth-common.json b/worklenz-frontend/public/locales/en/auth/auth-common.json new file mode 100644 index 00000000..94803a12 --- /dev/null +++ b/worklenz-frontend/public/locales/en/auth/auth-common.json @@ -0,0 +1,5 @@ +{ + "loggingOut": "Logging out...", + "authenticating": "Authenticating...", + "gettingThingsReady": "Getting things ready for you..." +} diff --git a/worklenz-frontend/public/locales/en/auth/forgot-password.json b/worklenz-frontend/public/locales/en/auth/forgot-password.json new file mode 100644 index 00000000..3534c388 --- /dev/null +++ b/worklenz-frontend/public/locales/en/auth/forgot-password.json @@ -0,0 +1,12 @@ +{ + "headerDescription": "Reset your password", + "emailLabel": "Email", + "emailPlaceholder": "Enter your email", + "emailRequired": "Please enter your Email!", + "resetPasswordButton": "Reset Password", + "returnToLoginButton": "Return to Login", + "passwordResetSuccessMessage": "A password reset link has been sent to your email.", + "orText": "OR", + "successTitle": "Reset instruction sent!", + "successMessage": "Reset information has been sent to your email. Please check your email." +} diff --git a/worklenz-frontend/public/locales/en/auth/login.json b/worklenz-frontend/public/locales/en/auth/login.json new file mode 100644 index 00000000..b77e7fba --- /dev/null +++ b/worklenz-frontend/public/locales/en/auth/login.json @@ -0,0 +1,27 @@ +{ + "headerDescription": "Login to your account", + "emailLabel": "Email", + "emailPlaceholder": "Enter your email", + "emailRequired": "Please enter your Email!", + "passwordLabel": "Password", + "passwordPlaceholder": "Enter your password", + "passwordRequired": "Please enter your Password!", + "rememberMe": "Remember me", + "loginButton": "Log in", + "signupButton": "Sign up", + "forgotPasswordButton": "Forgot password?", + "signInWithGoogleButton": "Sign in with Google", + "dontHaveAccountText": "Don’t have an account?", + "orText": "OR", + "successMessage": "You have successfully logged in!", + "loginError": "Login failed", + "googleLoginError": "Google login failed", + "validationMessages": { + "email": "Please enter a valid email address", + "password": "Password must be at least 8 characters long" + }, + "errorMessages": { + "loginErrorTitle": "Login failed", + "loginErrorMessage": "Please check your email and password and try again" + } +} diff --git a/worklenz-frontend/public/locales/en/auth/signup.json b/worklenz-frontend/public/locales/en/auth/signup.json new file mode 100644 index 00000000..af4611ba --- /dev/null +++ b/worklenz-frontend/public/locales/en/auth/signup.json @@ -0,0 +1,29 @@ +{ + "headerDescription": "Sign up to get started", + "nameLabel": "Full Name", + "namePlaceholder": "Enter your full name", + "nameRequired": "Please enter your full name!", + "nameMinCharacterRequired": "Full name must be at least 4 characters!", + "emailLabel": "Email", + "emailPlaceholder": "Enter your email", + "emailRequired": "Please enter your Email!", + "passwordLabel": "Password", + "passwordPlaceholder": "Enter your password", + "passwordRequired": "Please enter your Password!", + "passwordMinCharacterRequired": "Password must be at least 8 characters!", + "passwordPatternRequired": "Password does not meet the requirements!", + "strongPasswordPlaceholder": "Enter a stronger password", + "passwordValidationAltText": "Password must include at least 8 characters with upper and lower case letters, a number, and a symbol.", + "signupSuccessMessage": "You have successfully signed up!", + "privacyPolicyLink": "Privacy Policy", + "termsOfUseLink": "Terms of Use", + "bySigningUpText": "By signing up, you agree to our", + "andText": "and", + "signupButton": "Sign up", + "signInWithGoogleButton": "Sign in with Google", + "alreadyHaveAccountText": "Already have an account?", + "loginButton": "Login", + "orText": "OR", + "reCAPTCHAVerificationError": "reCAPTCHA Verification Error", + "reCAPTCHAVerificationErrorMessage": "We were unable to verify your reCAPTCHA. Please try again." +} diff --git a/worklenz-frontend/public/locales/en/auth/verify-reset-email.json b/worklenz-frontend/public/locales/en/auth/verify-reset-email.json new file mode 100644 index 00000000..e685e193 --- /dev/null +++ b/worklenz-frontend/public/locales/en/auth/verify-reset-email.json @@ -0,0 +1,14 @@ +{ + "title": "Verify Reset Email", + "description": "Enter your new password", + "placeholder": "Enter your new password", + "confirmPasswordPlaceholder": "Confirm your new password", + "passwordHint": "Minimum of 8 characters, with upper and lowercase and a number and a symbol.", + "resetPasswordButton": "Reset password", + "orText": "Or", + "resendResetEmail": "Resend reset email", + "passwordRequired": "Please enter your new password", + "returnToLoginButton": "Return to Login", + "confirmPasswordRequired": "Please confirm your new password", + "passwordMismatch": "The two passwords do not match" +} diff --git a/worklenz-frontend/public/locales/en/common.json b/worklenz-frontend/public/locales/en/common.json new file mode 100644 index 00000000..815560be --- /dev/null +++ b/worklenz-frontend/public/locales/en/common.json @@ -0,0 +1,9 @@ +{ + "login-success": "Login successful!", + "login-failed": "Login failed. Please check your credentials and try again.", + "signup-success": "Signup successful! Welcome aboard.", + "signup-failed": "Signup failed. Please ensure all required fields are filled and try again.", + "reconnecting": "Disconnected from server.", + "connection-lost": "Failed to connect to server. Please check your internet connection.", + "connection-restored": "Connected to server successfully" +} diff --git a/worklenz-frontend/public/locales/en/create-first-project-form.json b/worklenz-frontend/public/locales/en/create-first-project-form.json new file mode 100644 index 00000000..337f4a10 --- /dev/null +++ b/worklenz-frontend/public/locales/en/create-first-project-form.json @@ -0,0 +1,13 @@ +{ + "formTitle": "Create your first project", + "inputLabel": "What project are you working on right now?", + "or": "or", + "templateButton": "Import from template", + "createFromTemplate": "Create from template", + "goBack": "Go Back", + "continue": "Continue", + "cancel": "Cancel", + "create": "Create", + "templateDrawerTitle": "Select from templates", + "createProject": "Create Project" +} diff --git a/worklenz-frontend/public/locales/en/create-first-tasks.json b/worklenz-frontend/public/locales/en/create-first-tasks.json new file mode 100644 index 00000000..1447f355 --- /dev/null +++ b/worklenz-frontend/public/locales/en/create-first-tasks.json @@ -0,0 +1,7 @@ +{ + "formTitle": "Create your first task.", + "inputLable": "Type a few tasks that you are going to do in", + "addAnother": "Add another", + "goBack": "Go back", + "continue": "Continue" +} diff --git a/worklenz-frontend/public/locales/en/home.json b/worklenz-frontend/public/locales/en/home.json new file mode 100644 index 00000000..ccf40936 --- /dev/null +++ b/worklenz-frontend/public/locales/en/home.json @@ -0,0 +1,46 @@ +{ + "todoList": { + "title": "To do list", + "refreshTasks": "Refresh tasks", + "addTask": "+ Add Task", + "noTasks": "No tasks", + "pressEnter": "Press", + "toCreate": "to create.", + "markAsDone": "Mark as done" + }, + "projects": { + "title": "Projects", + "refreshProjects": "Refresh projects", + "noRecentProjects": "You are currently not assigned to any project.", + "noFavouriteProjects": "No projects have been marked as favorites.", + "recent": "Recent", + "favourites": "Favourites" + }, + "tasks": { + "assignedToMe": "Assigned to me", + "assignedByMe": "Assigned by me", + "all": "All", + "today": "Today", + "upcoming": "Upcoming", + "overdue": "Overdue", + "noDueDate": "No due date", + "noTasks": "No tasks to show.", + "addTask": "+ Add task", + "name": "Name", + "project": "Project", + "status": "Status", + "dueDate": "Due Date", + "dueDatePlaceholder": "Set Due Date", + "tomorrow": "Tomorrow", + "nextWeek": "Next Week", + "nextMonth": "Next Month", + "projectRequired": "Please select a project", + "pressTabToSelectDueDateAndProject": "Press Tab to select a due date and a project", + "dueOn": "Tasks due on", + "taskRequired": "Please add a task", + "list": "List", + "calendar": "Calendar", + "tasks": "Tasks", + "refresh": "Refresh" + } +} diff --git a/worklenz-frontend/public/locales/en/invite-initial-team-members.json b/worklenz-frontend/public/locales/en/invite-initial-team-members.json new file mode 100644 index 00000000..09f23e87 --- /dev/null +++ b/worklenz-frontend/public/locales/en/invite-initial-team-members.json @@ -0,0 +1,8 @@ +{ + "formTitle": "Invite your team to work with", + "inputLable": "Invite with email", + "addAnother": "Add another", + "goBack": "Go back", + "continue": "Continue", + "skipForNow": "Skip for now" +} diff --git a/worklenz-frontend/public/locales/en/kanban-board.json b/worklenz-frontend/public/locales/en/kanban-board.json new file mode 100644 index 00000000..59b4b293 --- /dev/null +++ b/worklenz-frontend/public/locales/en/kanban-board.json @@ -0,0 +1,23 @@ +{ + "rename": "Rename", + "delete": "Delete", + "addTask": "Add Task", + "addSectionButton": "Add Section", + "changeCategory": "Change category", + + "deleteTooltip": "Delete", + "deleteConfirmationTitle": "Are you sure?", + "deleteConfirmationOk": "Yes", + "deleteConfirmationCancel": "Cancel", + + "dueDate": "Due date", + "cancel": "Cancel", + + "today": "Today", + "tomorrow": "Tomorrow", + "assignToMe": "Assign to me", + "archive": "Archive", + + "newTaskNamePlaceholder": "Write a task Name", + "newSubtaskNamePlaceholder": "Write a subtask Name" +} diff --git a/worklenz-frontend/public/locales/en/license-expired.json b/worklenz-frontend/public/locales/en/license-expired.json new file mode 100644 index 00000000..e4556064 --- /dev/null +++ b/worklenz-frontend/public/locales/en/license-expired.json @@ -0,0 +1,6 @@ +{ + "title": "Your Worklenz trial has expired!", + "subtitle": "Please upgrade now.", + "button": "Upgrade now", + "checking": "Checking subscription status..." +} diff --git a/worklenz-frontend/public/locales/en/navbar.json b/worklenz-frontend/public/locales/en/navbar.json new file mode 100644 index 00000000..e7e22cb3 --- /dev/null +++ b/worklenz-frontend/public/locales/en/navbar.json @@ -0,0 +1,31 @@ +{ + "logoAlt": "Worklenz Logo", + "home": "Home", + "projects": "Projects", + "schedule": "Schedule", + "reporting": "Reporting", + "clients": "Clients", + "teams": "Teams", + "labels": "Labels", + "jobTitles": "Job Titles", + "upgradePlan": "Upgrade Plan", + "upgradePlanTooltip": "Upgrade Plan", + "invite": "Invite", + "inviteTooltip": "Invite team members to join", + "switchTeamTooltip": "Switch team", + "help": "Help", + "notificationTooltip": "View notifications", + "profileTooltip": "View profile", + "adminCenter": "Admin Center", + "settings": "Settings", + "logOut": "Log Out", + "notificationsDrawer": { + "read": "Read notifications", + "unread": "Unread notifications", + "markAsRead": "Mark as read", + "readAndJoin": "Read & Join", + "accept": "Accept", + "acceptAndJoin": "Accept & Join", + "noNotifications": "No notifications" + } +} diff --git a/worklenz-frontend/public/locales/en/organization-name-form.json b/worklenz-frontend/public/locales/en/organization-name-form.json new file mode 100644 index 00000000..26ffa973 --- /dev/null +++ b/worklenz-frontend/public/locales/en/organization-name-form.json @@ -0,0 +1,5 @@ +{ + "nameYourOrganization": "Name your organization.", + "worklenzAccountTitle": "Pick a name for your Worklenz account.", + "continue": "Continue" +} diff --git a/worklenz-frontend/public/locales/en/phases-drawer.json b/worklenz-frontend/public/locales/en/phases-drawer.json new file mode 100644 index 00000000..51ac7899 --- /dev/null +++ b/worklenz-frontend/public/locales/en/phases-drawer.json @@ -0,0 +1,7 @@ +{ + "configurePhases": "Configure Phases", + "phaseLabel": "Phase Label", + "enterPhaseName": "Enter a name for phase label", + "addOption": "Add Option", + "phaseOptions": "Phase Options:" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/en/project-drawer.json b/worklenz-frontend/public/locales/en/project-drawer.json new file mode 100644 index 00000000..d72138d6 --- /dev/null +++ b/worklenz-frontend/public/locales/en/project-drawer.json @@ -0,0 +1,42 @@ +{ + "createProject": "Create Project", + "editProject": "Edit Project", + "enterCategoryName": "Enter a name for the category", + "hitEnterToCreate": "Hit enter to create!", + "enterNotes": "Notes", + "youCanManageClientsUnderSettings": "You can manage clients under Settings", + "addCategory": "Add a category to the project", + "newCategory": "New Category", + "notes": "Notes", + "startDate": "Start Date", + "endDate": "End Date", + "estimateWorkingDays": "Estimate working days", + "estimateManDays": "Estimate man days", + "hoursPerDay": "Hours per day", + "create": "Create", + "update": "Update", + "delete": "Delete", + "typeToSearchClients": "Type to search clients", + "projectColor": "Project Color", + "pleaseEnterAName": "Please enter a name", + "enterProjectName": "Enter project name", + "name": "Name", + "status": "Status", + "health": "Health", + "category": "Category", + "projectManager": "Project Manager", + "client": "Client", + "deleteConfirmation": "Are you sure you want to delete?", + "deleteConfirmationDescription": "This will remove all associated data and cannot be undone.", + "yes": "Yes", + "no": "No", + "createdAt": "Created", + "updatedAt": "Updated", + "by": "by", + "add": "Add", + "asClient": "as client", + "createClient": "Create client", + "searchInputPlaceholder": "Search by name or email", + "hoursPerDayValidationMessage": "Hours per day must be a number between 1 and 24", + "noPermission": "No permission" +} diff --git a/worklenz-frontend/public/locales/en/project-view-files.json b/worklenz-frontend/public/locales/en/project-view-files.json new file mode 100644 index 00000000..12672620 --- /dev/null +++ b/worklenz-frontend/public/locales/en/project-view-files.json @@ -0,0 +1,14 @@ +{ + "nameColumn": "Name", + "attachedTaskColumn": "Attached Task", + "sizeColumn": "Size", + "uploadedByColumn": "Uploaded By", + "uploadedAtColumn": "Uploaded At", + "fileIconAlt": "File icon", + "titleDescriptionText": "All attachments to tasks in this project will appear here.", + "deleteConfirmationTitle": "Are you sure?", + "deleteConfirmationOk": "Yes", + "deleteConfirmationCancel": "Cancel", + "segmentedTooltip": "Coming soon! Switch between list view and thumbnail view.", + "emptyText": "There are no attachments in the project." +} diff --git a/worklenz-frontend/public/locales/en/project-view-insights.json b/worklenz-frontend/public/locales/en/project-view-insights.json new file mode 100644 index 00000000..1b174a85 --- /dev/null +++ b/worklenz-frontend/public/locales/en/project-view-insights.json @@ -0,0 +1,41 @@ +{ + "overview": { + "title": "Overview", + "statusOverview": "Status Overview", + "priorityOverview": "Priority Overview", + "lastUpdatedTasks": "Last Updated Tasks" + }, + "members": { + "title": "Members", + "tooltip": "Members", + "tasksByMembers": "Tasks by members", + "tasksByMembersTooltip": "Tasks by members", + "name": "Name", + "taskCount": "Task Count", + "contribution": "Contribution", + "completed": "Completed", + "incomplete": "Incomplete", + "overdue": "Overdue", + "progress": "Progress" + }, + "tasks": { + "overdueTasks": "Overdue Tasks", + "overLoggedTasks": "Over logged Tasks", + "tasksCompletedEarly": "Tasks completed early", + "tasksCompletedLate": "Tasks completed late", + "overLoggedTasksTooltip": "Tasks that has time logged past their estimated time", + "overdueTasksTooltip": "Tasks that are past their due date" + }, + "common": { + "seeAll": "See all", + "totalLoggedHours": "Total logged hours", + "totalEstimation": "Total estimation", + "completedTasks": "Completed tasks", + "incompleteTasks": "Incomplete tasks", + "overdueTasks": "Overdue tasks", + "overdueTasksTooltip": "Tasks that are past their due date", + "totalLoggedHoursTooltip": "Task estimation and logged time for tasks.", + "includeArchivedTasks": "Include Archived Tasks", + "export": "Export" + } +} diff --git a/worklenz-frontend/public/locales/en/project-view-members.json b/worklenz-frontend/public/locales/en/project-view-members.json new file mode 100644 index 00000000..6ed8ddf0 --- /dev/null +++ b/worklenz-frontend/public/locales/en/project-view-members.json @@ -0,0 +1,17 @@ +{ + "nameColumn": "Name", + "jobTitleColumn": "Job Title", + "emailColumn": "Email", + "tasksColumn": "Tasks", + "taskProgressColumn": "Task Progress", + "accessColumn": "Access", + "fileIconAlt": "File icon", + "deleteConfirmationTitle": "Are you sure?", + "deleteConfirmationOk": "Yes", + "deleteConfirmationCancel": "Cancel", + "refreshButtonTooltip": "Refresh members", + "deleteButtonTooltip": "Remove from project", + "memberCount": "Member", + "membersCountPlural": "Members", + "emptyText": "There are no attachments in the project." +} diff --git a/worklenz-frontend/public/locales/en/project-view-updates.json b/worklenz-frontend/public/locales/en/project-view-updates.json new file mode 100644 index 00000000..d7140ad8 --- /dev/null +++ b/worklenz-frontend/public/locales/en/project-view-updates.json @@ -0,0 +1,6 @@ +{ + "inputPlaceholder": "Add a comment..", + "addButton": "Add", + "cancelButton": "Cancel", + "deleteButton": "Delete" +} diff --git a/worklenz-frontend/public/locales/en/project-view/import-task-templates.json b/worklenz-frontend/public/locales/en/project-view/import-task-templates.json new file mode 100644 index 00000000..6057a524 --- /dev/null +++ b/worklenz-frontend/public/locales/en/project-view/import-task-templates.json @@ -0,0 +1,11 @@ +{ + "importTaskTemplate": "Import Task Template", + "templateName": "Template Name", + "templateDescription": "Template Description", + "selectedTasks": "Selected Tasks", + "tasks": "Tasks", + "templates": "Templates", + "remove": "Remove", + "cancel": "Cancel", + "import": "Import" +} diff --git a/worklenz-frontend/public/locales/en/project-view/project-member-drawer.json b/worklenz-frontend/public/locales/en/project-view/project-member-drawer.json new file mode 100644 index 00000000..4b54e2b5 --- /dev/null +++ b/worklenz-frontend/public/locales/en/project-view/project-member-drawer.json @@ -0,0 +1,8 @@ +{ + "title": "Project Members", + "searchLabel": "Add members by adding their name or email", + "searchPlaceholder": "Type name or email", + "inviteAsAMember": "Invite as a member", + "inviteNewMemberByEmail": "Invite new member by email" + +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/en/project-view/project-view-header.json b/worklenz-frontend/public/locales/en/project-view/project-view-header.json new file mode 100644 index 00000000..8100e068 --- /dev/null +++ b/worklenz-frontend/public/locales/en/project-view/project-view-header.json @@ -0,0 +1,13 @@ +{ + "importTasks": "Import tasks", + "createTask": "Create task", + "settings": "Settings", + "subscribe": "Subscribe", + "unsubscribe": "Unsubscribe", + "deleteProject": "Delete project", + "startDate": "Start date", + "endDate": "End date", + "projectSettings": "Project settings", + "projectSummary": "Project summary", + "receiveProjectSummary": "Receive a project summary every evening." +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/en/project-view/save-as-template.json b/worklenz-frontend/public/locales/en/project-view/save-as-template.json new file mode 100644 index 00000000..2b3e7564 --- /dev/null +++ b/worklenz-frontend/public/locales/en/project-view/save-as-template.json @@ -0,0 +1,27 @@ +{ + "title": "Save as Template", + "templateName": "Template Name", + "includes": "What should be included in the template from the project ?", + "includesOptions": { + "statuses": "Statuses", + "phases": "Phases", + "labels": "Labels" + }, + "taskIncludes": "What should be included in the template from the tasks ?", + "taskIncludesOptions": { + "statuses": "Statuses", + "phases": "Phases", + "labels": "Labels", + "name": "Name", + "priority": "Priority", + "status": "Status", + "phase": "Phase", + "label": "Label", + "timeEstimate": "Time Estimate", + "description": "Description", + "subTasks": "Sub Tasks" + }, + "cancel": "Cancel", + "save": "Save", + "templateNamePlaceholder": "Enter template name" +} diff --git a/worklenz-frontend/public/locales/en/reporting-members-drawer.json b/worklenz-frontend/public/locales/en/reporting-members-drawer.json new file mode 100644 index 00000000..cca01177 --- /dev/null +++ b/worklenz-frontend/public/locales/en/reporting-members-drawer.json @@ -0,0 +1,90 @@ +{ + "exportButton": "Export", + "timeLogsButton": "TimeLogs", + "activityLogsButton": "Activity Logs", + "tasksButton": "Tasks", + "searchByNameInputPlaceholder": "Search by name", + + "overviewTab": "Overview", + "timeLogsTab": "Time Logs", + "activityLogsTab": "Activity Logs", + "tasksTab": "Tasks", + + "projectsText": "Projects", + "totalTasksText": "Total Tasks", + "assignedTasksText": "Assigned Tasks", + "completedTasksText": "Completed Tasks", + "ongoingTasksText": "Ongoing Tasks", + "overdueTasksText": "Overdue Tasks", + "loggedHoursText": "Logged Hours", + + "tasksText": "Tasks", + "allText": "All", + + "tasksByProjectsText": "Tasks By Projects", + "tasksByStatusText": "Tasks By Status", + "tasksByPriorityText": "Tasks By Priority", + + "todoText": "To Do", + "doingText": "Doing", + "doneText": "Done", + "lowText": "Low", + "mediumText": "Medium", + "highText": "High", + + "billableButton": "Billable", + "billableText": "Billable", + "nonBillableText": "Non Billable", + + "timeLogsEmptyPlaceholder": "No time logs to show", + "loggedText": "Logged", + "forText": "for", + "inText": "in", + "updatedText": "Updated", + "fromText": "From", + "toText": "to", + "withinText": "within", + + "activityLogsEmptyPlaceholder": "No activity logs to show", + + "filterByText": "Filter by:", + "selectProjectPlaceholder": "Select Project", + + "taskColumn": "Task", + "nameColumn": "Name", + "projectColumn": "Project", + "statusColumn": "Status", + "priorityColumn": "Priority", + "dueDateColumn": "Due Date", + "completedDateColumn": "Completed Date", + "estimatedTimeColumn": "Estimated Time", + "loggedTimeColumn": "Logged Time", + "overloggedTimeColumn": "Overlogged Time", + "daysLeftColumn": "Days Left/Overdue", + "startDateColumn": "Start Date", + "endDateColumn": "End Date", + "actualTimeColumn": "Actual Time", + "projectHealthColumn": "Project Health", + "categoryColumn": "Category", + "projectManagerColumn": "Project Manager", + + "tasksStatsOverviewDrawerTitle": "'s Tasks", + "projectsStatsOverviewDrawerTitle": "'s Projects", + + "cancelledText": "Cancelled", + "blockedText": "Blocked", + "onHoldText": "On Hold", + "proposedText": "Proposed", + "inPlanningText": "In Planning", + "inProgressText": "In Progress", + "completedText": "Completed", + "continuousText": "Continuous", + + "daysLeftText": "days left", + "daysOverdueText": "days overdue", + + "notSetText": "NotSet", + "needsAttentionText": "Needs Attention", + "atRiskText": "At Risk", + "goodText": "Good" +} diff --git a/worklenz-frontend/public/locales/en/reporting-members.json b/worklenz-frontend/public/locales/en/reporting-members.json new file mode 100644 index 00000000..a8035dcd --- /dev/null +++ b/worklenz-frontend/public/locales/en/reporting-members.json @@ -0,0 +1,35 @@ +{ + "yesterdayText": "Yesterday", + "lastSevenDaysText": "Last 7 Days", + "lastWeekText": "Last Week", + "lastThirtyDaysText": "Last 30 Days", + "lastMonthText": "Last Month", + "lastThreeMonthsText": "Last 3 Months", + "allTimeText": "All Time", + "customRangeText": "Custom range", + "startDateInputPlaceholder": "Start date", + "EndDateInputPlaceholder": "End date", + "filterButton": "Filter", + + "membersTitle": "Members", + "includeArchivedButton": "Include Archived Projects", + "exportButton": "Export", + "excelButton": "Excel", + "searchByNameInputPlaceholder": "Search by name", + + "memberColumn": "Member", + "tasksProgressColumn": "Tasks Progress", + "tasksAssignedColumn": "Tasks Assigned", + "completedTasksColumn": "Completed Tasks", + "overdueTasksColumn": "Overdue Tasks", + "ongoingTasksColumn": "Ongoing Tasks", + + "tasksAssignedColumnTooltip": "Tasks assigned on selected date range", + "overdueTasksColumnTooltip": "Tasks overdue for end of the selected date range", + "completedTasksColumnTooltip": "Tasks completed on selected date range", + "ongoingTasksColumnTooltip": "Started tasks not completed yet", + + "todoText": "To Do", + "doingText": "Doing", + "doneText": "Done" +} diff --git a/worklenz-frontend/public/locales/en/reporting-overview-drawer.json b/worklenz-frontend/public/locales/en/reporting-overview-drawer.json new file mode 100644 index 00000000..84fab1b8 --- /dev/null +++ b/worklenz-frontend/public/locales/en/reporting-overview-drawer.json @@ -0,0 +1,39 @@ +{ + "exportButton": "Export", + "projectsButton": "Projects", + "membersButton": "Members", + "searchByNameInputPlaceholder": "Search by name", + + "overviewTab": "Overview", + "projectsTab": "Projects", + "membersTab": "Members", + + "projectsByStatusText": "Projects By Status", + "projectsByCategoryText": "Projects By Category", + "projectsByHealthText": "Projects By Health", + + "projectsText": "Projects", + "allText": "All", + + "cancelledText": "Cancelled", + "blockedText": "Blocked", + "onHoldText": "On Hold", + "proposedText": "Proposed", + "inPlanningText": "In Planning", + "inProgressText": "In Progress", + "completedText": "Completed", + "continuousText": "Continuous", + + "notSetText": "Not Set", + "needsAttentionText": "Needs Attention", + "atRiskText": "At Risk", + "goodText": "Good", + + "nameColumn": "Name", + "emailColumn": "Email", + "projectsColumn": "Projects", + "tasksColumn": "Tasks", + "overdueTasksColumn": "Overdue Tasks", + "completedTasksColumn": "Completed Tasks", + "ongoingTasksColumn": "Ongoing Tasks" +} diff --git a/worklenz-frontend/public/locales/en/reporting-overview.json b/worklenz-frontend/public/locales/en/reporting-overview.json new file mode 100644 index 00000000..73faffd3 --- /dev/null +++ b/worklenz-frontend/public/locales/en/reporting-overview.json @@ -0,0 +1,25 @@ +{ + "overviewTitle": "Overview", + "includeArchivedButton": "Include Archived Projects", + + "teamCount": "Team", + "teamCountPlural": "Teams", + "projectCount": "Project", + "projectCountPlural": "Projects", + "memberCount": "Member", + "memberCountPlural": "Members", + "activeProjectCount": "Active Project", + "activeProjectCountPlural": "Active Projects", + "overdueProjectCount": "Overdue Project", + "overdueProjectCountPlural": "Overdue Projects", + "unassignedMemberCount": "Unassigned Member", + "unassignedMemberCountPlural": "Unassigned Members", + "memberWithOverdueTaskCount": "Member With Overdue Task", + "memberWithOverdueTaskCountPlural": "Member With Overdue Tasks", + + "teamsText": "Teams", + + "nameColumn": "Name", + "projectsColumn": "Projects", + "membersColumn": "Members" +} diff --git a/worklenz-frontend/public/locales/en/reporting-projects-drawer.json b/worklenz-frontend/public/locales/en/reporting-projects-drawer.json new file mode 100644 index 00000000..243bb411 --- /dev/null +++ b/worklenz-frontend/public/locales/en/reporting-projects-drawer.json @@ -0,0 +1,59 @@ +{ + "exportButton": "Export", + "membersButton": "Members", + "tasksButton": "Tasks", + "searchByNameInputPlaceholder": "Search by name", + + "overviewTab": "Overview", + "membersTab": "Members", + "tasksTab": "Tasks", + + "completedTasksText": "Completed Tasks", + "incompleteTasksText": "Incomplete Tasks", + "overdueTasksText": "Overdue Tasks", + "allocatedHoursText": "Allocated Hours", + "loggedHoursText": "Logged Hours", + + "tasksText": "Tasks", + "allText": "All", + + "tasksByStatusText": "Tasks By Status", + "tasksByPriorityText": "Tasks By Priority", + "tasksByDueDateText": "Tasks By Due Date", + + "todoText": "To Do", + "doingText": "Doing", + "doneText": "Done", + "lowText": "Low", + "mediumText": "Medium", + "highText": "High", + "completedText": "Completed", + "upcomingText": "Upcoming", + "overdueText": "Overdue", + "noDueDateText": "No Due Date", + + "nameColumn": "Name", + "tasksCountColumn": "Tasks Count", + "completedTasksColumn": "Completed Tasks", + "incompleteTasksColumn": "Incomplete Tasks", + "overdueTasksColumn": "Overdue Tasks", + "contributionColumn": "Contribution", + "progressColumn": "Progress", + "loggedTimeColumn": "Logged Time", + "taskColumn": "Task", + "projectColumn": "Project", + "statusColumn": "Status", + "priorityColumn": "Priority", + "phaseColumn": "Phase", + "dueDateColumn": "Due Date", + "completedDateColumn": "Completed Date", + "estimatedTimeColumn": "Estimated Time", + "overloggedTimeColumn": "Overlogged Time", + "completedOnColumn": "Completed On", + "daysOverdueColumn": "Days overdue", + + "groupByText": "Group By:", + "statusText": "Status", + "priorityText": "Priority", + "phaseText": "Phase" +} diff --git a/worklenz-frontend/public/locales/en/reporting-projects-filters.json b/worklenz-frontend/public/locales/en/reporting-projects-filters.json new file mode 100644 index 00000000..7d9afccd --- /dev/null +++ b/worklenz-frontend/public/locales/en/reporting-projects-filters.json @@ -0,0 +1,35 @@ +{ + "searchByNamePlaceholder": "Search by name", + "searchByCategoryPlaceholder": "Search by category", + + "statusText": "Status", + "healthText": "Health", + "categoryText": "Category", + "projectManagerText": "Project Manager", + "showFieldsText": "Show fields", + + "cancelledText": "Cancelled", + "blockedText": "Blocked", + "onHoldText": "On Hold", + "proposedText": "Proposed", + "inPlanningText": "In Planning", + "inProgressText": "In Progress", + "completedText": "Completed", + "continuousText": "Continuous", + + "notSetText": "NotSet", + "needsAttentionText": "Needs Attention", + "atRiskText": "At Risk", + "goodText": "Good", + + "nameText": "Project", + "estimatedVsActualText": "Estimated Vs Actual", + "tasksProgressText": "Tasks Progress", + "lastActivityText": "Last Activity", + "datesText": "Start/End Dates", + "daysLeftText": "Days Left/Overdue", + "projectHealthText": "Project Health", + "projectUpdateText": "Project Update", + "clientText": "Client", + "teamText": "Team" +} diff --git a/worklenz-frontend/public/locales/en/reporting-projects.json b/worklenz-frontend/public/locales/en/reporting-projects.json new file mode 100644 index 00000000..8dd472c4 --- /dev/null +++ b/worklenz-frontend/public/locales/en/reporting-projects.json @@ -0,0 +1,52 @@ +{ + "projectCount": "Project", + "projectCountPlural": "Projects", + "includeArchivedButton": "Include Archived Projects", + "exportButton": "Export", + "excelButton": "Excel", + + "projectColumn": "Project", + "estimatedVsActualColumn": "Estimated Vs Actual", + "tasksProgressColumn": "Tasks Progress", + "lastActivityColumn": "Last Activity", + "statusColumn": "Status", + "datesColumn": "Start/End Dates", + "daysLeftColumn": "Days Left/Overdue", + "projectHealthColumn": "Project Health", + "categoryColumn": "Category", + "projectUpdateColumn": "Project Update", + "clientColumn": "Client", + "teamColumn": "Team", + "projectManagerColumn": "Project Manager", + + "openButton": "Open", + + "estimatedText": "Estimated", + "actualText": "Actual", + + "todoText": "To Do", + "doingText": "Doing", + "doneText": "Done", + + "cancelledText": "Cancelled", + "blockedText": "Blocked", + "onHoldText": "On Hold", + "proposedText": "Proposed", + "inPlanningText": "In Planning", + "inProgressText": "In Progress", + "completedText": "Completed", + "continuousText": "Continuous", + + "daysLeftText": "days left", + "dayLeftText": "day left", + "daysOverdueText": "days overdue", + + "notSetText": "Not Set", + "needsAttentionText": "Needs Attention", + "atRiskText": "At Risk", + "goodText": "Good", + + "setCategoryText": "Set Category", + "searchByNameInputPlaceholder": "Search by name", + "todayText": "Today" +} diff --git a/worklenz-frontend/public/locales/en/reporting-sidebar.json b/worklenz-frontend/public/locales/en/reporting-sidebar.json new file mode 100644 index 00000000..8e82224d --- /dev/null +++ b/worklenz-frontend/public/locales/en/reporting-sidebar.json @@ -0,0 +1,8 @@ +{ + "overview": "Overview", + "projects": "Projects", + "members": "Members", + "timeReports": "Time Reports", + "estimateVsActual": "Estimate Vs Actual", + "currentOrganizationTooltip": "Current organization" +} diff --git a/worklenz-frontend/public/locales/en/schedule.json b/worklenz-frontend/public/locales/en/schedule.json new file mode 100644 index 00000000..9e30c04b --- /dev/null +++ b/worklenz-frontend/public/locales/en/schedule.json @@ -0,0 +1,39 @@ +{ + "today": "Today", + "week": "Week", + "month": "Month", + + "settings": "Settings", + "workingDays": "Working days", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "workingHours": "Working hours", + "hours": "hours", + "saveButton": "Save", + + "totalAllocation": "Total Allocation", + "timeLogged": "Time Logged", + "remainingTime": "Remaining Time", + "total": "Total", + "perDay": "Per Day", + "tasks": "tasks", + "startDate": "Start Date", + "endDate": "End Date", + + "hoursPerDay": "Hours Per Day", + "totalHours": "Total Hours", + "deleteButton": "Delete", + "cancelButton": "Cancel", + + "tabTitle": "Task without Start & End dates", + + "allocatedTime": "Allocated time", + "totalLogged": "Total Logged", + "loggedBillable": "Logged Billable", + "loggedNonBillable": "Logged Non Billable" +} diff --git a/worklenz-frontend/public/locales/en/settings/categories.json b/worklenz-frontend/public/locales/en/settings/categories.json new file mode 100644 index 00000000..716cb5c3 --- /dev/null +++ b/worklenz-frontend/public/locales/en/settings/categories.json @@ -0,0 +1,10 @@ +{ + "categoryColumn": "Category", + "deleteConfirmationTitle": "Are you sure?", + "deleteConfirmationOk": "Yes", + "deleteConfirmationCancel": "Cancel", + "associatedTaskColumn": "Associated Projects", + "searchPlaceholder": "Search by name", + "emptyText": "Categories can be created while updating or creating projects.", + "colorChangeTooltip": "Click to change color" +} diff --git a/worklenz-frontend/public/locales/en/settings/change-password.json b/worklenz-frontend/public/locales/en/settings/change-password.json new file mode 100644 index 00000000..ad39088b --- /dev/null +++ b/worklenz-frontend/public/locales/en/settings/change-password.json @@ -0,0 +1,15 @@ +{ + "title": "Change Password", + "currentPassword": "Current Password", + "newPassword": "New Password", + "confirmPassword": "Confirm Password", + "currentPasswordPlaceholder": "Enter your current password", + "newPasswordPlaceholder": "New Password", + "confirmPasswordPlaceholder": "Confirm Password", + "currentPasswordRequired": "Please input your current password!", + "newPasswordRequired": "Please input your new password!", + "passwordValidationError": "Password must be at least 8 characters with an uppercase letter, a number, and a symbol.", + "passwordMismatch": "Passwords do not match!", + "passwordRequirements": "New password should be a minimum of 8 characters, with an uppercase letter, a number, and a symbol.", + "updateButton": "Update Password" +} diff --git a/worklenz-frontend/public/locales/en/settings/clients.json b/worklenz-frontend/public/locales/en/settings/clients.json new file mode 100644 index 00000000..b7fa4dac --- /dev/null +++ b/worklenz-frontend/public/locales/en/settings/clients.json @@ -0,0 +1,22 @@ +{ + "nameColumn": "Name", + "projectColumn": "Project", + "noProjectsAvailable": "No projects available", + "deleteConfirmationTitle": "Are you sure?", + "deleteConfirmationOk": "Yes", + "deleteConfirmationCancel": "Cancel", + "searchPlaceholder": "Search by name", + "createClient": "Create Client", + "pinTooltip": "Click to pin this into the main menu", + "createClientDrawerTitle": "Create Client", + "updateClientDrawerTitle": "Update Client", + "nameLabel": "Name", + "namePlaceholder": "Name", + "nameRequiredError": "Please enter a Name", + "createButton": "Create", + "updateButton": "Update", + "createClientSuccessMessage": "Create client success!", + "createClientErrorMessage": "Create client failed!", + "updateClientSuccessMessage": "Update client success!", + "updateClientErrorMessage": "Update client failed!" +} diff --git a/worklenz-frontend/public/locales/en/settings/job-titles.json b/worklenz-frontend/public/locales/en/settings/job-titles.json new file mode 100644 index 00000000..9ec54f98 --- /dev/null +++ b/worklenz-frontend/public/locales/en/settings/job-titles.json @@ -0,0 +1,20 @@ +{ + "nameColumn": "Name", + "deleteConfirmationTitle": "Are you sure?", + "deleteConfirmationOk": "Yes", + "deleteConfirmationCancel": "Cancel", + "searchPlaceholder": "Search by name", + "createJobTitleButton": "Create Job Title", + "pinTooltip": "Click to pin this into the main menu", + "createJobTitleDrawerTitle": "Create Job Title", + "updateJobTitleDrawerTitle": "Update Job Title", + "nameLabel": "Name", + "namePlaceholder": "Name", + "nameRequiredError": "Please enter a Name", + "createButton": "Create", + "updateButton": "Update", + "createJobTitleSuccessMessage": "Create job title success!", + "createJobTitleErrorMessage": "Create job title failed!", + "updateJobTitleSuccessMessage": "Update job title success!", + "updateJobTitleErrorMessage": "Update job title failed!" +} diff --git a/worklenz-frontend/public/locales/en/settings/labels.json b/worklenz-frontend/public/locales/en/settings/labels.json new file mode 100644 index 00000000..5c3d2479 --- /dev/null +++ b/worklenz-frontend/public/locales/en/settings/labels.json @@ -0,0 +1,11 @@ +{ + "labelColumn": "Label", + "deleteConfirmationTitle": "Are you sure?", + "deleteConfirmationOk": "Yes", + "deleteConfirmationCancel": "Cancel", + "associatedTaskColumn": "Associated Task Count", + "searchPlaceholder": "Search by name", + "emptyText": "Labels can be created while updating or creating tasks.", + "pinTooltip": "Click to pin this into the main menu", + "colorChangeTooltip": "Click to change color" +} diff --git a/worklenz-frontend/public/locales/en/settings/language.json b/worklenz-frontend/public/locales/en/settings/language.json new file mode 100644 index 00000000..331cb7df --- /dev/null +++ b/worklenz-frontend/public/locales/en/settings/language.json @@ -0,0 +1,7 @@ +{ + "language": "Language", + "language_required": "Language is required", + "time_zone": "Time zone", + "time_zone_required": "Time zone is required", + "save_changes": "Save Changes" +} diff --git a/worklenz-frontend/public/locales/en/settings/notifications.json b/worklenz-frontend/public/locales/en/settings/notifications.json new file mode 100644 index 00000000..7cc1eb47 --- /dev/null +++ b/worklenz-frontend/public/locales/en/settings/notifications.json @@ -0,0 +1,11 @@ +{ + "title": "Notifications Settings", + "emailTitle": "Send me email notifications", + "emailDescription": "This includes new task assignments", + "dailyDigestTitle": "Send me a daily digest", + "dailyDigestDescription": "Every evening, you will receive a summary of recent activity in tasks.", + "popupTitle": "Pop up notifications on my computer when Worklenz is open", + "popupDescription": "Pop up notifications can be disabled by your browser. Change your browser settings to allow them.", + "unreadItemsTitle": "Show the number of unread items", + "unreadItemsDescription": "You'll see counts for each notification." +} diff --git a/worklenz-frontend/public/locales/en/settings/profile.json b/worklenz-frontend/public/locales/en/settings/profile.json new file mode 100644 index 00000000..5dd49095 --- /dev/null +++ b/worklenz-frontend/public/locales/en/settings/profile.json @@ -0,0 +1,13 @@ +{ + "uploadError": "You can only upload JPG/PNG file!", + "uploadSizeError": "Image must be smaller than 2MB!", + "upload": "Upload", + "nameLabel": "Name", + "nameRequiredError": "Name is required", + "emailLabel": "Email", + "emailRequiredError": "Email is required", + "saveChanges": "Save Changes", + "profileJoinedText": "Joined a month ago", + "profileLastUpdatedText": "Last updated a month ago", + "avatarTooltip": "Click to upload an avatar" +} diff --git a/worklenz-frontend/public/locales/en/settings/project-templates.json b/worklenz-frontend/public/locales/en/settings/project-templates.json new file mode 100644 index 00000000..802e017b --- /dev/null +++ b/worklenz-frontend/public/locales/en/settings/project-templates.json @@ -0,0 +1,8 @@ +{ + "nameColumn": "Name", + "editToolTip": "Edit", + "deleteToolTip": "Delete", + "confirmText": "Are you sure?", + "okText": "Yes", + "cancelText": "Cancel" +} diff --git a/worklenz-frontend/public/locales/en/settings/sidebar.json b/worklenz-frontend/public/locales/en/settings/sidebar.json new file mode 100644 index 00000000..41bc3e0f --- /dev/null +++ b/worklenz-frontend/public/locales/en/settings/sidebar.json @@ -0,0 +1,14 @@ +{ + "profile": "Profile", + "notifications": "Notifications", + "clients": "Clients", + "job-titles": "Job Titles", + "labels": "Labels", + "categories": "Categories", + "project-templates": "Project Templates", + "task-templates": "Task Templates", + "team-members": "Team Members", + "teams": "Teams", + "change-password": "Change Password", + "language-and-region": "Language and Region" +} diff --git a/worklenz-frontend/public/locales/en/settings/task-templates.json b/worklenz-frontend/public/locales/en/settings/task-templates.json new file mode 100644 index 00000000..b40bed2d --- /dev/null +++ b/worklenz-frontend/public/locales/en/settings/task-templates.json @@ -0,0 +1,9 @@ +{ + "nameColumn": "Name", + "createdColumn": "Created", + "editToolTip": "Edit", + "deleteToolTip": "Delete", + "confirmText": "Are you sure?", + "okText": "Yes", + "cancelText": "Cancel" +} diff --git a/worklenz-frontend/public/locales/en/settings/team-members.json b/worklenz-frontend/public/locales/en/settings/team-members.json new file mode 100644 index 00000000..35e77f6e --- /dev/null +++ b/worklenz-frontend/public/locales/en/settings/team-members.json @@ -0,0 +1,44 @@ +{ + "nameColumn": "Name", + "projectsColumn": "Projects", + "emailColumn": "Email", + "teamAccessColumn": "Team Access", + "memberCount": "Member", + "membersCountPlural": "Members", + "searchPlaceholder": "Search members by name", + "pinTooltip": "Refresh member list", + "addMemberButton": "Add New Member", + "editTooltip": "Edit member", + "deactivateTooltip": "Deactivate member", + "activateTooltip": "Activate member", + "deleteTooltip": "Delete member", + "confirmDeleteTitle": "Are you sure you want to delete this member?", + "confirmActivateTitle": "Are you sure you want to change this member's status?", + "okText": "Yes, proceed", + "cancelText": "No, cancel", + "deactivatedText": "(Currently deactivated)", + "pendingInvitationText": "(Invitation pending)", + "addMemberDrawerTitle": "Add New Team Member", + "updateMemberDrawerTitle": "Update Team Member", + "addMemberEmailHint": "Members will be added to the team regardless of invitation acceptance status", + "memberEmailLabel": "Email(s)", + "memberEmailPlaceholder": "Enter team member email address", + "memberEmailRequiredError": "Please enter a valid email address", + "jobTitleLabel": "Job Title", + "jobTitlePlaceholder": "Select or search job title (Optional)", + "memberAccessLabel": "Access Level", + "addToTeamButton": "Add Member to Team", + "updateButton": "Save Changes", + "resendInvitationButton": "Resend Invitation Email", + "invitationSentSuccessMessage": "Team invitation sent successfully!", + "createMemberSuccessMessage": "New team member added successfully!", + "createMemberErrorMessage": "Failed to add team member. Please try again.", + "updateMemberSuccessMessage": "Team member updated successfully!", + "updateMemberErrorMessage": "Failed to update team member. Please try again.", + "memberText": "Member", + "adminText": "Admin", + "ownerText": "Team Owner", + "addedText": "Added", + "updatedText": "Updated", + "noResultFound": "Type an email address and hit enter..." +} diff --git a/worklenz-frontend/public/locales/en/task-drawer/task-drawer-info-tab.json b/worklenz-frontend/public/locales/en/task-drawer/task-drawer-info-tab.json new file mode 100644 index 00000000..42ffdc83 --- /dev/null +++ b/worklenz-frontend/public/locales/en/task-drawer/task-drawer-info-tab.json @@ -0,0 +1,29 @@ +{ + "details": { + "task-key": "Task Key", + "phase": "Phase", + "assignees": "Assignees", + "due-date": "Due Date", + "time-estimation": "Time Estimation", + "priority": "Priority", + "labels": "Labels", + "billable": "Billable", + "notify": "Notify", + "when-done-notify": "When done, notify", + "start-date": "Start Date", + "end-date": "End Date", + "hide-start-date": "Hide Start Date", + "show-start-date": "Show Start Date", + "hours": "Hours", + "minutes": "Minutes" + }, + "description": { + "title": "Description", + "placeholder": "Add a more detailed description..." + }, + "subTasks": { + "title": "Sub Tasks", + "add-sub-task": "+ Add Sub Task", + "refresh-sub-tasks": "Refresh Sub Tasks" + } +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/en/task-drawer/task-drawer.json b/worklenz-frontend/public/locales/en/task-drawer/task-drawer.json new file mode 100644 index 00000000..003fa112 --- /dev/null +++ b/worklenz-frontend/public/locales/en/task-drawer/task-drawer.json @@ -0,0 +1,78 @@ +{ + "taskHeader": { + "taskNamePlaceholder": "Type your Task", + "deleteTask": "Delete Task" + }, + "taskInfoTab": { + "title": "Info", + "details": { + "title": "Details", + "task-key": "Task Key", + "phase": "Phase", + "assignees": "Assignees", + "due-date": "Due Date", + "time-estimation": "Time Estimation", + "priority": "Priority", + "labels": "Labels", + "billable": "Billable", + "notify": "Notify", + "when-done-notify": "When done, notify", + "start-date": "Start Date", + "end-date": "End Date", + "hide-start-date": "Hide Start Date", + "show-start-date": "Show Start Date", + "hours": "Hours", + "minutes": "Minutes" + }, + "labels": { + "labelInputPlaceholder": "Search or create", + "labelsSelectorInputTip": "Hit Enter to create" + }, + "description": { + "title": "Description", + "placeholder": "Add a more detailed description..." + }, + "subTasks": { + "title": "Sub Tasks", + "addSubTask": "+ Add Sub Task", + "addSubTaskInputPlaceholder": "Type your task and hit enter", + "refreshSubTasks": "Refresh Sub Tasks", + "edit": "Edit", + "delete": "Delete", + "confirmDeleteSubTask": "Are you sure you want to delete this subtask?", + "deleteSubTask": "Delete Sub Task" + }, + "dependencies": { + "title": "Dependencies", + "addDependency": "+ Add new dependency", + "blockedBy": "Blocked By", + "searchTask": "Type to search task", + "noTasksFound": "No tasks found", + "confirmDeleteDependency": "Are you sure you want to delete?" + }, + "attachments": { + "title": "Attachments", + "chooseOrDropFileToUpload": "Choose or drop file to upload", + "uploading": "Uploading..." + }, + "comments": { + "title": "Comments", + "addComment": "+ Add new comment", + "noComments": "No comments yet. Be the first to comment!", + "delete": "Delete", + "confirmDeleteComment": "Are you sure you want to delete this comment?" + }, + "searchInputPlaceholder": "Search by name", + "pendingInvitation": "Pending Invitation" + }, + "taskTimeLogTab": { + "title": "Time Log", + "addTimeLog": "Add new time log", + "totalLogged": "Total Logged", + "exportToExcel": "Export to Excel", + "noTimeLogsFound": "No time logs found" + }, + "taskActivityLogTab": { + "title": "Activity Log" + } +} diff --git a/worklenz-frontend/public/locales/en/task-list-filters.json b/worklenz-frontend/public/locales/en/task-list-filters.json new file mode 100644 index 00000000..72c79823 --- /dev/null +++ b/worklenz-frontend/public/locales/en/task-list-filters.json @@ -0,0 +1,59 @@ +{ + "searchButton": "Search", + "resetButton": "Reset", + "searchInputPlaceholder": "Search by name", + + "sortText": "Sort", + "statusText": "Status", + "phaseText": "Phase", + "memberText": "Members", + "assigneesText": "Assignees", + "priorityText": "Priority", + "labelsText": "Labels", + "membersText": "Members", + "groupByText": "Group by", + "showArchivedText": "Show archived", + "showFieldsText": "Show fields", + "keyText": "Key", + "taskText": "Task", + "descriptionText": "Description", + "phasesText": "Phases", + "listText": "List", + "progressText": "Progress", + "timeTrackingText": "Time Tracking", + "timetrackingText": "Time Tracking", + "estimationText": "Estimation", + "startDateText": "Start Date", + "startdateText": "Start Date", + "endDateText": "End Date", + "dueDateText": "Due Date", + "duedateText": "Due Date", + "completedDateText": "Completed Date", + "completeddateText": "Completed Date", + "createdDateText": "Created Date", + "createddateText": "Created Date", + "lastUpdatedText": "Last Updated", + "lastupdatedText": "Last Updated", + "reporterText": "Reporter", + "dueTimeText": "Due Time", + "duetimeText": "Due Time", + + "lowText": "Low", + "mediumText": "Medium", + "highText": "High", + + "createStatusButtonTooltip": "Status settings", + "configPhaseButtonTooltip": "Phase settings", + "noLabelsFound": "No labels found", + + "addStatusButton": "Add Status", + "addPhaseButton": "Add Phase", + + "createStatus": "Create Status", + "name": "Name", + "category": "Category", + "selectCategory": "Select a category", + "pleaseEnterAName": "Please enter a name", + "pleaseSelectACategory": "Please select a category", + "create": "Create" +} diff --git a/worklenz-frontend/public/locales/en/task-list-table.json b/worklenz-frontend/public/locales/en/task-list-table.json new file mode 100644 index 00000000..e05b7790 --- /dev/null +++ b/worklenz-frontend/public/locales/en/task-list-table.json @@ -0,0 +1,63 @@ +{ + "keyColumn": "Key", + "taskColumn": "Task", + "descriptionColumn": "Description", + "progressColumn": "Progress", + "membersColumn": "Members", + "assigneesColumn": "Assignees", + "labelsColumn": "Labels", + "phasesColumn": "Phases", + "phaseColumn": "Phase", + "statusColumn": "Status", + "priorityColumn": "Priority", + "timeTrackingColumn": "Time Tracking", + "timetrackingColumn": "Time Tracking", + "estimationColumn": "Estimation", + "startDateColumn": "Start Date", + "startdateColumn": "Start Date", + "dueDateColumn": "Due Date", + "duedateColumn": "Due Date", + "completedDateColumn": "Completed Date", + "completeddateColumn": "Completed Date", + "createdDateColumn": "Created Date", + "createddateColumn": "Created Date", + "lastUpdatedColumn": "Last Updated", + "lastupdatedColumn": "Last Updated", + "reporterColumn": "Reporter", + "dueTimeColumn": "Due Time", + "todoSelectorText": "To Do", + "doingSelectorText": "Doing", + "doneSelectorText": "Done", + + "lowSelectorText": "Low", + "mediumSelectorText": "Medium", + "highSelectorText": "High", + + "selectText": "Select", + "labelsSelectorInputTip": "Hit enter to create!", + + "addTaskText": "+ Add Task", + "addSubTaskText": "+ Add Sub Task", + "addTaskInputPlaceholder": "Type your task and hit enter", + + "openButton": "Open", + "okButton": "Ok", + + "noLabelsFound": "No labels found", + "searchInputPlaceholder": "Search or create", + "assigneeSelectorInviteButton": "Invite a new member by email", + "labelInputPlaceholder": "Search or create", + + "pendingInvitation": "Pending Invitation", + + "contextMenu": { + "assignToMe": "Assign to me", + "moveTo": "Move to", + "unarchive": "Unarchive", + "archive": "Archive", + "convertToSubTask": "Convert to Sub task", + "convertToTask": "Convert to Task", + "delete": "Delete", + "searchByNameInputPlaceholder": "Search by name" + } +} diff --git a/worklenz-frontend/public/locales/en/task-template-drawer.json b/worklenz-frontend/public/locales/en/task-template-drawer.json new file mode 100644 index 00000000..f2e23bee --- /dev/null +++ b/worklenz-frontend/public/locales/en/task-template-drawer.json @@ -0,0 +1,11 @@ +{ + "createTaskTemplate": "Create Task Template", + "editTaskTemplate": "Edit Task Template", + "cancelText": "Cancel", + "saveText": "Save", + "templateNameText": "Template Name", + "selectedTasks": "Selected Tasks", + "removeTask": "Remove", + "cancelButton": "Cancel", + "saveButton": "Save" +} diff --git a/worklenz-frontend/public/locales/en/tasks/task-table-bulk-actions.json b/worklenz-frontend/public/locales/en/tasks/task-table-bulk-actions.json new file mode 100644 index 00000000..2434aea4 --- /dev/null +++ b/worklenz-frontend/public/locales/en/tasks/task-table-bulk-actions.json @@ -0,0 +1,24 @@ +{ + "taskSelected": "task selected", + "tasksSelected": "tasks selected", + "changeStatus": "Change Status/ Prioriy/ Phases", + "changeLabel": "Change Label", + "assignToMe": "Assign to me", + "changeAssignees": "Change Assignees", + "archive": "Archive", + "unarchive": "Unarchive", + "delete": "Delete", + "moreOptions": "More options", + "deselectAll": "Deselect all", + "status": "Status", + "priority": "Priority", + "phase": "Phase", + "member": "Member", + "createTaskTemplate": "Create Task Template", + "apply": "Apply", + "createLabel": "+ Create Label", + "hitEnterToCreate": "Press Enter to create", + "pendingInvitation": "Pending Invitation", + "noMatchingLabels": "No matching labels", + "noLabels": "No labels" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/en/template-drawer.json b/worklenz-frontend/public/locales/en/template-drawer.json new file mode 100644 index 00000000..55364835 --- /dev/null +++ b/worklenz-frontend/public/locales/en/template-drawer.json @@ -0,0 +1,19 @@ +{ + "title": "Edit Task Template", + "cancelText": "Cancel", + "saveText": "Save", + "templateNameText": "Template Name", + "selectedTasks": "Selected Tasks", + "removeTask": "Remove", + "description": "Description", + "phase": "Phase", + "statuses": "Statuses", + "priorities": "Priorities", + "labels": "Labels", + "tasks": "Tasks", + "noTemplateSelected": "No template selected", + "noDescription": "No description", + "worklenzTemplates": "Worklenz Templates", + "yourTemplatesLibrary": "Your Library", + "searchTemplates": "Search Templates" +} diff --git a/worklenz-frontend/public/locales/en/templateDrawer.json b/worklenz-frontend/public/locales/en/templateDrawer.json new file mode 100644 index 00000000..70bf2b0c --- /dev/null +++ b/worklenz-frontend/public/locales/en/templateDrawer.json @@ -0,0 +1,23 @@ +{ + "bugTracking": "Bug Tracking", + "construction": "Construction", + "designCreative": "Design & Creative", + "education": "Education", + "finance": "Finance", + "hrRecruiting": "HR & Recruiting", + "informationTechnology": "Information Technology", + "legal": "Legal", + "manufacturing": "Manufacturing", + "marketing": "Marketing", + "nonprofit": "Nonprofit", + "personalUse": "Personal use", + "salesCRM": "Sales & CRM", + "serviceConsulting": "Service & Consulting", + "softwareDevelopment": "Software Development", + "description": "Description", + "phase": "Phase", + "statuses": "Statuses", + "priorities": "Priorities", + "labels": "Labels", + "tasks": "Tasks" +} diff --git a/worklenz-frontend/public/locales/en/time-report.json b/worklenz-frontend/public/locales/en/time-report.json new file mode 100644 index 00000000..b5da8dd2 --- /dev/null +++ b/worklenz-frontend/public/locales/en/time-report.json @@ -0,0 +1,44 @@ +{ + "includeArchivedProjects": "Include Archived Projects", + "export": "Export", + "timeSheet": "Time Sheet", + + "searchByName": "Search by name", + "selectAll": "Select All", + "teams": "Teams", + + "searchByProject": "Search by project name", + "projects": "Projects", + + "searchByCategory": "Search by category name", + "categories": "Categories", + + "billable": "Billable", + "nonBillable": "Non Billable", + + "total": "Total", + + "projectsTimeSheet": "Projects Time Sheet", + + "loggedTime": "Logged Time(hours)", + + "exportToExcel": "Export to Excel", + "logged": "logged", + "for": "for", + + "membersTimeSheet": "Members Time Sheet", + "member": "Member", + + "estimatedVsActual": "Estimated vs Actual", + "workingDays": "Working Days", + "manDays": "Man Days", + "days": "Days", + "estimatedDays": "Estimated Days", + "actualDays": "Actual Days", + + "noCategories": "No categories found", + "noCategory": "No Category", + "noProjects": "No projects found", + "noTeams": "No teams found", + "noData": "No data found" +} diff --git a/worklenz-frontend/public/locales/en/unauthorized.json b/worklenz-frontend/public/locales/en/unauthorized.json new file mode 100644 index 00000000..ad92a7c5 --- /dev/null +++ b/worklenz-frontend/public/locales/en/unauthorized.json @@ -0,0 +1,5 @@ +{ + "title": "Unauthorized!", + "subtitle": "You are not authorized to access this page", + "button": "Go to Home" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/es/404-page.json b/worklenz-frontend/public/locales/es/404-page.json new file mode 100644 index 00000000..9413ae9e --- /dev/null +++ b/worklenz-frontend/public/locales/es/404-page.json @@ -0,0 +1,4 @@ +{ + "doesNotExistText": "Lo sentimos, la página que visitaste no existe.", + "backHomeButton": "Volver al inicio" +} diff --git a/worklenz-frontend/public/locales/es/account-setup.json b/worklenz-frontend/public/locales/es/account-setup.json new file mode 100644 index 00000000..3f7b013e --- /dev/null +++ b/worklenz-frontend/public/locales/es/account-setup.json @@ -0,0 +1,32 @@ +{ + "continue": "Continuar", + + "setupYourAccount": "Configura tu cuenta.", + "organizationStepTitle": "Nombra tu organización", + "organizationStepLabel": "Elige un nombre para tu cuenta de Worklenz.", + + "projectStepTitle": "Crea tu primer proyecto", + "projectStepLabel": "¿En qué proyecto estás trabajando ahora?", + "projectStepPlaceholder": "e.g. Plan de Marketing", + + "step2Title": "Crea tus primeras tareas", + "step2InputLabel": "Escribe algunas tareas que vas a hacer en", + "step2AddAnother": "Agregar otro", + + "emailPlaceholder": "Dirección de correo electrónico", + "invalidEmail": "Por favor, introduce una dirección de correo electrónico válida", + "or": "o", + "templateButton": "Importar desde plantilla", + "goBack": "Volver", + "cancel": "Cancelar", + "create": "Crear", + "templateDrawerTitle": "Seleccionar de plantillas", + "step3InputLabel": "Invitar por correo electrónico", + "addAnother": "Agregar otro", + "skipForNow": "Omitir por ahora", + "formTitle": "Crea tu primera tarea.", + "step3Title": "Invita a tu equipo a trabajar", + + "maxMembers": " (Puedes invitar hasta 5 miembros)", + "maxTasks": " (Puedes crear hasta 5 tareas)" +} diff --git a/worklenz-frontend/public/locales/es/admin-center/current-bill.json b/worklenz-frontend/public/locales/es/admin-center/current-bill.json new file mode 100644 index 00000000..9278dfe1 --- /dev/null +++ b/worklenz-frontend/public/locales/es/admin-center/current-bill.json @@ -0,0 +1,105 @@ +{ + "title": "Facturación", + "currentBill": "Factura Actual", + "configuration": "Configuración", + "currentPlanDetails": "Detalles del Plan Actual", + "upgradePlan": "Actualizar Plan", + "cardBodyText01": "Prueba gratuita", + "cardBodyText02": "(Tu plan de prueba expira en 1 mes 19 días)", + "redeemCode": "Canjear Código", + "accountStorage": "Almacenamiento de la Cuenta", + "used": "Usado:", + "remaining": "Restante:", + "charges": "Cargos", + "tooltip": "Cargos para el ciclo de facturación actual", + "description": "Descripción", + "billingPeriod": "Periodo de Facturación", + "billStatus": "Estado de la Factura", + "perUserValue": "Valor por Usuario", + "users": "Usuarios", + "amount": "Cantidad", + "invoices": "Facturas", + "transactionId": "ID de Transacción", + "transactionDate": "Fecha de Transacción", + "paymentMethod": "Método de Pago", + "status": "Estado", + "ltdUsers": "Puedes agregar hasta {{ltd_users}} usuarios.", + + "drawerTitle": "Canjear Código", + "label": "Canjear Código", + "drawerPlaceholder": "Ingrese su código de canje", + "redeemSubmit": "Enviar", + + "modalTitle": "Seleccione el mejor plan para su equipo", + "seatLabel": "Número de asientos", + "freePlan": "Plan Gratuito", + "startup": "Startup", + "business": "Negocio", + "tag": "Más Popular", + "enterprise": "Empresa", + + "freeSubtitle": "gratis para siempre", + "freeUsers": "Mejor para uso personal", + "freeText01": "100MB de almacenamiento", + "freeText02": "3 proyectos", + "freeText03": "5 miembros del equipo", + + "startupSubtitle": "TARIFA PLANa / mes", + "startupUsers": "Hasta 15 usuarios", + "startupText01": "25GB de almacenamiento", + "startupText02": "Proyectos activos ilimitados", + "startupText03": "Programación", + "startupText04": "Informes", + "startupText05": "Suscribirse a proyectos", + + "businessSubtitle": "usuario / mes", + "businessUsers": "16 - 200 usuarios", + + "enterpriseUsers": "200 - 500+ usuarios", + + "footerTitle": "Por favor, proporciónenos un número de teléfono que podamos usar para contactarte.", + "footerLabel": "Número de Teléfono", + "footerButton": "Contactarnos", + + "redeemCodePlaceHolder": "Ingrese su código de canje", + "submit": "Enviar", + + "trialPlan": "Plan de Prueba", + "trialExpireDate": "Válido hasta {{trial_expire_date}}", + "trialExpired": "Su prueba gratuita expiró {{trial_expire_string}}", + "trialInProgress": "Su prueba gratuita expira {{trial_expire_string}}", + + "required": "Este campo es requerido", + "invalidCode": "Código inválido", + + "selectPlan": "Seleccione el mejor plan para su equipo", + "changeSubscriptionPlan": "Cambie su plan de suscripción", + "noOfSeats": "Número de asientos", + "annualPlan": "Pro - Anual", + "monthlyPlan": "Pro - Mensual", + "freeForever": "Gratis para siempre", + "bestForPersonalUse": "Mejor para uso personal", + "storage": "Almacenamiento", + "projects": "Proyectos", + "teamMembers": "Miembros del equipo", + "unlimitedTeamMembers": "Miembros del equipo ilimitados", + "unlimitedActiveProjects": "Proyectos activos ilimitados", + "schedule": "Programación", + "reporting": "Informes", + "subscribeToProjects": "Suscribirse a proyectos", + "billedAnnually": "Facturado Anualmente", + "billedMonthly": "Facturado Mensualmente", + + "pausePlan": "Pausar Plan", + "resumePlan": "Reanudar Plan", + "changePlan": "Cambiar Plan", + "cancelPlan": "Cancelar Plan", + + "perMonthPerUser": "por usuario / mes", + "viewInvoice": "Ver Factura", + "switchToFreePlan": "Cambiar a Plan Gratuito", + + "expirestoday": "hoy", + "expirestomorrow": "mañana", + "expiredDaysAgo": "hace {{days}} días" +} diff --git a/worklenz-frontend/public/locales/es/admin-center/overview.json b/worklenz-frontend/public/locales/es/admin-center/overview.json new file mode 100644 index 00000000..f88dbdf6 --- /dev/null +++ b/worklenz-frontend/public/locales/es/admin-center/overview.json @@ -0,0 +1,8 @@ +{ + "overview": "Resumen", + "name": "Nombre de la Organización", + "owner": "Propietario de la Organización", + "admins": "Administradores de la Organización", + "contactNumber": "Agregar Número de Contacto", + "edit": "Editar" +} diff --git a/worklenz-frontend/public/locales/es/admin-center/projects.json b/worklenz-frontend/public/locales/es/admin-center/projects.json new file mode 100644 index 00000000..ab28374f --- /dev/null +++ b/worklenz-frontend/public/locales/es/admin-center/projects.json @@ -0,0 +1,12 @@ +{ + "membersCount": "Cantidad de miembros", + "createdAt": "Creado en", + "projectName": "Nombre del proyecto", + "teamName": "Nombre del equipo", + "refreshProjects": "Refrescar proyectos", + "searchPlaceholder": "Buscar por nombre de proyecto", + "deleteProject": "¿Estás seguro de que deseas eliminar este proyecto?", + "confirm": "Confirmar", + "cancel": "Cancelar", + "delete": "Eliminar proyecto" +} diff --git a/worklenz-frontend/public/locales/es/admin-center/sidebar.json b/worklenz-frontend/public/locales/es/admin-center/sidebar.json new file mode 100644 index 00000000..7626302c --- /dev/null +++ b/worklenz-frontend/public/locales/es/admin-center/sidebar.json @@ -0,0 +1,8 @@ +{ + "overview": "Resumen", + "users": "Usuarios", + "teams": "Equipos", + "billing": "Facturación", + "projects": "Proyectos", + "adminCenter": "Centro de Administración" +} diff --git a/worklenz-frontend/public/locales/es/admin-center/teams.json b/worklenz-frontend/public/locales/es/admin-center/teams.json new file mode 100644 index 00000000..98e3b188 --- /dev/null +++ b/worklenz-frontend/public/locales/es/admin-center/teams.json @@ -0,0 +1,33 @@ +{ + "title": "Equipos", + "subtitle": "equipos", + "tooltip": "Actualizar equipos", + "placeholder": "Buscar por nombre", + "addTeam": "Agregar Equipo", + "team": "Equipo", + "membersCount": "Cantidad de Miembros", + "members": "Miembros", + "drawerTitle": "Crear Nuevo Equipo", + "label": "Nombre del Equipo", + "drawerPlaceholder": "Nombre", + "create": "Crear", + "delete": "Eliminar", + "settings": "Configuración", + "popTitle": "¿Está seguro?", + "message": "Por favor ingrese un nombre", + "teamSettings": "Configuración del Equipo", + "teamName": "Nombre del Equipo", + "teamDescription": "Descripción del Equipo", + "teamMembers": "Miembros del Equipo", + "teamMembersCount": "Cantidad de Miembros del Equipo", + "teamMembersPlaceholder": "Buscar por nombre", + "addMember": "Agregar Miembro", + "add": "Agregar", + "update": "Actualizar", + "teamNamePlaceholder": "Nombre del Equipo", + "user": "Usuario", + "role": "Rol", + "owner": "Propietario", + "admin": "Administrador", + "member": "Miembro" +} diff --git a/worklenz-frontend/public/locales/es/admin-center/users.json b/worklenz-frontend/public/locales/es/admin-center/users.json new file mode 100644 index 00000000..05626c3b --- /dev/null +++ b/worklenz-frontend/public/locales/es/admin-center/users.json @@ -0,0 +1,9 @@ +{ + "title": "Usuarios", + "subTitle": "usuarios", + "placeholder": "Buscar por nombre", + "user": "Usuario", + "email": "Correo electrónico", + "lastActivity": "Última actividad", + "refresh": "Actualizar usuarios" +} diff --git a/worklenz-frontend/public/locales/es/all-project-list.json b/worklenz-frontend/public/locales/es/all-project-list.json new file mode 100644 index 00000000..60e9108a --- /dev/null +++ b/worklenz-frontend/public/locales/es/all-project-list.json @@ -0,0 +1,23 @@ +{ + "name": "Nombre", + "client": "Cliente", + "category": "Categoría", + "status": "Estado", + "tasksProgress": "Progreso de tareas", + "updated_at": "Última actualización", + "members": "Miembros", + "setting": "Configuración", + "archive": "Archivar", + "projects": "Proyectos", + "refreshProjects": "Actualizar proyectos", + "all": "Todos", + "favorites": "Favoritos", + "archived": "Archivados", + "placeholder": "Buscar por nombre", + "unarchive": "Desarchivar", + "archiveConfirm": "¿Estás seguro de que deseas archivar este proyecto?", + "unarchiveConfirm": "¿Estás seguro de que deseas desarchivar este proyecto?", + "clickToFilter": "Clique para filtrar por", + "noProjects": "No se encontraron proyectos", + "addToFavourites": "Añadir a favoritos" +} diff --git a/worklenz-frontend/public/locales/es/auth/auth-common.json b/worklenz-frontend/public/locales/es/auth/auth-common.json new file mode 100644 index 00000000..6539ec51 --- /dev/null +++ b/worklenz-frontend/public/locales/es/auth/auth-common.json @@ -0,0 +1,5 @@ +{ + "loggingOut": "Cerrando sesión...", + "authenticating": "Autenticando...", + "gettingThingsReady": "Preparando todo para ti..." +} diff --git a/worklenz-frontend/public/locales/es/auth/forgot-password.json b/worklenz-frontend/public/locales/es/auth/forgot-password.json new file mode 100644 index 00000000..5ba75336 --- /dev/null +++ b/worklenz-frontend/public/locales/es/auth/forgot-password.json @@ -0,0 +1,12 @@ +{ + "headerDescription": "Restablecer tu contraseña", + "emailLabel": "Correo electrónico", + "emailPlaceholder": "Ingresa tu correo electrónico", + "emailRequired": "¡Por favor ingresa tu correo electrónico!", + "resetPasswordButton": "Restablecer Contraseña", + "returnToLoginButton": "Volver al Inicio de Sesión", + "passwordResetSuccessMessage": "Se ha enviado un enlace para restablecer la contraseña a tu correo electrónico.", + "orText": "O", + "successTitle": "¡Instrucciones de restablecimiento enviadas!", + "successMessage": "La información de restablecimiento se ha enviado a tu correo electrónico. Por favor, verifica tu correo." +} diff --git a/worklenz-frontend/public/locales/es/auth/login.json b/worklenz-frontend/public/locales/es/auth/login.json new file mode 100644 index 00000000..8c1697f8 --- /dev/null +++ b/worklenz-frontend/public/locales/es/auth/login.json @@ -0,0 +1,27 @@ +{ + "headerDescription": "Inicia sesión en tu cuenta", + "emailLabel": "Correo electrónico", + "emailPlaceholder": "Ingresa tu correo electrónico", + "emailRequired": "¡Por favor ingresa tu correo electrónico!", + "passwordLabel": "Contraseña", + "passwordPlaceholder": "Ingresa tu contraseña", + "passwordRequired": "¡Por favor ingresa tu contraseña!", + "rememberMe": "Recordarme", + "loginButton": "Iniciar sesión", + "signupButton": "Registrarse", + "forgotPasswordButton": "¿Olvidaste tu contraseña?", + "signInWithGoogleButton": "Iniciar sesión con Google", + "successMessage": "¡Has iniciado sesión exitosamente!", + "dontHaveAccountText": "¿No tienes una cuenta?", + "orText": "O", + "loginError": "Iniciar sesión falló", + "googleLoginError": "Iniciar sesión con Google falló", + "validationMessages": { + "password": "La contraseña debe tener al menos 8 caracteres", + "email": "Por favor ingresa una dirección de correo electrónico válida" + }, + "errorMessages": { + "loginErrorTitle": "Iniciar sesión falló", + "loginErrorMessage": "Por favor verifica tu correo electrónico y contraseña y vuelve a intentarlo" + } +} diff --git a/worklenz-frontend/public/locales/es/auth/signup.json b/worklenz-frontend/public/locales/es/auth/signup.json new file mode 100644 index 00000000..465ff287 --- /dev/null +++ b/worklenz-frontend/public/locales/es/auth/signup.json @@ -0,0 +1,29 @@ +{ + "headerDescription": "Regístrate para comenzar", + "nameLabel": "Nombre completo", + "namePlaceholder": "Ingresa tu nombre completo", + "nameRequired": "¡Por favor ingresa tu nombre completo!", + "nameMinCharacterRequired": "¡El nombre completo debe tener al menos 4 caracteres!", + "emailLabel": "Correo electrónico", + "emailPlaceholder": "Ingresa tu correo electrónico", + "emailRequired": "¡Por favor ingresa tu correo electrónico!", + "passwordLabel": "Contraseña", + "passwordPlaceholder": "Ingresa tu contraseña", + "passwordRequired": "¡Por favor ingresa tu contraseña!", + "passwordMinCharacterRequired": "¡La contraseña debe tener al menos 8 caracteres!", + "passwordPatternRequired": "¡La contraseña no cumple con los requisitos!", + "strongPasswordPlaceholder": "Ingresa una contraseña más segura", + "passwordValidationAltText": "La contraseña debe incluir al menos 8 caracteres con letras mayúsculas y minúsculas, un número y un símbolo.", + "signupSuccessMessage": "¡Te has registrado exitosamente!", + "privacyPolicyLink": "Política de Privacidad", + "termsOfUseLink": "Términos de Uso", + "bySigningUpText": "Al registrarte, aceptas nuestra", + "andText": "y", + "signupButton": "Registrarse", + "signInWithGoogleButton": "Iniciar sesión con Google", + "alreadyHaveAccountText": "¿Ya tienes una cuenta?", + "loginButton": "Iniciar sesión", + "orText": "O", + "reCAPTCHAVerificationError": "Error de verificación de reCAPTCHA", + "reCAPTCHAVerificationErrorMessage": "No pudimos verificar tu reCAPTCHA. Por favor, inténtalo de nuevo." +} diff --git a/worklenz-frontend/public/locales/es/auth/verify-reset-email.json b/worklenz-frontend/public/locales/es/auth/verify-reset-email.json new file mode 100644 index 00000000..1058bc1c --- /dev/null +++ b/worklenz-frontend/public/locales/es/auth/verify-reset-email.json @@ -0,0 +1,14 @@ +{ + "title": "Verificar correo de restablecimiento", + "description": "Ingresa tu nueva contraseña", + "placeholder": "Ingresa tu nueva contraseña", + "confirmPasswordPlaceholder": "Confirma tu nueva contraseña", + "passwordHint": "Mínimo 8 caracteres, con mayúsculas y minúsculas, un número y un símbolo.", + "resetPasswordButton": "Restablecer contraseña", + "orText": "O", + "resendResetEmail": "Reenviar correo de restablecimiento", + "passwordRequired": "Por favor ingresa tu nueva contraseña", + "returnToLoginButton": "Volver al inicio de sesión", + "confirmPasswordRequired": "Por favor confirma tu nueva contraseña", + "passwordMismatch": "Las contraseñas no coinciden" +} diff --git a/worklenz-frontend/public/locales/es/common.json b/worklenz-frontend/public/locales/es/common.json new file mode 100644 index 00000000..583e8670 --- /dev/null +++ b/worklenz-frontend/public/locales/es/common.json @@ -0,0 +1,9 @@ +{ + "login-success": "¡Inicio de sesión exitoso!", + "login-failed": "Error al iniciar sesión. Por favor verifica tus credenciales e intenta nuevamente.", + "signup-success": "¡Registro exitoso! Bienvenido a bordo.", + "signup-failed": "Error al registrarse. Por favor asegúrate de llenar todos los campos requeridos e intenta nuevamente.", + "reconnecting": "Reconectando al servidor...", + "connection-lost": "Conexión perdida. Intentando reconectarse...", + "connection-restored": "Conexión restaurada. Reconectando al servidor..." +} diff --git a/worklenz-frontend/public/locales/es/create-first-project-form.json b/worklenz-frontend/public/locales/es/create-first-project-form.json new file mode 100644 index 00000000..4382cda5 --- /dev/null +++ b/worklenz-frontend/public/locales/es/create-first-project-form.json @@ -0,0 +1,13 @@ +{ + "formTitle": "Crea tu primer proyecto", + "inputLabel": "¿En qué proyecto estás trabajando ahora?", + "or": "o", + "templateButton": "Importar desde plantilla", + "createFromTemplate": "Crear desde plantilla", + "goBack": "Volver", + "continue": "Continuar", + "cancel": "Cancelar", + "create": "Crear", + "templateDrawerTitle": "Seleccionar de plantillas", + "createProject": "Crear proyecto" +} diff --git a/worklenz-frontend/public/locales/es/create-first-tasks.json b/worklenz-frontend/public/locales/es/create-first-tasks.json new file mode 100644 index 00000000..adca7366 --- /dev/null +++ b/worklenz-frontend/public/locales/es/create-first-tasks.json @@ -0,0 +1,7 @@ +{ + "formTitle": "Crea tu primera tarea.", + "inputLable": "Escribe algunas tareas que vas a hacer en", + "addAnother": "Agregar otra", + "goBack": "Volver", + "continue": "Continuar" +} diff --git a/worklenz-frontend/public/locales/es/home.json b/worklenz-frontend/public/locales/es/home.json new file mode 100644 index 00000000..cfd238f9 --- /dev/null +++ b/worklenz-frontend/public/locales/es/home.json @@ -0,0 +1,45 @@ +{ + "todoList": { + "title": "Lista de tareas", + "refreshTasks": "Actualizar tareas", + "addTask": "+ Agregar tarea", + "noTasks": "Sin tareas", + "pressEnter": "Presiona", + "toCreate": "para crear.", + "markAsDone": "Marcar como hecho" + }, + "projects": { + "title": "Proyectos", + "refreshProjects": "Actualizar proyectos", + "noRecentProjects": "Actualmente no estás asignado a ningún proyecto.", + "noFavouriteProjects": "No hay proyectos marcados como favoritos.", + "recent": "Recientes", + "favourites": "Favoritos" + }, + "tasks": { + "assignedToMe": "Asignadas a mí", + "assignedByMe": "Asignadas por mí", + "all": "Todas", + "today": "Hoy", + "upcoming": "Próximas", + "overdue": "Vencidas", + "noDueDate": "Sin fecha de vencimiento", + "noTasks": "No hay tareas para mostrar.", + "addTask": "+ Agregar tarea", + "name": "Nombre", + "project": "Proyecto", + "status": "Estado", + "dueDate": "Fecha de vencimiento", + "dueDatePlaceholder": "Establecer fecha de vencimiento", + "tomorrow": "Mañana", + "nextWeek": "La semana que viene", + "nextMonth": "El próximo mes", + "projectRequired": "Por favor selecciona un proyecto", + "dueOn": "Tareas vencidas el", + "taskRequired": "Por favor agrega una tarea", + "list": "Lista", + "calendar": "Calendario", + "tasks": "Tareas", + "refresh": "Actualizar" + } +} diff --git a/worklenz-frontend/public/locales/es/invite-initial-team-members.json b/worklenz-frontend/public/locales/es/invite-initial-team-members.json new file mode 100644 index 00000000..87fb006c --- /dev/null +++ b/worklenz-frontend/public/locales/es/invite-initial-team-members.json @@ -0,0 +1,8 @@ +{ + "formTitle": "Invita a tu equipo a trabajar", + "inputLable": "Invitar por correo electrónico", + "addAnother": "Agregar otro", + "goBack": "Volver", + "continue": "Continuar", + "skipForNow": "Omitir por ahora" +} diff --git a/worklenz-frontend/public/locales/es/kanban-board.json b/worklenz-frontend/public/locales/es/kanban-board.json new file mode 100644 index 00000000..71de992c --- /dev/null +++ b/worklenz-frontend/public/locales/es/kanban-board.json @@ -0,0 +1,23 @@ +{ + "rename": "Renombrar", + "delete": "Eliminar", + "addTask": "Agregar tarea", + "addSectionButton": "Agregar sección", + "changeCategory": "Cambiar categoría", + + "deleteTooltip": "Eliminar", + "deleteConfirmationTitle": "¿Estás seguro?", + "deleteConfirmationOk": "Sí", + "deleteConfirmationCancel": "Cancelar", + + "dueDate": "Fecha de vencimiento", + "cancel": "Cancelar", + + "today": "Hoy", + "tomorrow": "Mañana", + "assignToMe": "Asignarme", + "archive": "Archivar", + + "newTaskNamePlaceholder": "Escribe un nombre de tarea", + "newSubtaskNamePlaceholder": "Escribe un nombre de subtarea" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/es/license-expired.json b/worklenz-frontend/public/locales/es/license-expired.json new file mode 100644 index 00000000..3cd0de2d --- /dev/null +++ b/worklenz-frontend/public/locales/es/license-expired.json @@ -0,0 +1,6 @@ +{ + "title": "¡Tu prueba de Worklenz ha expirado!", + "subtitle": "Por favor actualiza ahora.", + "button": "Actualizar ahora", + "checking": "Verificando estado de la suscripción..." +} diff --git a/worklenz-frontend/public/locales/es/navbar.json b/worklenz-frontend/public/locales/es/navbar.json new file mode 100644 index 00000000..97c79d50 --- /dev/null +++ b/worklenz-frontend/public/locales/es/navbar.json @@ -0,0 +1,31 @@ +{ + "logoAlt": "Logo de Worklenz", + "home": "Inicio", + "projects": "Proyectos", + "schedule": "Calendario", + "reporting": "Informes", + "clients": "Clientes", + "teams": "Equipos", + "labels": "Etiquetas", + "jobTitles": "Cargos", + "upgradePlan": "Actualizar Plan", + "upgradePlanTooltip": "Actualizar Plan", + "invite": "Invitar", + "inviteTooltip": "Invitar miembros al equipo", + "switchTeamTooltip": "Cambiar equipo", + "help": "Ayuda", + "notificationTooltip": "Ver notificaciones", + "profileTooltip": "Ver perfil", + "adminCenter": "Centro de administración", + "settings": "Configuración", + "logOut": "Cerrar sesión", + "notificationsDrawer": { + "read": "Notificaciones leídas", + "unread": "Notificaciones no leídas", + "markAsRead": "Marcar como leído", + "readAndJoin": "Leer y unirse", + "accept": "Aceptar", + "acceptAndJoin": "Aceptar y unirse", + "noNotifications": "Sin notificaciones" + } +} diff --git a/worklenz-frontend/public/locales/es/organization-name-form.json b/worklenz-frontend/public/locales/es/organization-name-form.json new file mode 100644 index 00000000..efd60ed7 --- /dev/null +++ b/worklenz-frontend/public/locales/es/organization-name-form.json @@ -0,0 +1,5 @@ +{ + "nameYourOrganization": "Nombra tu organización.", + "worklenzAccountTitle": "Elige un nombre para tu cuenta de Worklenz.", + "continue": "Continuar" +} diff --git a/worklenz-frontend/public/locales/es/phases-drawer.json b/worklenz-frontend/public/locales/es/phases-drawer.json new file mode 100644 index 00000000..0363c69c --- /dev/null +++ b/worklenz-frontend/public/locales/es/phases-drawer.json @@ -0,0 +1,7 @@ +{ + "configurePhases": "Configurar fases", + "phaseLabel": "Etiqueta de fase", + "enterPhaseName": "Ingrese un nombre para la etiqueta de fase", + "addOption": "Agregar opción", + "phaseOptions": "Opciones de fase:" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/es/project-drawer.json b/worklenz-frontend/public/locales/es/project-drawer.json new file mode 100644 index 00000000..2dc114cc --- /dev/null +++ b/worklenz-frontend/public/locales/es/project-drawer.json @@ -0,0 +1,42 @@ +{ + "createProject": "Crear Proyecto", + "editProject": "Editar Proyecto", + "enterCategoryName": "Ingrese un nombre para la categoría", + "hitEnterToCreate": "¡Presiona enter para crear!", + "enterNotes": "Notas", + "youCanManageClientsUnderSettings": "Puedes gestionar clientes en Configuración", + "addCategory": "Agregar una categoría al proyecto", + "newCategory": "Nueva Categoría", + "notes": "Notas", + "startDate": "Fecha de Inicio", + "endDate": "Fecha de Finalización", + "estimateWorkingDays": "Estimar días de trabajo", + "estimateManDays": "Estimar días de trabajo", + "hoursPerDay": "Horas por día", + "create": "Crear", + "update": "Actualizar", + "delete": "Eliminar", + "typeToSearchClients": "Escribe para buscar clientes", + "projectColor": "Color del Proyecto", + "pleaseEnterAName": "Por favor ingresa un nombre", + "enterProjectName": "Ingresa el nombre del proyecto", + "name": "Nombre", + "status": "Estado", + "health": "Salud", + "category": "Categoría", + "projectManager": "Gerente de Proyecto", + "client": "Cliente", + "deleteConfirmation": "¿Estás seguro de que quieres eliminar?", + "deleteConfirmationDescription": "Esto eliminará todos los datos asociados y no se puede deshacer.", + "yes": "Sí", + "no": "No", + "createdAt": "Creado", + "updatedAt": "Actualizado", + "by": "por", + "add": "Agregar", + "asClient": "como cliente", + "createClient": "Crear cliente", + "searchInputPlaceholder": "Busca por nombre o email", + "hoursPerDayValidationMessage": "Las horas por día deben ser un número entre 1 y 24", + "noPermission": "Sin permiso" +} diff --git a/worklenz-frontend/public/locales/es/project-view-files.json b/worklenz-frontend/public/locales/es/project-view-files.json new file mode 100644 index 00000000..13071a2a --- /dev/null +++ b/worklenz-frontend/public/locales/es/project-view-files.json @@ -0,0 +1,14 @@ +{ + "nameColumn": "Nombre", + "attachedTaskColumn": "Tarea Adjunta", + "sizeColumn": "Tamaño", + "uploadedByColumn": "Subido Por", + "uploadedAtColumn": "Subido El", + "fileIconAlt": "Icono de archivo", + "titleDescriptionText": "Todos los archivos adjuntos a las tareas en este proyecto aparecerán aquí.", + "deleteConfirmationTitle": "¿Estás seguro?", + "deleteConfirmationOk": "Sí", + "deleteConfirmationCancel": "Cancelar", + "segmentedTooltip": "¡Próximamente! Cambiar entre vista de lista y vista de miniaturas.", + "emptyText": "No hay archivos adjuntos en el proyecto." +} diff --git a/worklenz-frontend/public/locales/es/project-view-insights.json b/worklenz-frontend/public/locales/es/project-view-insights.json new file mode 100644 index 00000000..bd60b58e --- /dev/null +++ b/worklenz-frontend/public/locales/es/project-view-insights.json @@ -0,0 +1,41 @@ +{ + "overview": { + "title": "Resumen", + "statusOverview": "Resumen de Estado", + "priorityOverview": "Resumen de Prioridad", + "lastUpdatedTasks": "Últimas Tareas Actualizadas" + }, + "members": { + "title": "Miembros", + "tooltip": "Miembros", + "tasksByMembers": "Tareas por miembros", + "tasksByMembersTooltip": "Tareas por miembros", + "name": "Nombre", + "taskCount": "Cantidad de Tareas", + "contribution": "Contribución", + "completed": "Completadas", + "incomplete": "Incompletas", + "overdue": "Atrasadas", + "progress": "Progreso" + }, + "tasks": { + "overdueTasks": "Tareas Atrasadas", + "overLoggedTasks": "Tareas con Exceso de Tiempo", + "tasksCompletedEarly": "Tareas completadas antes de tiempo", + "tasksCompletedLate": "Tareas completadas tarde", + "overLoggedTasksTooltip": "Tareas que tienen tiempo registrado más allá de su tiempo estimado", + "overdueTasksTooltip": "Tareas que están más allá de su fecha límite" + }, + "common": { + "seeAll": "Ver todo", + "totalLoggedHours": "Total de horas registradas", + "totalEstimation": "Estimación total", + "completedTasks": "Tareas completadas", + "incompleteTasks": "Tareas incompletas", + "overdueTasks": "Tareas atrasadas", + "overdueTasksTooltip": "Tareas que están más allá de su fecha límite", + "totalLoggedHoursTooltip": "Estimación de tareas y tiempo registrado para las tareas.", + "includeArchivedTasks": "Incluir Tareas Archivadas", + "export": "Exportar" + } +} diff --git a/worklenz-frontend/public/locales/es/project-view-members.json b/worklenz-frontend/public/locales/es/project-view-members.json new file mode 100644 index 00000000..95a8d943 --- /dev/null +++ b/worklenz-frontend/public/locales/es/project-view-members.json @@ -0,0 +1,17 @@ +{ + "nameColumn": "Nombre", + "jobTitleColumn": "Cargo", + "emailColumn": "Correo", + "tasksColumn": "Tareas", + "taskProgressColumn": "Progreso de Tareas", + "accessColumn": "Acceso", + "fileIconAlt": "Icono de archivo", + "deleteConfirmationTitle": "¿Estás seguro?", + "deleteConfirmationOk": "Sí", + "deleteConfirmationCancel": "Cancelar", + "refreshButtonTooltip": "Actualizar miembros", + "deleteButtonTooltip": "Eliminar del proyecto", + "memberCount": "Miembro", + "membersCountPlural": "Miembros", + "emptyText": "No hay archivos adjuntos en el proyecto." +} diff --git a/worklenz-frontend/public/locales/es/project-view-updates.json b/worklenz-frontend/public/locales/es/project-view-updates.json new file mode 100644 index 00000000..d565fcfc --- /dev/null +++ b/worklenz-frontend/public/locales/es/project-view-updates.json @@ -0,0 +1,6 @@ +{ + "inputPlaceholder": "Agregar un comentario..", + "addButton": "Agregar", + "cancelButton": "Cancelar", + "deleteButton": "Eliminar" +} diff --git a/worklenz-frontend/public/locales/es/project-view/import-task-templates.json b/worklenz-frontend/public/locales/es/project-view/import-task-templates.json new file mode 100644 index 00000000..c47edc71 --- /dev/null +++ b/worklenz-frontend/public/locales/es/project-view/import-task-templates.json @@ -0,0 +1,11 @@ +{ + "importTaskTemplate": "Importar plantilla de tarea", + "templateName": "Nombre de la plantilla", + "templateDescription": "Descripción de la plantilla", + "selectedTasks": "Tareas seleccionadas", + "tasks": "Tareas", + "templates": "Plantillas", + "remove": "Eliminar", + "cancel": "Cancelar", + "import": "Importar" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/es/project-view/project-member-drawer.json b/worklenz-frontend/public/locales/es/project-view/project-member-drawer.json new file mode 100644 index 00000000..1a90bbd6 --- /dev/null +++ b/worklenz-frontend/public/locales/es/project-view/project-member-drawer.json @@ -0,0 +1,8 @@ +{ + "title": "Miembros del Proyecto", + "searchLabel": "Agregar miembros ingresando su nombre o correo electrónico", + "searchPlaceholder": "Escriba nombre o correo electrónico", + "inviteAsAMember": "Invitar como miembro", + "inviteNewMemberByEmail": "Invitar nuevo miembro por correo electrónico" + +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/es/project-view/project-view-header.json b/worklenz-frontend/public/locales/es/project-view/project-view-header.json new file mode 100644 index 00000000..de6020cf --- /dev/null +++ b/worklenz-frontend/public/locales/es/project-view/project-view-header.json @@ -0,0 +1,13 @@ +{ + "importTasks": "Importar tareas", + "createTask": "Crear tarea", + "settings": "Ajustes", + "subscribe": "Suscribirse", + "unsubscribe": "Cancelar suscripción", + "deleteProject": "Eliminar proyecto", + "startDate": "Fecha de inicio", + "endDate": "Fecha de finalización", + "projectSettings": "Ajustes del proyecto", + "projectSummary": "Resumen del proyecto", + "receiveProjectSummary": "Recibir un resumen del proyecto todas las noches." +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/es/project-view/save-as-template.json b/worklenz-frontend/public/locales/es/project-view/save-as-template.json new file mode 100644 index 00000000..6ad67182 --- /dev/null +++ b/worklenz-frontend/public/locales/es/project-view/save-as-template.json @@ -0,0 +1,27 @@ +{ + "title": "Guardar como Plantilla", + "templateName": "Nombre de la Plantilla", + "includes": "¿Qué se debe incluir en la plantilla del proyecto?", + "includesOptions": { + "statuses": "Estados", + "phases": "Fases", + "labels": "Etiquetas" + }, + "taskIncludes": "¿Qué se debe incluir en la plantilla de las tareas?", + "taskIncludesOptions": { + "statuses": "Estados", + "phases": "Fases", + "labels": "Etiquetas", + "name": "Nombre", + "priority": "Prioridad", + "status": "Estado", + "phase": "Fase", + "label": "Etiqueta", + "timeEstimate": "Estimación de Tiempo", + "description": "Descripción", + "subTasks": "Sub Tasks" + }, + "cancel": "Cancel", + "save": "Save", + "templateNamePlaceholder": "Enter template name" +} diff --git a/worklenz-frontend/public/locales/es/reporting-members-drawer.json b/worklenz-frontend/public/locales/es/reporting-members-drawer.json new file mode 100644 index 00000000..fb8ecf20 --- /dev/null +++ b/worklenz-frontend/public/locales/es/reporting-members-drawer.json @@ -0,0 +1,90 @@ +{ + "exportButton": "Exportar", + "timeLogsButton": "Registros de Tiempo", + "activityLogsButton": "Registros de Actividad", + "tasksButton": "Tareas", + "searchByNameInputPlaceholder": "Buscar por nombre", + + "overviewTab": "Resumen", + "timeLogsTab": "Registros de Tiempo", + "activityLogsTab": "Registros de Actividad", + "tasksTab": "Tareas", + + "projectsText": "Proyectos", + "totalTasksText": "Total de Tareas", + "assignedTasksText": "Tareas Asignadas", + "completedTasksText": "Tareas Completadas", + "ongoingTasksText": "Tareas en Curso", + "overdueTasksText": "Tareas Atrasadas", + "loggedHoursText": "Horas Registradas", + + "tasksText": "Tareas", + "allText": "Todo", + + "tasksByProjectsText": "Tareas por Proyectos", + "tasksByStatusText": "Tareas por Estado", + "tasksByPriorityText": "Tareas por Prioridad", + + "todoText": "Por Hacer", + "doingText": "Haciendo", + "doneText": "Hecho", + "lowText": "Baja", + "mediumText": "Media", + "highText": "Alta", + + "billableButton": "Facturable", + "billableText": "Facturable", + "nonBillableText": "No Facturable", + + "timeLogsEmptyPlaceholder": "No hay registros de tiempo para mostrar", + "loggedText": "Registrado", + "forText": "para", + "inText": "en", + "updatedText": "Actualizado", + "fromText": "Desde", + "toText": "hasta", + "withinText": "dentro de", + + "activityLogsEmptyPlaceholder": "No hay registros de actividad para mostrar", + + "filterByText": "Filtrar por:", + "selectProjectPlaceholder": "Seleccionar Proyecto", + + "taskColumn": "Tarea", + "nameColumn": "Nombre", + "projectColumn": "Proyecto", + "statusColumn": "Estado", + "priorityColumn": "Prioridad", + "dueDateColumn": "Fecha de Vencimiento", + "completedDateColumn": "Fecha de Finalización", + "estimatedTimeColumn": "Tiempo Estimado", + "loggedTimeColumn": "Tiempo Registrado", + "overloggedTimeColumn": "Tiempo Excedido", + "daysLeftColumn": "Días Restantes/Atrasados", + "startDateColumn": "Fecha de Inicio", + "endDateColumn": "Fecha de Fin", + "actualTimeColumn": "Tiempo Real", + "projectHealthColumn": "Salud del Proyecto", + "categoryColumn": "Categoría", + "projectManagerColumn": "Gerente de Proyecto", + + "tasksStatsOverviewDrawerTitle": "Tareas de", + "projectsStatsOverviewDrawerTitle": "Proyectos de", + + "cancelledText": "Cancelado", + "blockedText": "Bloqueado", + "onHoldText": "En Espera", + "proposedText": "Propuesto", + "inPlanningText": "En Planificación", + "inProgressText": "En Progreso", + "completedText": "Completado", + "continuousText": "Continuo", + + "daysLeftText": "días restantes", + "daysOverdueText": "días de retraso", + + "notSetText": "No Establecido", + "needsAttentionText": "Necesita Atención", + "atRiskText": "En Riesgo", + "goodText": "Bien" +} diff --git a/worklenz-frontend/public/locales/es/reporting-members.json b/worklenz-frontend/public/locales/es/reporting-members.json new file mode 100644 index 00000000..d87cafb8 --- /dev/null +++ b/worklenz-frontend/public/locales/es/reporting-members.json @@ -0,0 +1,35 @@ +{ + "yesterdayText": "Ayer", + "lastSevenDaysText": "Últimos 7 Días", + "lastWeekText": "Última Semana", + "lastThirtyDaysText": "Últimos 30 Días", + "lastMonthText": "Último Mes", + "lastThreeMonthsText": "Últimos 3 Meses", + "allTimeText": "Todo el Tiempo", + "customRangeText": "Rango personalizado", + "startDateInputPlaceholder": "Fecha de inicio", + "EndDateInputPlaceholder": "Fecha final", + "filterButton": "Filtrar", + + "membersTitle": "Miembros", + "includeArchivedButton": "Incluir Proyectos Archivados", + "exportButton": "Exportar", + "excelButton": "Excel", + "searchByNameInputPlaceholder": "Buscar por nombre", + + "memberColumn": "Miembro", + "tasksProgressColumn": "Progreso de Tareas", + "tasksAssignedColumn": "Tareas Asignadas", + "completedTasksColumn": "Tareas Completadas", + "overdueTasksColumn": "Tareas Atrasadas", + "ongoingTasksColumn": "Tareas en Curso", + + "tasksAssignedColumnTooltip": "Tareas asignadas en el rango de fechas seleccionado", + "overdueTasksColumnTooltip": "Tareas atrasadas al final del rango de fechas seleccionado", + "completedTasksColumnTooltip": "Tareas completadas en el rango de fechas seleccionado", + "ongoingTasksColumnTooltip": "Tareas iniciadas aún no completadas", + + "todoText": "Por Hacer", + "doingText": "Haciendo", + "doneText": "Hecho" +} diff --git a/worklenz-frontend/public/locales/es/reporting-overview-drawer.json b/worklenz-frontend/public/locales/es/reporting-overview-drawer.json new file mode 100644 index 00000000..fce8e554 --- /dev/null +++ b/worklenz-frontend/public/locales/es/reporting-overview-drawer.json @@ -0,0 +1,39 @@ +{ + "exportButton": "Exportar", + "projectsButton": "Proyectos", + "membersButton": "Miembros", + "searchByNameInputPlaceholder": "Buscar por nombre", + + "overviewTab": "Resumen", + "projectsTab": "Proyectos", + "membersTab": "Miembros", + + "projectsByStatusText": "Proyectos por Estado", + "projectsByCategoryText": "Proyectos por Categoría", + "projectsByHealthText": "Proyectos por Salud", + + "projectsText": "Proyectos", + "allText": "Todo", + + "cancelledText": "Cancelado", + "blockedText": "Bloqueado", + "onHoldText": "En Espera", + "proposedText": "Propuesto", + "inPlanningText": "En Planificación", + "inProgressText": "En Progreso", + "completedText": "Completado", + "continuousText": "Continuo", + + "notSetText": "No Establecido", + "needsAttentionText": "Necesita Atención", + "atRiskText": "En Riesgo", + "goodText": "Bien", + + "nameColumn": "Nombre", + "emailColumn": "Correo", + "projectsColumn": "Proyectos", + "tasksColumn": "Tareas", + "overdueTasksColumn": "Tareas Atrasadas", + "completedTasksColumn": "Tareas Completadas", + "ongoingTasksColumn": "Tareas en Curso" +} diff --git a/worklenz-frontend/public/locales/es/reporting-overview.json b/worklenz-frontend/public/locales/es/reporting-overview.json new file mode 100644 index 00000000..3f18fbed --- /dev/null +++ b/worklenz-frontend/public/locales/es/reporting-overview.json @@ -0,0 +1,25 @@ +{ + "overviewTitle": "Resumen", + "includeArchivedButton": "Incluir Proyectos Archivados", + + "teamCount": "Equipo", + "teamCountPlural": "Equipos", + "projectCount": "Proyecto", + "projectCountPlural": "Proyectos", + "memberCount": "Miembro", + "memberCountPlural": "Miembros", + "activeProjectCount": "Proyecto Activo", + "activeProjectCountPlural": "Proyectos Activos", + "overdueProjectCount": "Proyecto Atrasado", + "overdueProjectCountPlural": "Proyectos Atrasados", + "unassignedMemberCount": "Miembro Sin Asignar", + "unassignedMemberCountPlural": "Miembros Sin Asignar", + "memberWithOverdueTaskCount": "Miembro Con Tarea Atrasada", + "memberWithOverdueTaskCountPlural": "Miembros Con Tareas Atrasadas", + + "teamsText": "Equipos", + + "nameColumn": "Nombre", + "projectsColumn": "Proyectos", + "membersColumn": "Miembros" +} diff --git a/worklenz-frontend/public/locales/es/reporting-projects-drawer.json b/worklenz-frontend/public/locales/es/reporting-projects-drawer.json new file mode 100644 index 00000000..1e056a29 --- /dev/null +++ b/worklenz-frontend/public/locales/es/reporting-projects-drawer.json @@ -0,0 +1,59 @@ +{ + "exportButton": "Exportar", + "membersButton": "Miembros", + "tasksButton": "Tareas", + "searchByNameInputPlaceholder": "Buscar por nombre", + + "overviewTab": "Resumen", + "membersTab": "Miembros", + "tasksTab": "Tareas", + + "completedTasksText": "Tareas Completadas", + "incompleteTasksText": "Tareas Incompletas", + "overdueTasksText": "Tareas Atrasadas", + "allocatedHoursText": "Horas Asignadas", + "loggedHoursText": "Horas Registradas", + + "tasksText": "Tareas", + "allText": "Todos", + + "tasksByStatusText": "Tareas por Estado", + "tasksByPriorityText": "Tareas por Prioridad", + "tasksByDueDateText": "Tareas por Fecha de Vencimiento", + + "todoText": "Por Hacer", + "doingText": "En Proceso", + "doneText": "Hecho", + "lowText": "Baja", + "mediumText": "Media", + "highText": "Alta", + "completedText": "Completado", + "upcomingText": "Próximo", + "overdueText": "Atrasado", + "noDueDateText": "Sin Fecha de Vencimiento", + + "nameColumn": "Nombre", + "tasksCountColumn": "Cantidad de Tareas", + "completedTasksColumn": "Tareas Completadas", + "incompleteTasksColumn": "Tareas Incompletas", + "overdueTasksColumn": "Tareas Atrasadas", + "contributionColumn": "Contribución", + "progressColumn": "Progreso", + "loggedTimeColumn": "Tiempo Registrado", + "taskColumn": "Tarea", + "projectColumn": "Proyecto", + "statusColumn": "Estado", + "priorityColumn": "Prioridad", + "phaseColumn": "Fase", + "dueDateColumn": "Fecha de Vencimiento", + "completedDateColumn": "Fecha de Finalización", + "estimatedTimeColumn": "Tiempo Estimado", + "overloggedTimeColumn": "Tiempo Excedido", + "completedOnColumn": "Completado El", + "daysOverdueColumn": "Días de Retraso", + + "groupByText": "Agrupar Por:", + "statusText": "Estado", + "priorityText": "Prioridad", + "phaseText": "Fase" +} diff --git a/worklenz-frontend/public/locales/es/reporting-projects-filters.json b/worklenz-frontend/public/locales/es/reporting-projects-filters.json new file mode 100644 index 00000000..1a4df4af --- /dev/null +++ b/worklenz-frontend/public/locales/es/reporting-projects-filters.json @@ -0,0 +1,35 @@ +{ + "searchByNamePlaceholder": "Buscar por nombre", + "searchByCategoryPlaceholder": "Buscar por categoría", + + "statusText": "Estado", + "healthText": "Salud", + "categoryText": "Categoría", + "projectManagerText": "Gerente de Proyecto", + "showFieldsText": "Mostrar campos", + + "cancelledText": "Cancelado", + "blockedText": "Bloqueado", + "onHoldText": "En Espera", + "proposedText": "Propuesto", + "inPlanningText": "En Planificación", + "inProgressText": "En Progreso", + "completedText": "Completado", + "continuousText": "Continuo", + + "notSetText": "No Establecido", + "needsAttentionText": "Necesita Atención", + "atRiskText": "En Riesgo", + "goodText": "Bien", + + "nameText": "Proyecto", + "estimatedVsActualText": "Estimado vs Real", + "tasksProgressText": "Progreso de Tareas", + "lastActivityText": "Última Actividad", + "datesText": "Fechas de Inicio/Fin", + "daysLeftText": "Días Restantes/Atrasados", + "projectHealthText": "Salud del Proyecto", + "projectUpdateText": "Actualización del Proyecto", + "clientText": "Cliente", + "teamText": "Equipo" +} diff --git a/worklenz-frontend/public/locales/es/reporting-projects.json b/worklenz-frontend/public/locales/es/reporting-projects.json new file mode 100644 index 00000000..fbd9283f --- /dev/null +++ b/worklenz-frontend/public/locales/es/reporting-projects.json @@ -0,0 +1,52 @@ +{ + "projectCount": "Proyecto", + "projectCountPlural": "Proyectos", + "includeArchivedButton": "Incluir Proyectos Archivados", + "exportButton": "Exportar", + "excelButton": "Excel", + + "projectColumn": "Proyecto", + "estimatedVsActualColumn": "Estimado vs Real", + "tasksProgressColumn": "Progreso de Tareas", + "lastActivityColumn": "Última Actividad", + "statusColumn": "Estado", + "datesColumn": "Fechas Inicio/Fin", + "daysLeftColumn": "Días Restantes/Atrasados", + "projectHealthColumn": "Salud del Proyecto", + "categoryColumn": "Categoría", + "projectUpdateColumn": "Actualización del Proyecto", + "clientColumn": "Cliente", + "teamColumn": "Equipo", + "projectManagerColumn": "Gerente de Proyecto", + + "openButton": "Abrir", + + "estimatedText": "Estimado", + "actualText": "Real", + + "todoText": "Por Hacer", + "doingText": "En Proceso", + "doneText": "Terminado", + + "cancelledText": "Cancelado", + "blockedText": "Bloqueado", + "onHoldText": "En Espera", + "proposedText": "Propuesto", + "inPlanningText": "En Planificación", + "inProgressText": "En Progreso", + "completedText": "Completado", + "continuousText": "Continuo", + + "daysLeftText": "días restantes", + "dayLeftText": "día restante", + "daysOverdueText": "días de retraso", + + "notSetText": "No Establecido", + "needsAttentionText": "Necesita Atención", + "atRiskText": "En Riesgo", + "goodText": "Bien", + + "setCategoryText": "Establecer Categoría", + "searchByNameInputPlaceholder": "Buscar por nombre", + "todayText": "Hoy" +} diff --git a/worklenz-frontend/public/locales/es/reporting-sidebar.json b/worklenz-frontend/public/locales/es/reporting-sidebar.json new file mode 100644 index 00000000..d5e89788 --- /dev/null +++ b/worklenz-frontend/public/locales/es/reporting-sidebar.json @@ -0,0 +1,8 @@ +{ + "overviewText": "Resumen", + "projectsText": "Proyectos", + "membersText": "Miembros", + "timeReportsText": "Informes de Tiempo", + "estimateVsActualText": "Estimado vs Real", + "currentOrganizationTooltip": "Organización actual" +} diff --git a/worklenz-frontend/public/locales/es/schedule.json b/worklenz-frontend/public/locales/es/schedule.json new file mode 100644 index 00000000..5b24c1f4 --- /dev/null +++ b/worklenz-frontend/public/locales/es/schedule.json @@ -0,0 +1,39 @@ +{ + "today": "Hoy", + "week": "Semana", + "month": "Mes", + + "settings": "Configuración", + "workingDays": "Días laborables", + "monday": "Lunes", + "tuesday": "Martes", + "wednesday": "Miércoles", + "thursday": "Jueves", + "friday": "Viernes", + "saturday": "Sábado", + "sunday": "Domingo", + "workingHours": "Horas laborables", + "hours": "horas", + "saveButton": "Guardar", + + "totalAllocation": "Asignación Total", + "timeLogged": "Tiempo Registrado", + "remainingTime": "Tiempo Restante", + "total": "Total", + "perDay": "Por Día", + "tasks": "tareas", + "startDate": "Fecha de Inicio", + "endDate": "Fecha de Fin", + + "hoursPerDay": "Horas Por Día", + "totalHours": "Horas Totales", + "deleteButton": "Eliminar", + "cancelButton": "Cancelar", + + "tabTitle": "Tarea sin Fechas de Inicio y Fin", + + "allocatedTime": "Tiempo Asignado", + "totalLogged": "Total Registrado", + "loggedBillable": "Registrado Facturable", + "loggedNonBillable": "Registrado No Facturable" +} diff --git a/worklenz-frontend/public/locales/es/settings/categories.json b/worklenz-frontend/public/locales/es/settings/categories.json new file mode 100644 index 00000000..417e17dd --- /dev/null +++ b/worklenz-frontend/public/locales/es/settings/categories.json @@ -0,0 +1,10 @@ +{ + "categoryColumn": "Category", + "deleteConfirmationTitle": "Are you sure?", + "deleteConfirmationOk": "Yes", + "deleteConfirmationCancel": "Cancel", + "associatedTaskColumn": "Associated Task", + "searchPlaceholder": "Search by name", + "emptyText": "Categories can be created while updating or creating projects.", + "colorChangeTooltip": "Click to change color" +} diff --git a/worklenz-frontend/public/locales/es/settings/change-password.json b/worklenz-frontend/public/locales/es/settings/change-password.json new file mode 100644 index 00000000..e52b9aef --- /dev/null +++ b/worklenz-frontend/public/locales/es/settings/change-password.json @@ -0,0 +1,15 @@ +{ + "title": "Cambiar Contraseña", + "currentPassword": "Contraseña Actual", + "newPassword": "Nueva Contraseña", + "confirmPassword": "Confirmar Contraseña", + "currentPasswordPlaceholder": "Introduce tu contraseña actual", + "newPasswordPlaceholder": "Nueva Contraseña", + "confirmPasswordPlaceholder": "Confirmar Contraseña", + "currentPasswordRequired": "¡Por favor, introduce tu contraseña actual!", + "newPasswordRequired": "¡Por favor, introduce tu nueva contraseña!", + "passwordValidationError": "La contraseña debe tener al menos 8 caracteres con una letra mayúscula, un número y un símbolo.", + "passwordMismatch": "¡Las contraseñas no coinciden!", + "passwordRequirements": "La nueva contraseña debe tener un mínimo de 8 caracteres, con una letra mayúscula, un número y un símbolo.", + "updateButton": "Actualizar Contraseña" +} diff --git a/worklenz-frontend/public/locales/es/settings/clients.json b/worklenz-frontend/public/locales/es/settings/clients.json new file mode 100644 index 00000000..ca206be1 --- /dev/null +++ b/worklenz-frontend/public/locales/es/settings/clients.json @@ -0,0 +1,22 @@ +{ + "nameColumn": "Nombre", + "projectColumn": "Proyecto", + "noProjectsAvailable": "No hay proyectos disponibles", + "deleteConfirmationTitle": "¿Estás seguro?", + "deleteConfirmationOk": "Sí", + "deleteConfirmationCancel": "Cancelar", + "searchPlaceholder": "Buscar por nombre", + "createClient": "Crear Cliente", + "pinTooltip": "Haz clic para fijar esto en el menú principal", + "createClientDrawerTitle": "Crear Cliente", + "updateClientDrawerTitle": "Actualizar Cliente", + "nameLabel": "Nombre", + "namePlaceholder": "Nombre", + "nameRequiredError": "Por favor ingresa un nombre", + "createButton": "Crear", + "updateButton": "Actualizar", + "createClientSuccessMessage": "¡Cliente creado exitosamente!", + "createClientErrorMessage": "¡Error al crear el cliente!", + "updateClientSuccessMessage": "¡Cliente actualizado exitosamente!", + "updateClientErrorMessage": "¡Error al actualizar el cliente!" +} diff --git a/worklenz-frontend/public/locales/es/settings/job-titles.json b/worklenz-frontend/public/locales/es/settings/job-titles.json new file mode 100644 index 00000000..1b892d72 --- /dev/null +++ b/worklenz-frontend/public/locales/es/settings/job-titles.json @@ -0,0 +1,20 @@ +{ + "nameColumn": "Nombre", + "deleteConfirmationTitle": "¿Estás seguro?", + "deleteConfirmationOk": "Sí", + "deleteConfirmationCancel": "Cancelar", + "searchPlaceholder": "Buscar por nombre", + "createJobTitleButton": "Crear Cargo", + "pinTooltip": "Haz clic para fijar esto en el menú principal", + "createJobTitleDrawerTitle": "Crear Cargo", + "updateJobTitleDrawerTitle": "Actualizar Cargo", + "nameLabel": "Nombre", + "namePlaceholder": "Nombre", + "nameRequiredError": "Por favor ingresa un nombre", + "createButton": "Crear", + "updateButton": "Actualizar", + "createJobTitleSuccessMessage": "¡Cargo creado exitosamente!", + "createJobTitleErrorMessage": "¡Error al crear el cargo!", + "updateJobTitleSuccessMessage": "¡Cargo actualizado exitosamente!", + "updateJobTitleErrorMessage": "¡Error al actualizar el cargo!" +} diff --git a/worklenz-frontend/public/locales/es/settings/labels.json b/worklenz-frontend/public/locales/es/settings/labels.json new file mode 100644 index 00000000..22cd9532 --- /dev/null +++ b/worklenz-frontend/public/locales/es/settings/labels.json @@ -0,0 +1,11 @@ +{ + "labelColumn": "Etiqueta", + "deleteConfirmationTitle": "¿Estás seguro?", + "deleteConfirmationOk": "Sí", + "deleteConfirmationCancel": "Cancelar", + "associatedTaskColumn": "Cantidad de Tareas Asociadas", + "searchPlaceholder": "Buscar por nombre", + "emptyText": "Las etiquetas se pueden crear al actualizar o crear tareas.", + "pinTooltip": "Haz clic para fijar esto en el menú principal", + "colorChangeTooltip": "Haz clic para cambiar el color" +} diff --git a/worklenz-frontend/public/locales/es/settings/language.json b/worklenz-frontend/public/locales/es/settings/language.json new file mode 100644 index 00000000..e07fd933 --- /dev/null +++ b/worklenz-frontend/public/locales/es/settings/language.json @@ -0,0 +1,7 @@ +{ + "language": "Idioma", + "language_required": "El idioma es requerido", + "time_zone": "Zona horaria", + "time_zone_required": "La zona horaria es requerida", + "save_changes": "Guardar cambios" +} diff --git a/worklenz-frontend/public/locales/es/settings/notifications.json b/worklenz-frontend/public/locales/es/settings/notifications.json new file mode 100644 index 00000000..c7a5af22 --- /dev/null +++ b/worklenz-frontend/public/locales/es/settings/notifications.json @@ -0,0 +1,10 @@ +{ + "emailTitle": "Enviarme notificaciones por correo electrónico", + "emailDescription": "Esto incluye nuevas asignaciones de tareas", + "dailyDigestTitle": "Enviarme un resumen diario", + "dailyDigestDescription": "Cada tarde, recibirás un resumen de la actividad reciente en las tareas.", + "popupTitle": "Mostrar notificaciones emergentes en mi computadora cuando Worklenz esté abierto", + "popupDescription": "Las notificaciones emergentes pueden ser desactivadas por tu navegador. Cambia la configuración de tu navegador para permitirlas.", + "unreadItemsTitle": "Mostrar el número de elementos no leídos", + "unreadItemsDescription": "Verás contadores para cada notificación." +} diff --git a/worklenz-frontend/public/locales/es/settings/profile.json b/worklenz-frontend/public/locales/es/settings/profile.json new file mode 100644 index 00000000..9c43a470 --- /dev/null +++ b/worklenz-frontend/public/locales/es/settings/profile.json @@ -0,0 +1,13 @@ +{ + "uploadError": "¡Solo puedes subir archivos JPG/PNG!", + "uploadSizeError": "¡La imagen debe ser menor de 2MB!", + "upload": "Subir", + "nameLabel": "Nombre", + "nameRequiredError": "El nombre es obligatorio", + "emailLabel": "Correo electrónico", + "emailRequiredError": "El correo electrónico es obligatorio", + "saveChanges": "Guardar cambios", + "profileJoinedText": "Se unió hace un mes", + "profileLastUpdatedText": "Última actualización hace un mes", + "avatarTooltip": "Haz clic para subir un avatar" +} diff --git a/worklenz-frontend/public/locales/es/settings/project-templates.json b/worklenz-frontend/public/locales/es/settings/project-templates.json new file mode 100644 index 00000000..045f2240 --- /dev/null +++ b/worklenz-frontend/public/locales/es/settings/project-templates.json @@ -0,0 +1,8 @@ +{ + "nameColumn": "Nombre", + "editToolTip": "Editar", + "deleteToolTip": "Eliminar", + "confirmText": "¿Estás seguro?", + "okText": "Sí", + "cancelText": "Cancelar" +} diff --git a/worklenz-frontend/public/locales/es/settings/sidebar.json b/worklenz-frontend/public/locales/es/settings/sidebar.json new file mode 100644 index 00000000..32d529ea --- /dev/null +++ b/worklenz-frontend/public/locales/es/settings/sidebar.json @@ -0,0 +1,14 @@ +{ + "profile": "Perfil", + "notifications": "Notificaciones", + "clients": "Clientes", + "job-titles": "Títulos de trabajo", + "labels": "Etiquetas", + "categories": "Categorías", + "project-templates": "Plantillas de proyectos", + "task-templates": "Plantillas de tareas", + "team-members": "Miembros del equipo", + "teams": "Equipos", + "change-password": "Cambiar contraseña", + "language-and-region": "Idioma y región" +} diff --git a/worklenz-frontend/public/locales/es/settings/task-templates.json b/worklenz-frontend/public/locales/es/settings/task-templates.json new file mode 100644 index 00000000..fbdc3c81 --- /dev/null +++ b/worklenz-frontend/public/locales/es/settings/task-templates.json @@ -0,0 +1,9 @@ +{ + "nameColumn": "Nombre", + "createdColumn": "Creado", + "editToolTip": "Editar", + "deleteToolTip": "Eliminar", + "confirmText": "¿Estás seguro?", + "okText": "Sí", + "cancelText": "Cancelar" +} diff --git a/worklenz-frontend/public/locales/es/settings/team-members.json b/worklenz-frontend/public/locales/es/settings/team-members.json new file mode 100644 index 00000000..8de73b84 --- /dev/null +++ b/worklenz-frontend/public/locales/es/settings/team-members.json @@ -0,0 +1,44 @@ +{ + "nameColumn": "Nombre", + "projectsColumn": "Proyectos", + "emailColumn": "Correo electrónico", + "teamAccessColumn": "Acceso al equipo", + "memberCount": "Miembro", + "membersCountPlural": "Miembros", + "searchPlaceholder": "Buscar miembros por nombre", + "pinTooltip": "Actualizar lista de miembros", + "addMemberButton": "Agregar nuevo miembro", + "editTooltip": "Editar miembro", + "deactivateTooltip": "Desactivar miembro", + "activateTooltip": "Activar miembro", + "deleteTooltip": "Eliminar miembro", + "confirmDeleteTitle": "¿Está seguro de que desea eliminar este miembro?", + "confirmActivateTitle": "¿Está seguro de que desea cambiar el estado de este miembro?", + "okText": "Sí, continuar", + "cancelText": "No, cancelar", + "deactivatedText": "(Actualmente desactivado)", + "pendingInvitationText": "(Invitación pendiente)", + "addMemberDrawerTitle": "Agregar nuevo miembro del equipo", + "updateMemberDrawerTitle": "Actualizar miembro del equipo", + "addMemberEmailHint": "Los miembros se agregarán al equipo independientemente del estado de aceptación de la invitación", + "memberEmailLabel": "Dirección(es) de correo electrónico", + "memberEmailPlaceholder": "Ingrese la dirección de correo electrónico del miembro del equipo", + "memberEmailRequiredError": "Por favor, ingrese una dirección de correo electrónico válida", + "jobTitleLabel": "Cargo", + "jobTitlePlaceholder": "Seleccione o busque cargo (Opcional)", + "memberAccessLabel": "Nivel de acceso", + "addToTeamButton": "Agregar miembro al equipo", + "updateButton": "Guardar cambios", + "resendInvitationButton": "Reenviar correo de invitación", + "invitationSentSuccessMessage": "¡Invitación al equipo enviada exitosamente!", + "createMemberSuccessMessage": "¡Nuevo miembro del equipo agregado exitosamente!", + "createMemberErrorMessage": "Error al agregar miembro del equipo. Por favor, intente nuevamente.", + "updateMemberSuccessMessage": "¡Miembro del equipo actualizado exitosamente!", + "updateMemberErrorMessage": "Error al actualizar miembro del equipo. Por favor, intente nuevamente.", + "memberText": "Miembro del equipo", + "adminText": "Administrador", + "ownerText": "Propietario del equipo", + "addedText": "Agregado", + "updatedText": "Actualizado", + "noResultFound": "Escriba una dirección de correo electrónico y presione enter..." +} diff --git a/worklenz-frontend/public/locales/es/task-drawer/task-drawer-info-tab.json b/worklenz-frontend/public/locales/es/task-drawer/task-drawer-info-tab.json new file mode 100644 index 00000000..58c5715e --- /dev/null +++ b/worklenz-frontend/public/locales/es/task-drawer/task-drawer-info-tab.json @@ -0,0 +1,29 @@ +{ + "details": { + "task-key": "Clave de tarea", + "phase": "Fase", + "assignees": "Asignados", + "due-date": "Fecha de vencimiento", + "time-estimation": "Estimación de tiempo", + "priority": "Prioridad", + "labels": "Etiquetas", + "billable": "Facturable", + "notify": "Notificar", + "when-done-notify": "Al terminar, notificar", + "start-date": "Fecha de inicio", + "end-date": "Fecha de finalización", + "hide-start-date": "Ocultar fecha de inicio", + "show-start-date": "Mostrar fecha de inicio", + "hours": "Horas", + "minutes": "Minutos" + }, + "description": { + "title": "Descripción", + "placeholder": "Añadir una descripción más detallada..." + }, + "subTasks": { + "title": "Subtareas", + "add-sub-task": "+ Añadir subtarea", + "refresh-sub-tasks": "Actualizar subtareas" + } +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/es/task-drawer/task-drawer.json b/worklenz-frontend/public/locales/es/task-drawer/task-drawer.json new file mode 100644 index 00000000..387968e9 --- /dev/null +++ b/worklenz-frontend/public/locales/es/task-drawer/task-drawer.json @@ -0,0 +1,78 @@ +{ + "taskHeader": { + "taskNamePlaceholder": "Escribe tu tarea", + "deleteTask": "Eliminar tarea" + }, + "taskInfoTab": { + "title": "Información", + "details": { + "title": "Detalles", + "task-key": "Clave de tarea", + "phase": "Fase", + "assignees": "Asignados", + "due-date": "Fecha de vencimiento", + "time-estimation": "Estimación de tiempo", + "priority": "Prioridad", + "labels": "Etiquetas", + "billable": "Facturable", + "notify": "Notificar", + "when-done-notify": "Al terminar, notificar", + "start-date": "Fecha de inicio", + "end-date": "Fecha de finalización", + "hide-start-date": "Ocultar fecha de inicio", + "show-start-date": "Mostrar fecha de inicio", + "hours": "Horas", + "minutes": "Minutos" + }, + "labels": { + "labelInputPlaceholder": "Buscar o crear", + "labelsSelectorInputTip": "Pulse Enter para crear" + }, + "description": { + "title": "Descripción", + "placeholder": "Añadir una descripción más detallada..." + }, + "subTasks": { + "title": "Subtareas", + "addSubTask": "+ Añadir subtarea", + "addSubTaskInputPlaceholder": "Escribe tu tarea y pulsa enter", + "refreshSubTasks": "Actualizar subtareas", + "edit": "Editar", + "delete": "Eliminar", + "confirmDeleteSubTask": "¿Estás seguro de que quieres eliminar esta subtarea?", + "deleteSubTask": "Eliminar subtarea" + }, + "dependencies": { + "title": "Dependencias", + "addDependency": "+ Añadir nueva dependencia", + "blockedBy": "Bloqueado por", + "searchTask": "Escribe para buscar tarea", + "noTasksFound": "No se encontraron tareas", + "confirmDeleteDependency": "¿Estás seguro de que quieres eliminar?" + }, + "attachments": { + "title": "Adjuntos", + "chooseOrDropFileToUpload": "Elige o arrastra un archivo para subir", + "uploading": "Subiendo..." + }, + "comments": { + "title": "Comentarios", + "addComment": "+ Añadir nuevo comentario", + "noComments": "No hay comentarios todavía. ¡Sé el primero en comentar!", + "delete": "Eliminar", + "confirmDeleteComment": "¿Estás seguro de que quieres eliminar este comentario?" + }, + "searchInputPlaceholder": "Buscar por nombre", + "pendingInvitation": "Invitación pendiente" + }, + "taskTimeLogTab": { + "title": "Registro de tiempo", + "addTimeLog": "Añadir nuevo registro de tiempo", + "totalLogged": "Total registrado", + "exportToExcel": "Exportar a Excel", + "noTimeLogsFound": "No se encontraron registros de tiempo" + }, + "taskActivityLogTab": { + "title": "Registro de actividad" + } +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/es/task-list-filters.json b/worklenz-frontend/public/locales/es/task-list-filters.json new file mode 100644 index 00000000..c6cf8c99 --- /dev/null +++ b/worklenz-frontend/public/locales/es/task-list-filters.json @@ -0,0 +1,55 @@ +{ + "searchButton": "Buscar", + "resetButton": "Restablecer", + "searchInputPlaceholder": "Buscar por nombre", + + "sortText": "Ordenar", + "statusText": "Estado", + "phaseText": "Fase", + "priorityText": "Prioridad", + "labelsText": "Etiquetas", + "membersText": "Miembros", + "groupByText": "Agrupar por", + "showArchivedText": "Mostrar archivados", + "showFieldsText": "Mostrar campos", + "keyText": "Clave", + "taskText": "Tarea", + "descriptionText": "Descripción", + "phasesText": "Fases", + "progressText": "Progreso", + "timeTrackingText": "Seguimiento de tiempo", + "estimationText": "Estimación", + "startDateText": "Fecha de inicio", + "endDateText": "Fecha de fin", + "dueDateText": "Fecha de vencimiento", + "completedDateText": "Fecha de finalización", + "createdDateText": "Fecha de creación", + "lastUpdatedText": "Última actualización", + "reporterText": "Reportero", + "dueTimeText": "Hora de vencimiento", + "lowText": "Baja", + "mediumText": "Media", + "highText": "Alta", + "assigneesText": "Asignados", + "timetrackingText": "Seguimiento de tiempo", + "startdateText": "Fecha de inicio", + "duedateText": "Fecha de vencimiento", + "completeddateText": "Fecha de finalización", + "createddateText": "Fecha de creación", + "lastupdatedText": "Última actualización", + "duetimeText": "Hora de vencimiento", + "createStatusButtonTooltip": "Configuración de estados", + "configPhaseButtonTooltip": "Configuración de fases", + "noLabelsFound": "No se encontraron etiquetas", + + "addStatusButton": "Agregar estado", + "addPhaseButton": "Agregar fase", + + "createStatus": "Crear estado", + "name": "Nombre", + "category": "Categoría", + "selectCategory": "Seleccionar una categoría", + "pleaseEnterAName": "Por favor, ingrese un nombre", + "pleaseSelectACategory": "Por favor, seleccione una categoría", + "create": "Crear" +} diff --git a/worklenz-frontend/public/locales/es/task-list-table.json b/worklenz-frontend/public/locales/es/task-list-table.json new file mode 100644 index 00000000..659cb8c1 --- /dev/null +++ b/worklenz-frontend/public/locales/es/task-list-table.json @@ -0,0 +1,63 @@ +{ + "keyColumn": "Clave", + "taskColumn": "Tarea", + "descriptionColumn": "Descripción", + "progressColumn": "Progreso", + "membersColumn": "Miembros", + "assigneesColumn": "Asignados", + "labelsColumn": "Etiquetas", + "phasesColumn": "Fases", + "phaseColumn": "Fase", + "statusColumn": "Estado", + "priorityColumn": "Prioridad", + "timeTrackingColumn": "Seguimiento de tiempo", + "timetrackingColumn": "Seguimiento de tiempo", + "estimationColumn": "Estimación", + "startDateColumn": "Fecha de inicio", + "startdateColumn": "Fecha de inicio", + "dueDateColumn": "Fecha de vencimiento", + "duedateColumn": "Fecha de vencimiento", + "completedDateColumn": "Fecha de completado", + "completeddateColumn": "Fecha de completado", + "createdDateColumn": "Fecha de creación", + "createddateColumn": "Fecha de creación", + "lastUpdatedColumn": "Última actualización", + "lastupdatedColumn": "Última actualización", + "reporterColumn": "Reportador", + "dueTimeColumn": "Hora de vencimiento", + "todoSelectorText": "Por hacer", + "doingSelectorText": "En progreso", + "doneSelectorText": "Completado", + + "lowSelectorText": "Baja", + "mediumSelectorText": "Media", + "highSelectorText": "Alta", + + "selectText": "Seleccionar", + "labelsSelectorInputTip": "¡Presiona enter para crear!", + + "addTaskText": "+ Agregar tarea", + "addSubTaskText": "+ Agregar subtarea", + "addTaskInputPlaceholder": "Escribe tu tarea y presiona enter", + + "openButton": "Abrir", + "okButton": "Aceptar", + + "noLabelsFound": "No se encontraron etiquetas", + "searchInputPlaceholder": "Buscar o crear", + "assigneeSelectorInviteButton": "Invitar a un nuevo miembro por correo", + "labelInputPlaceholder": "Buscar o crear", + + "pendingInvitation": "Invitación pendiente", + + "contextMenu": { + "assignToMe": "Asignar a mí", + "moveTo": "Mover a", + "unarchive": "Desarchivar", + "archive": "Archivar", + "convertToSubTask": "Convertir en subtarea", + "convertToTask": "Convertir en tarea", + "delete": "Eliminar", + "searchByNameInputPlaceholder": "Buscar por nombre" + } +} diff --git a/worklenz-frontend/public/locales/es/task-template-drawer.json b/worklenz-frontend/public/locales/es/task-template-drawer.json new file mode 100644 index 00000000..a3bfc45b --- /dev/null +++ b/worklenz-frontend/public/locales/es/task-template-drawer.json @@ -0,0 +1,11 @@ +{ + "createTaskTemplate": "Crear Plantilla de Tarea", + "editTaskTemplate": "Editar Plantilla de Tarea", + "cancelText": "Cancelar", + "saveText": "Guardar", + "templateNameText": "Nombre de la Plantilla", + "selectedTasks": "Tareas Seleccionadas", + "removeTask": "Eliminar", + "cancelButton": "Cancelar", + "saveButton": "Guardar" +} diff --git a/worklenz-frontend/public/locales/es/tasks/task-table-bulk-actions.json b/worklenz-frontend/public/locales/es/tasks/task-table-bulk-actions.json new file mode 100644 index 00000000..0040c407 --- /dev/null +++ b/worklenz-frontend/public/locales/es/tasks/task-table-bulk-actions.json @@ -0,0 +1,24 @@ +{ + "taskSelected": "Tarea seleccionada", + "tasksSelected": "Tareas seleccionadas", + "changeStatus": "Cambiar estado/ prioridad/ fases", + "changeLabel": "Cambiar etiqueta", + "assignToMe": "Asignar a mí", + "changeAssignees": "Cambiar asignados", + "archive": "Archivar", + "unarchive": "Desarchivar", + "delete": "Eliminar", + "moreOptions": "Más opciones", + "deselectAll": "Deseleccionar todo", + "status": "Estado", + "priority": "Prioridad", + "phase": "Fase", + "member": "Miembro", + "createTaskTemplate": "Crear plantilla de tarea", + "apply": "Aplicar", + "createLabel": "+ Crear etiqueta", + "hitEnterToCreate": "Presione Enter para crear", + "pendingInvitation": "Invitación Pendiente", + "noMatchingLabels": "No hay etiquetas coincidentes", + "noLabels": "Sin etiquetas" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/es/template-drawer.json b/worklenz-frontend/public/locales/es/template-drawer.json new file mode 100644 index 00000000..7c6c7f3d --- /dev/null +++ b/worklenz-frontend/public/locales/es/template-drawer.json @@ -0,0 +1,19 @@ +{ + "title": "Editar Plantilla de Tarea", + "cancelText": "Cancelar", + "saveText": "Guardar", + "templateNameText": "Nombre de la Plantilla", + "selectedTasks": "Tareas Seleccionadas", + "removeTask": "Eliminar", + "description": "Descripción", + "phase": "Fase", + "statuses": "Estados", + "priorities": "Prioridades", + "labels": "Etiquetas", + "tasks": "Tareas", + "noTemplateSelected": "No hay plantilla seleccionada", + "noDescription": "Sin descripción", + "worklenzTemplates": "Plantillas de Worklenz", + "yourTemplatesLibrary": "Tu Biblioteca", + "searchTemplates": "Buscar Plantillas" +} diff --git a/worklenz-frontend/public/locales/es/templateDrawer.json b/worklenz-frontend/public/locales/es/templateDrawer.json new file mode 100644 index 00000000..8028f0cd --- /dev/null +++ b/worklenz-frontend/public/locales/es/templateDrawer.json @@ -0,0 +1,23 @@ +{ + "bugTracking": "Seguimiento de Errores", + "construction": "Construcción", + "designCreative": "Diseño y Creatividad", + "education": "Educación", + "finance": "Finanzas", + "hrRecruiting": "RRHH y Reclutamiento", + "informationTechnology": "Tecnología de la Información", + "legal": "Legal", + "manufacturing": "Fabricación", + "marketing": "Marketing", + "nonprofit": "Sin fines de lucro", + "personalUse": "Uso personal", + "salesCRM": "Ventas y CRM", + "serviceConsulting": "Servicios y Consultoría", + "softwareDevelopment": "Desarrollo de Software", + "description": "Descripción", + "phase": "Fase", + "statuses": "Estados", + "priorities": "Prioridades", + "labels": "Etiquetas", + "tasks": "Tareas" +} diff --git a/worklenz-frontend/public/locales/es/time-report.json b/worklenz-frontend/public/locales/es/time-report.json new file mode 100644 index 00000000..a602ec1d --- /dev/null +++ b/worklenz-frontend/public/locales/es/time-report.json @@ -0,0 +1,44 @@ +{ + "includeArchivedProjects": "Incluir Proyectos Archivados", + "export": "Exportar", + "timeSheet": "Hoja de Tiempo", + + "searchByName": "Buscar por nombre", + "selectAll": "Seleccionar Todo", + "teams": "Equipos", + + "searchByProject": "Buscar por nombre de proyecto", + "projects": "Proyectos", + + "searchByCategory": "Buscar por nombre de categoría", + "categories": "Categorías", + + "billable": "Facturable", + "nonBillable": "No Facturable", + + "total": "Total", + + "projectsTimeSheet": "Hoja de Tiempo de Proyectos", + + "loggedTime": "Tiempo Registrado(horas)", + + "exportToExcel": "Exportar a Excel", + "logged": "registrado", + "for": "para", + + "membersTimeSheet": "Hoja de Tiempo de Miembros", + "member": "Miembro", + + "estimatedVsActual": "Estimado vs Real", + "workingDays": "Días Laborables", + "manDays": "Días Hombre", + "days": "Días", + "estimatedDays": "Días Estimados", + "actualDays": "Días Reales", + + "noCategories": "No se encontraron categorías", + "noCategory": "No Categoría", + "noProjects": "No se encontraron proyectos", + "noTeams": "No se encontraron equipos", + "noData": "No se encontraron datos" +} diff --git a/worklenz-frontend/public/locales/es/unauthorized.json b/worklenz-frontend/public/locales/es/unauthorized.json new file mode 100644 index 00000000..586fed6b --- /dev/null +++ b/worklenz-frontend/public/locales/es/unauthorized.json @@ -0,0 +1,5 @@ +{ + "title": "¡No autorizado!", + "subtitle": "No tienes permisos para acceder a esta página", + "button": "Ir a Inicio" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/404-page.json b/worklenz-frontend/public/locales/pt/404-page.json new file mode 100644 index 00000000..638d30b3 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/404-page.json @@ -0,0 +1,4 @@ +{ + "doesNotExistText": "Desculpe, a página que você visitou não existe.", + "backHomeButton": "Voltar ao Início" +} diff --git a/worklenz-frontend/public/locales/pt/account-setup.json b/worklenz-frontend/public/locales/pt/account-setup.json new file mode 100644 index 00000000..1d8a8cba --- /dev/null +++ b/worklenz-frontend/public/locales/pt/account-setup.json @@ -0,0 +1,32 @@ +{ + "continue": "Continuar", + + "setupYourAccount": "Configure sua conta.", + "organizationStepTitle": "Nomeie sua organização", + "organizationStepLabel": "Escolha um nome para sua conta Worklenz.", + + "projectStepTitle": "Crie seu primeiro projeto", + "projectStepLabel": "Em qual projeto você está trabalhando agora?", + "projectStepPlaceholder": "ex. Plano de Marketing", + + "step2Title": "Crie suas primeiras tarefas", + "step2InputLabel": "Digite algumas tarefas que você vai fazer em", + "step2AddAnother": "Adicionar outro", + + "emailPlaceholder": "Endereço de e-mail", + "invalidEmail": "Por favor, insira um endereço de e-mail válido", + "or": "ou", + "templateButton": "Importar do modelo", + "goBack": "Voltar", + "cancel": "Cancelar", + "create": "Criar", + "templateDrawerTitle": "Selecionar dos modelos", + "step3InputLabel": "Convidar por email", + "addAnother": "Adicionar outro", + "skipForNow": "Pular por enquanto", + "formTitle": "Crie sua primeira tarefa.", + "step3Title": "Convide sua equipe para trabalhar", + + "maxMembers": " (Você pode convidar até 5 membros)", + "maxTasks": " (Você pode criar até 5 tarefas)" +} diff --git a/worklenz-frontend/public/locales/pt/admin-center/current-bill.json b/worklenz-frontend/public/locales/pt/admin-center/current-bill.json new file mode 100644 index 00000000..c4bc4126 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/admin-center/current-bill.json @@ -0,0 +1,105 @@ +{ + "title": "Cobranças", + "currentBill": "Fatura Atual", + "configuration": "Configuração", + "currentPlanDetails": "Detalhes do Plano Atual", + "upgradePlan": "Atualizar Plano", + "cardBodyText01": "Teste gratuito", + "cardBodyText02": "(Seu plano de teste expira em 1 mês e 19 dias)", + "redeemCode": "Resgatar Código", + "accountStorage": "Armazenamento da Conta", + "used": "Usado:", + "remaining": "Restante:", + "charges": "Cobranças", + "tooltip": "Cobranças para o ciclo de faturamento atual", + "description": "Descrição", + "billingPeriod": "Período de Faturamento", + "billStatus": "Status da Fatura", + "perUserValue": "Valor por Usuário", + "users": "Usuários", + "amount": "Valor", + "invoices": "Faturas", + "transactionId": "ID da Transação", + "transactionDate": "Data da Transação", + "paymentMethod": "Método de Pagamento", + "status": "Status", + "ltdUsers": "Puedes agregar hasta {{ltd_users}} usuarios.", + + "drawerTitle": "Resgatar Código", + "label": "Resgatar Código", + "drawerPlaceholder": "Digite seu código de resgate", + "redeemSubmit": "Enviar", + + "modalTitle": "Selecione o melhor plano para sua equipe", + "seatLabel": "Número de assentos", + "freePlan": "Plano Gratuito", + "startup": "Startup", + "business": "Empresarial", + "tag": "Mais Popular", + "enterprise": "Enterprise", + + "freeSubtitle": "gratuito para sempre", + "freeUsers": "Melhor para uso pessoal", + "freeText01": "100MB de armazenamento", + "freeText02": "3 projetos", + "freeText03": "5 membros na equipe", + + "startupSubtitle": "TAXA FIXA / mês", + "startupUsers": "Até 15 usuários", + "startupText01": "25GB de armazenamento", + "startupText02": "Projetos ativos ilimitados", + "startupText03": "Agendamento", + "startupText04": "Relatórios", + "startupText05": "Inscrever-se em projetos", + + "businessSubtitle": "usuário / mês", + "businessUsers": "16 - 200 usuários", + + "enterpriseUsers": "200 - 500+ usuários", + + "footerTitle": "Por favor, forneça um número de contato para que possamos entrar em contato com você.", + "footerLabel": "Número de Contato", + "footerButton": "Contate-nos", + + "redeemCodePlaceHolder": "Digite seu código de resgate", + "submit": "Enviar", + + "trialPlan": "Plano de Teste", + "trialExpireDate": "Válido até {{trial_expire_date}}", + "trialExpired": "Sua prova gratuita expirou {{trial_expire_string}}", + "trialInProgress": "Sua prova gratuita expira {{trial_expire_string}}", + + "required": "Este campo é obrigatório", + "invalidCode": "Código inválido", + + "selectPlan": "Selecione o melhor plano para sua equipe", + "changeSubscriptionPlan": "Mude seu plano de assinatura", + "noOfSeats": "Número de assentos", + "annualPlan": "Pro - Anual", + "monthlyPlan": "Pro - Mensal", + "freeForever": "Gratis para sempre", + "bestForPersonalUse": "Melhor para uso pessoal", + "storage": "Armazenamento", + "projects": "Projetos", + "teamMembers": "Membros da equipe", + "unlimitedTeamMembers": "Membros da equipe ilimitados", + "unlimitedActiveProjects": "Projetos ativos ilimitados", + "schedule": "Agendamento", + "reporting": "Relatórios", + "subscribeToProjects": "Inscreva-se em projetos", + "billedAnnually": "Faturado Anualmente", + "billedMonthly": "Faturado Mensalmente", + + "pausePlan": "Pausar Plano", + "resumePlan": "Reanudar Plano", + "changePlan": "Mudar Plano", + "cancelPlan": "Cancelar Plano", + + "perMonthPerUser": "por usuário / mês", + "viewInvoice": "Ver Fatura", + "switchToFreePlan": "Mudar para Plano Gratuito", + + "expirestoday": "hoje", + "expirestomorrow": "amanhã", + "expiredDaysAgo": "há {{days}} dias" +} diff --git a/worklenz-frontend/public/locales/pt/admin-center/overview.json b/worklenz-frontend/public/locales/pt/admin-center/overview.json new file mode 100644 index 00000000..7cce8587 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/admin-center/overview.json @@ -0,0 +1,8 @@ +{ + "overview": "Visão Geral", + "name": "Nome da Organização", + "owner": "Proprietário da Organização", + "admins": "Administradores da Organização", + "contactNumber": "Adicione o Número de Contato", + "edit": "Editar" +} diff --git a/worklenz-frontend/public/locales/pt/admin-center/projects.json b/worklenz-frontend/public/locales/pt/admin-center/projects.json new file mode 100644 index 00000000..02fdc0bb --- /dev/null +++ b/worklenz-frontend/public/locales/pt/admin-center/projects.json @@ -0,0 +1,12 @@ +{ + "membersCount": "Contagem de Membros", + "createdAt": "Criado em", + "projectName": "Nome do Projeto", + "teamName": "Nome do Time", + "refreshProjects": "Atualizar Projetos", + "searchPlaceholder": "Pesquisar por nome do projeto", + "deleteProject": "Tem a certeza de que deseja deletar este projeto?", + "confirm": "Confirmar", + "cancel": "Cancelar", + "delete": "Deletar Projeto" +} diff --git a/worklenz-frontend/public/locales/pt/admin-center/sidebar.json b/worklenz-frontend/public/locales/pt/admin-center/sidebar.json new file mode 100644 index 00000000..253b77e4 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/admin-center/sidebar.json @@ -0,0 +1,8 @@ +{ + "overview": "Visão Geral", + "users": "Usuários", + "teams": "Equipes", + "billing": "Faturamento", + "projects": "Projetos", + "adminCenter": "Central Administrativa" +} diff --git a/worklenz-frontend/public/locales/pt/admin-center/teams.json b/worklenz-frontend/public/locales/pt/admin-center/teams.json new file mode 100644 index 00000000..fea4c874 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/admin-center/teams.json @@ -0,0 +1,33 @@ +{ + "title": "Equipes", + "subtitle": "equipes", + "tooltip": "Atualizar equipes", + "placeholder": "Pesquisar por nome", + "addTeam": "Adicionar Equipe", + "team": "Equipe", + "membersCount": "Contagem de Membros", + "members": "Membros", + "drawerTitle": "Criar Nova Equipe", + "label": "Nome da Equipe", + "drawerPlaceholder": "Nome", + "create": "Criar", + "delete": "Deletar", + "settings": "Configurações", + "popTitle": "Tem a certeza?", + "message": "Por favor, insira um Nome", + "teamSettings": "Configurações da Equipe", + "teamName": "Nome da Equipe", + "teamDescription": "Descrição da Equipe", + "teamMembers": "Membros da Equipe", + "teamMembersCount": "Quantidade de Membros da Equipe", + "teamMembersPlaceholder": "Buscar por nome", + "addMember": "Adicionar Membro", + "add": "Adicionar", + "update": "Atualizar", + "teamNamePlaceholder": "Nome da Equipe", + "user": "Usuário", + "role": "Rol", + "owner": "Propietario", + "admin": "Administrador", + "member": "Miembro" +} diff --git a/worklenz-frontend/public/locales/pt/admin-center/users.json b/worklenz-frontend/public/locales/pt/admin-center/users.json new file mode 100644 index 00000000..9826c548 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/admin-center/users.json @@ -0,0 +1,9 @@ +{ + "title": "Usuários", + "subTitle": "usuários", + "placeholder": "Pesquisar por nome", + "user": "Usuário", + "email": "Email", + "lastActivity": "Última Atividade", + "refresh": "Atualizar usuários" +} diff --git a/worklenz-frontend/public/locales/pt/all-project-list.json b/worklenz-frontend/public/locales/pt/all-project-list.json new file mode 100644 index 00000000..a97f5223 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/all-project-list.json @@ -0,0 +1,23 @@ +{ + "name": "Nome", + "client": "Cliente", + "category": "Categoria", + "status": "Status", + "tasksProgress": "Progresso das Tarefas", + "updated_at": "Última Atualização", + "members": "Membros", + "setting": "Configuração", + "archive": "Arquivar", + "projects": "Projetos", + "refreshProjects": "Atualizar projetos", + "all": "Todos", + "favorites": "Favoritos", + "archived": "Arquivados", + "placeholder": "Pesquisar por nome", + "archiveConfirm": "Tem certeza de que deseja arquivar este projeto?", + "unarchive": "Desarquivar", + "unarchiveConfirm": "Tem certeza de que deseja desarquivar este projeto?", + "clickToFilter": "Clique para filtrar por", + "noProjects": "Nenhum projeto encontrado", + "addToFavourites": "Adicionar aos favoritos" +} diff --git a/worklenz-frontend/public/locales/pt/auth/auth-common.json b/worklenz-frontend/public/locales/pt/auth/auth-common.json new file mode 100644 index 00000000..e828bddf --- /dev/null +++ b/worklenz-frontend/public/locales/pt/auth/auth-common.json @@ -0,0 +1,5 @@ +{ + "loggingOut": "Deslogando...", + "authenticating": "Autenticando...", + "gettingThingsReady": "Preparando coisas para você..." +} diff --git a/worklenz-frontend/public/locales/pt/auth/forgot-password.json b/worklenz-frontend/public/locales/pt/auth/forgot-password.json new file mode 100644 index 00000000..5e9c89e0 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/auth/forgot-password.json @@ -0,0 +1,12 @@ +{ + "headerDescription": "Redefina sua senha", + "emailLabel": "Email", + "emailPlaceholder": "Digite seu email", + "emailRequired": "Por favor, digite seu email!", + "resetPasswordButton": "Redefinir Senha", + "returnToLoginButton": "Voltar para Login", + "passwordResetSuccessMessage": "Um link de redefinição de senha foi enviado para seu email.", + "orText": "OU", + "successTitle": "Instruções de redefinição enviadas!", + "successMessage": "A informação de redefinição foi enviada para seu email. Por favor, verifique seu email." +} diff --git a/worklenz-frontend/public/locales/pt/auth/login.json b/worklenz-frontend/public/locales/pt/auth/login.json new file mode 100644 index 00000000..2ce79115 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/auth/login.json @@ -0,0 +1,27 @@ +{ + "headerDescription": "Faça login na sua conta", + "emailLabel": "Email", + "emailPlaceholder": "Digite seu email", + "emailRequired": "Por favor, digite seu email!", + "passwordLabel": "Senha", + "passwordPlaceholder": "Digite sua senha", + "passwordRequired": "Por favor, digite sua Senha!", + "rememberMe": "Lembre de mim", + "loginButton": "Entrar", + "signupButton": "Inscrever-se", + "forgotPasswordButton": "Esqueceu sua senha?", + "signInWithGoogleButton": "Entrar com Google", + "successMessage": "Você entrou com sucesso!", + "dontHaveAccountText": "Não tem uma conta?", + "orText": "OU", + "loginError": "Login falhou", + "googleLoginError": "Login com Google falhou", + "validationMessages": { + "password": "A senha deve ter pelo menos 8 caracteres", + "email": "Por favor, insira um endereço de e-mail válido" + }, + "errorMessages": { + "loginErrorTitle": "Login falhou", + "loginErrorMessage": "Por favor, verifique seu e-mail e senha e tente novamente" + } +} diff --git a/worklenz-frontend/public/locales/pt/auth/signup.json b/worklenz-frontend/public/locales/pt/auth/signup.json new file mode 100644 index 00000000..cd994d4a --- /dev/null +++ b/worklenz-frontend/public/locales/pt/auth/signup.json @@ -0,0 +1,29 @@ +{ + "headerDescription": "Inscreva-se para começar", + "nameLabel": "Nome Completo", + "namePlaceholder": "Insira seu nome completo", + "nameRequired": "Por favor, insira seu nome completo!", + "nameMinCharacterRequired": "Nome completo deve ter pelo menos 4 caracteres!", + "emailLabel": "Email", + "emailPlaceholder": "Insira seu email", + "emailRequired": "Por favor, insira seu Email!", + "passwordLabel": "Senha", + "passwordPlaceholder": "Insira sua senha", + "passwordRequired": "Por favor, insira sua Senha!", + "passwordMinCharacterRequired": "Senha deve ter pelo menos 8 caracteres!", + "passwordPatternRequired": "Senha não atende aos requisitos!", + "strongPasswordPlaceholder": "Insira uma senha mais forte", + "passwordValidationAltText": "Senha deve incluir pelo menos 8 caracteres com letras maiúsculas e minúsculas, um número e um símbolo.", + "signupSuccessMessage": "Você se inscreveu com sucesso!", + "privacyPolicyLink": "Política de Privacidade", + "termsOfUseLink": "Termos de Uso", + "bySigningUpText": "Ao se inscrever, você concorda com nossos", + "andText": "e", + "signupButton": "Inscrever-se", + "signInWithGoogleButton": "Entrar com Google", + "alreadyHaveAccountText": "Já tem uma conta?", + "loginButton": "Entrar", + "orText": "OU", + "reCAPTCHAVerificationError": "Erro de verificação do reCAPTCHA", + "reCAPTCHAVerificationErrorMessage": "Não pudemos verificar seu reCAPTCHA. Por favor, tente novamente." +} diff --git a/worklenz-frontend/public/locales/pt/auth/verify-reset-email.json b/worklenz-frontend/public/locales/pt/auth/verify-reset-email.json new file mode 100644 index 00000000..189a6d51 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/auth/verify-reset-email.json @@ -0,0 +1,14 @@ +{ + "title": "Verificar E-mail de Redefinição", + "description": "Digite sua nova senha", + "placeholder": "Digite sua nova senha", + "confirmPasswordPlaceholder": "Confirme sua nova senha", + "passwordHint": "Mínimo de 8 caracteres, com maiúsculas e minúsculas, um número e um símbolo.", + "resetPasswordButton": "Redefinir senha", + "orText": "Ou", + "resendResetEmail": "Reenviar e-mail de redefinição", + "passwordRequired": "Por favor, digite sua nova senha", + "returnToLoginButton": "Voltar ao Login", + "confirmPasswordRequired": "Por favor, confirme sua nova senha", + "passwordMismatch": "As senhas não coincidem" +} diff --git a/worklenz-frontend/public/locales/pt/common.json b/worklenz-frontend/public/locales/pt/common.json new file mode 100644 index 00000000..ce540a28 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/common.json @@ -0,0 +1,9 @@ +{ + "login-success": "Login realizado com sucesso!", + "login-failed": "Falha no login. Por favor, verifique suas credenciais e tente novamente.", + "signup-success": "Cadastro realizado com sucesso! Bem-vindo a bordo.", + "signup-failed": "Falha no cadastro. Por favor, certifique-se de que todos os campos obrigatórios estão preenchidos e tente novamente.", + "reconnecting": "Reconectando ao servidor...", + "connection-lost": "Conexão perdida. Tentando reconectar...", + "connection-restored": "Conexão restaurada. Reconectando ao servidor..." +} diff --git a/worklenz-frontend/public/locales/pt/create-first-project-form.json b/worklenz-frontend/public/locales/pt/create-first-project-form.json new file mode 100644 index 00000000..ec3ec300 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/create-first-project-form.json @@ -0,0 +1,13 @@ +{ + "formTitle": "Crie seu primeiro projeto", + "inputLabel": "Em qual projeto você está trabalhando agora?", + "or": "ou", + "templateButton": "Importar do modelo", + "createFromTemplate": "Criar do modelo", + "goBack": "Voltar", + "continue": "Continuar", + "cancel": "Cancelar", + "create": "Criar", + "templateDrawerTitle": "Selecione um modelo", + "createProject": "Criar projeto" +} diff --git a/worklenz-frontend/public/locales/pt/create-first-tasks.json b/worklenz-frontend/public/locales/pt/create-first-tasks.json new file mode 100644 index 00000000..06c1ae87 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/create-first-tasks.json @@ -0,0 +1,7 @@ +{ + "formTitle": "Crie sua primeira tarefa.", + "inputLable": "Digite algumas tarefas que você vai fazer em", + "addAnother": "Adicionar outro", + "goBack": "Voltar", + "continue": "Continuar" +} diff --git a/worklenz-frontend/public/locales/pt/home.json b/worklenz-frontend/public/locales/pt/home.json new file mode 100644 index 00000000..b19ece5f --- /dev/null +++ b/worklenz-frontend/public/locales/pt/home.json @@ -0,0 +1,45 @@ +{ + "todoList": { + "title": "Lista de tarefas", + "refreshTasks": "Atualizar tarefas", + "addTask": "+ Adicionar tarefa", + "noTasks": "Nenhuma tarefa", + "pressEnter": "Pressione", + "toCreate": "para criar.", + "markAsDone": "Marcar como feito" + }, + "projects": { + "title": "Projetos", + "refreshProjects": "Atualizar projetos", + "noRecentProjects": "Você não está atribuído a nenhum projeto.", + "noFavouriteProjects": "Nenhum projeto foi marcado como favorito.", + "recent": "Recentes", + "favourites": "Favoritos" + }, + "tasks": { + "assignedToMe": "Atribuído a mim", + "assignedByMe": "Atribuído por mim", + "all": "Todas", + "today": "Hoje", + "upcoming": "Próximas", + "overdue": "Vencidas", + "noDueDate": "Sem data de vencimento", + "noTasks": "Nenhuma tarefa para mostrar.", + "addTask": "+ Adicionar tarefa", + "name": "Nome", + "project": "Projeto", + "status": "Status", + "dueDate": "Data de vencimento", + "dueDatePlaceholder": "Definir data de vencimento", + "tomorrow": "Amanhã", + "nextWeek": "Semana que vem", + "nextMonth": "Próximo mês", + "projectRequired": "Por favor selecione um projeto", + "dueOn": "Tarefas vencidas em", + "taskRequired": "Por favor adicione uma tarefa", + "list": "Lista", + "calendar": "Calendário", + "tasks": "Tarefas", + "refresh": "Atualizar" + } +} diff --git a/worklenz-frontend/public/locales/pt/invite-initial-team-members.json b/worklenz-frontend/public/locales/pt/invite-initial-team-members.json new file mode 100644 index 00000000..39808ab2 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/invite-initial-team-members.json @@ -0,0 +1,8 @@ +{ + "formTitle": "Convide sua equipe para trabalhar com", + "inputLable": "Convidar com email", + "addAnother": "Adicionar outro", + "goBack": "Voltar", + "continue": "Continuar", + "skipForNow": "Pular por enquanto" +} diff --git a/worklenz-frontend/public/locales/pt/kanban-board.json b/worklenz-frontend/public/locales/pt/kanban-board.json new file mode 100644 index 00000000..0cd9e27b --- /dev/null +++ b/worklenz-frontend/public/locales/pt/kanban-board.json @@ -0,0 +1,23 @@ +{ + "rename": "Renomear", + "delete": "Excluir", + "addTask": "Adicionar Tarefa", + "addSectionButton": "Adicionar Seção", + "changeCategory": "Alterar categoria", + + "deleteTooltip": "Excluir", + "deleteConfirmationTitle": "Tem certeza?", + "deleteConfirmationOk": "Sim", + "deleteConfirmationCancel": "Cancelar", + + "dueDate": "Data de vencimento", + "cancel": "Cancelar", + + "today": "Hoje", + "tomorrow": "Amanhã", + "assignToMe": "Atribuir a mim", + "archive": "Arquivar", + + "newTaskNamePlaceholder": "Escreva um nome de tarefa", + "newSubtaskNamePlaceholder": "Escreva um nome de subtarefa" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/license-expired.json b/worklenz-frontend/public/locales/pt/license-expired.json new file mode 100644 index 00000000..aa7ae88b --- /dev/null +++ b/worklenz-frontend/public/locales/pt/license-expired.json @@ -0,0 +1,6 @@ +{ + "title": "Seu teste do Worklenz expirou!", + "subtitle": "Por favor, atualize agora.", + "button": "Atualizar agora", + "checking": "Verificando status da assinatura..." +} diff --git a/worklenz-frontend/public/locales/pt/navbar.json b/worklenz-frontend/public/locales/pt/navbar.json new file mode 100644 index 00000000..be0f3a63 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/navbar.json @@ -0,0 +1,31 @@ +{ + "logoAlt": "Logotipo Worklenz", + "home": "Início", + "projects": "Projetos", + "schedule": "Agendamento", + "reporting": "Relatórios", + "clients": "Clientes", + "teams": "Equipes", + "labels": "Rótulos", + "jobTitles": "Títulos de Emprego", + "upgradePlan": "Plano de Upgrade", + "upgradePlanTooltip": "Plano de Upgrade", + "invite": "Convidar", + "inviteTooltip": "Convidar membros da equipe a se juntar", + "switchTeamTooltip": "Trocar equipe", + "help": "Ajuda", + "notificationTooltip": "Ver notificações", + "profileTooltip": "Ver perfil", + "adminCenter": "Centro de administração", + "settings": "Configurações", + "logOut": "Sair", + "notificationsDrawer": { + "read": "Notificações lidas", + "unread": "Notificações não lidas", + "markAsRead": "Marcar como lido", + "readAndJoin": "Ler e participar", + "accept": "Aceitar", + "acceptAndJoin": "Aceitar e participar", + "noNotifications": "Sem notificações" + } +} diff --git a/worklenz-frontend/public/locales/pt/organization-name-form.json b/worklenz-frontend/public/locales/pt/organization-name-form.json new file mode 100644 index 00000000..c165b8cb --- /dev/null +++ b/worklenz-frontend/public/locales/pt/organization-name-form.json @@ -0,0 +1,5 @@ +{ + "nameYourOrganization": "Nomeie sua organização.", + "worklenzAccountTitle": "Escolha um nome para sua conta Worklenz.", + "continue": "Continuar" +} diff --git a/worklenz-frontend/public/locales/pt/phases-drawer.json b/worklenz-frontend/public/locales/pt/phases-drawer.json new file mode 100644 index 00000000..0363c69c --- /dev/null +++ b/worklenz-frontend/public/locales/pt/phases-drawer.json @@ -0,0 +1,7 @@ +{ + "configurePhases": "Configurar fases", + "phaseLabel": "Etiqueta de fase", + "enterPhaseName": "Ingrese un nombre para la etiqueta de fase", + "addOption": "Agregar opción", + "phaseOptions": "Opciones de fase:" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/project-drawer.json b/worklenz-frontend/public/locales/pt/project-drawer.json new file mode 100644 index 00000000..55022c4e --- /dev/null +++ b/worklenz-frontend/public/locales/pt/project-drawer.json @@ -0,0 +1,42 @@ +{ + "createProject": "Criar Projeto", + "editProject": "Editar Projeto", + "enterCategoryName": "Insira um nome para a categoria", + "hitEnterToCreate": "Pressione enter para criar!", + "enterNotes": "Notas", + "youCanManageClientsUnderSettings": "Você pode gerenciar clientes em Configurações", + "addCategory": "Adicione uma categoria ao projeto", + "newCategory": "Nova Categoria", + "notes": "Notas", + "startDate": "Data de Início", + "endDate": "Data de Fim", + "estimateWorkingDays": "Estime os dias de trabalho", + "estimateManDays": "Estime os dias de trabalho", + "hoursPerDay": "Horas por dia", + "create": "Criar", + "update": "Atualizar", + "delete": "Excluir", + "typeToSearchClients": "Digite para buscar clientes", + "projectColor": "Cor do Projeto", + "pleaseEnterAName": "Por favor, insira um nome", + "enterProjectName": "Insira o nome do projeto", + "name": "Nome", + "status": "Estado", + "health": "Saúde", + "category": "Categoria", + "projectManager": "Gerente de Projeto", + "client": "Cliente", + "deleteConfirmation": "Tem a certeza de que deseja excluir?", + "deleteConfirmationDescription": "Isso removerá todos os dados associados e não pode ser desfeito.", + "yes": "Sim", + "no": "Não", + "createdAt": "Criado", + "updatedAt": "Atualizado", + "by": "por", + "add": "Adicionar", + "asClient": "como cliente", + "createClient": "Criar cliente", + "searchInputPlaceholder": "Pesquise por nome ou email", + "hoursPerDayValidationMessage": "As horas por dia devem ser um número entre 1 e 24", + "noPermission": "Sem permissão" +} diff --git a/worklenz-frontend/public/locales/pt/project-view-files.json b/worklenz-frontend/public/locales/pt/project-view-files.json new file mode 100644 index 00000000..61f1cb59 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/project-view-files.json @@ -0,0 +1,14 @@ +{ + "nameColumn": "Nome", + "attachedTaskColumn": "Tarefa Anexada", + "sizeColumn": "Tamanho", + "uploadedByColumn": "Enviado Por", + "uploadedAtColumn": "Enviado Em", + "fileIconAlt": "Ícone do Arquivo", + "titleDescriptionText": "Todos os anexos das tarefas neste projeto aparecerão aqui.", + "deleteConfirmationTitle": "Tem certeza?", + "deleteConfirmationOk": "Sim", + "deleteConfirmationCancel": "Cancelar", + "segmentedTooltip": "Em breve! Alterne entre a visualização em lista e a visualização em miniatura.", + "emptyText": "Não há anexos no projeto." +} diff --git a/worklenz-frontend/public/locales/pt/project-view-insights.json b/worklenz-frontend/public/locales/pt/project-view-insights.json new file mode 100644 index 00000000..2ad6ee92 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/project-view-insights.json @@ -0,0 +1,41 @@ +{ + "overview": { + "title": "Visão Geral", + "statusOverview": "Visão Geral do Status", + "priorityOverview": "Visão Geral da Prioridade", + "lastUpdatedTasks": "Últimas Tarefas Atualizadas" + }, + "members": { + "title": "Membros", + "tooltip": "Membros", + "tasksByMembers": "Tarefas por membros", + "tasksByMembersTooltip": "Tarefas por membros", + "name": "Nome", + "taskCount": "Contagem de Tarefas", + "contribution": "Contribuição", + "completed": "Concluído", + "incomplete": "Incompleto", + "overdue": "Atrasado", + "progress": "Progresso" + }, + "tasks": { + "overdueTasks": "Tarefas Atrasadas", + "overLoggedTasks": "Tarefas com excesso de tempo registrado", + "tasksCompletedEarly": "Tarefas concluídas cedo", + "tasksCompletedLate": "Tarefas concluídas tarde", + "overLoggedTasksTooltip": "Tarefas que têm tempo registrado além do tempo estimado", + "overdueTasksTooltip": "Tarefas que estão atrasadas" + }, + "common": { + "seeAll": "Ver tudo", + "totalLoggedHours": "Total de horas registradas", + "totalEstimation": "Total de estimativa", + "completedTasks": "Tarefas concluídas", + "incompleteTasks": "Tarefas incompletas", + "overdueTasks": "Tarefas atrasadas", + "overdueTasksTooltip": "Tarefas que estão atrasadas", + "totalLoggedHoursTooltip": "Estimativa de tarefas e tempo registrado.", + "includeArchivedTasks": "Incluir Tarefas Arquivadas", + "export": "Exportar" + } +} diff --git a/worklenz-frontend/public/locales/pt/project-view-members.json b/worklenz-frontend/public/locales/pt/project-view-members.json new file mode 100644 index 00000000..72524807 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/project-view-members.json @@ -0,0 +1,17 @@ +{ + "nameColumn": "Nome", + "jobTitleColumn": "Título do Cargo", + "emailColumn": "Email", + "tasksColumn": "Tarefas", + "taskProgressColumn": "Progresso da Tarefa", + "accessColumn": "Acesso", + "fileIconAlt": "Ícone do Arquivo", + "deleteConfirmationTitle": "Tem a certeza?", + "deleteConfirmationOk": "Sim", + "deleteConfirmationCancel": "Cancelar", + "refreshButtonTooltip": "Atualizar membros", + "deleteButtonTooltip": "Remover do projeto", + "memberCount": "Membro", + "membersCountPlural": "Membros", + "emptyText": "Não há anexos no projeto." +} diff --git a/worklenz-frontend/public/locales/pt/project-view-updates.json b/worklenz-frontend/public/locales/pt/project-view-updates.json new file mode 100644 index 00000000..93a48950 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/project-view-updates.json @@ -0,0 +1,6 @@ +{ + "inputPlaceholder": "Adicione um comentário..", + "addButton": "Adicionar", + "cancelButton": "Cancelar", + "deleteButton": "Deletar" +} diff --git a/worklenz-frontend/public/locales/pt/project-view/import-task-templates.json b/worklenz-frontend/public/locales/pt/project-view/import-task-templates.json new file mode 100644 index 00000000..82b3cabb --- /dev/null +++ b/worklenz-frontend/public/locales/pt/project-view/import-task-templates.json @@ -0,0 +1,11 @@ +{ + "importTaskTemplate": "Importar modelo de tarefa", + "templateName": "Nome do modelo", + "templateDescription": "Descrição do modelo", + "selectedTasks": "Tarefas selecionadas", + "tasks": "Tarefas", + "templates": "Modelos", + "remove": "Remover", + "cancel": "Cancelar", + "import": "Importar" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/project-view/project-member-drawer.json b/worklenz-frontend/public/locales/pt/project-view/project-member-drawer.json new file mode 100644 index 00000000..b4c402e4 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/project-view/project-member-drawer.json @@ -0,0 +1,8 @@ +{ + "title": "Membros do Projeto", + "searchLabel": "Adicionar membros inserindo nome ou e-mail", + "searchPlaceholder": "Digite nome ou e-mail", + "inviteAsAMember": "Convidar como membro", + "inviteNewMemberByEmail": "Convidar novo membro por e-mail" + +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/project-view/project-view-header.json b/worklenz-frontend/public/locales/pt/project-view/project-view-header.json new file mode 100644 index 00000000..194668eb --- /dev/null +++ b/worklenz-frontend/public/locales/pt/project-view/project-view-header.json @@ -0,0 +1,13 @@ +{ + "importTasks": "Importar tarefas", + "createTask": "Criar tarefa", + "settings": "Configurações", + "subscribe": "Inscrever-se", + "unsubscribe": "Cancelar inscrição", + "deleteProject": "Excluir projeto", + "startDate": "Data de início", + "endDate": "Data de fim", + "projectSettings": "Configurações do projeto", + "projectSummary": "Resumo do projeto", + "receiveProjectSummary": "Receber um resumo do projeto todas as noites." +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/project-view/save-as-template.json b/worklenz-frontend/public/locales/pt/project-view/save-as-template.json new file mode 100644 index 00000000..70629b2f --- /dev/null +++ b/worklenz-frontend/public/locales/pt/project-view/save-as-template.json @@ -0,0 +1,27 @@ +{ + "title": "Salvar como Modelo", + "templateName": "Nome do Modelo", + "includes": "O que deve ser incluído no modelo do projeto?", + "includesOptions": { + "statuses": "Status", + "phases": "Fases", + "labels": "Etiquetas" + }, + "taskIncludes": "O que deve ser incluído no modelo das tarefas?", + "taskIncludesOptions": { + "statuses": "Status", + "phases": "Fases", + "labels": "Etiquetas", + "name": "Nome", + "priority": "Prioridade", + "status": "Status", + "phase": "Fase", + "label": "Etiqueta", + "timeEstimate": "Estimativa de Tempo", + "description": "Descrição", + "subTasks": "Subtarefas" + }, + "cancel": "Cancelar", + "save": "Salvar", + "templateNamePlaceholder": "Digite o nome do modelo" +} diff --git a/worklenz-frontend/public/locales/pt/reporting-members-drawer.json b/worklenz-frontend/public/locales/pt/reporting-members-drawer.json new file mode 100644 index 00000000..49d0008b --- /dev/null +++ b/worklenz-frontend/public/locales/pt/reporting-members-drawer.json @@ -0,0 +1,90 @@ +{ + "exportButton": "Exportar", + "timeLogsButton": "Registros de Tempo", + "activityLogsButton": "Registros de Atividade", + "tasksButton": "Tarefas", + "searchByNameInputPlaceholder": "Pesquisar por nome", + + "overviewTab": "Visão Geral", + "timeLogsTab": "Registros de Tempo", + "activityLogsTab": "Registros de Atividade", + "tasksTab": "Tarefas", + + "projectsText": "Projetos", + "totalTasksText": "Total de Tarefas", + "assignedTasksText": "Tarefas Atribuídas", + "completedTasksText": "Tarefas Concluídas", + "ongoingTasksText": "Tarefas em Andamento", + "overdueTasksText": "Tarefas Atrasadas", + "loggedHoursText": "Horas Registradas", + + "tasksText": "Tarefas", + "allText": "Todas", + + "tasksByProjectsText": "Tarefas Por Projetos", + "tasksByStatusText": "Tarefas Por Status", + "tasksByPriorityText": "Tarefas Por Prioridade", + + "todoText": "A Fazer", + "doingText": "Fazendo", + "doneText": "Feita", + "lowText": "Baixa", + "mediumText": "Média", + "highText": "Alta", + + "billableButton": "Cobrável", + "billableText": "Cobrável", + "nonBillableText": "Não Cobrável", + + "timeLogsEmptyPlaceholder": "Nenhum registro de tempo para mostrar", + "loggedText": "Registrado", + "forText": "para", + "inText": "em", + "updatedText": "Atualizado", + "fromText": "De", + "toText": "até", + "withinText": "dentro de", + + "activityLogsEmptyPlaceholder": "Nenhum registro de atividade para mostrar", + + "filterByText": "Filtrar por:", + "selectProjectPlaceholder": "Selecione o Projeto", + + "taskColumn": "Tarefa", + "nameColumn": "Nome", + "projectColumn": "Projeto", + "statusColumn": "Status", + "priorityColumn": "Prioridade", + "dueDateColumn": "Data de Vencimento", + "completedDateColumn": "Data de Conclusão", + "estimatedTimeColumn": "Tempo Estimado", + "loggedTimeColumn": "Tempo Registrado", + "overloggedTimeColumn": "Tempo Excedido", + "daysLeftColumn": "Dias Restantes/Atrasados", + "startDateColumn": "Data de Início", + "endDateColumn": "Data de Fim", + "actualTimeColumn": "Tempo Real", + "projectHealthColumn": "Saúde do Projeto", + "categoryColumn": "Categoria", + "projectManagerColumn": "Gerente do Projeto", + + "tasksStatsOverviewDrawerTitle": "Tarefas de", + "projectsStatsOverviewDrawerTitle": "Projetos de", + + "cancelledText": "Cancelada", + "blockedText": "Bloqueada", + "onHoldText": "Em Espera", + "proposedText": "Proposta", + "inPlanningText": "Em Planejamento", + "inProgressText": "Em Progresso", + "completedText": "Concluída", + "continuousText": "Contínua", + + "daysLeftText": "dias restantes", + "daysOverdueText": "dias atrasados", + + "notSetText": "Não Definido", + "needsAttentionText": "Precisa de Atenção", + "atRiskText": "Em Risco", + "goodText": "Bom" +} diff --git a/worklenz-frontend/public/locales/pt/reporting-members.json b/worklenz-frontend/public/locales/pt/reporting-members.json new file mode 100644 index 00000000..a8035dcd --- /dev/null +++ b/worklenz-frontend/public/locales/pt/reporting-members.json @@ -0,0 +1,35 @@ +{ + "yesterdayText": "Yesterday", + "lastSevenDaysText": "Last 7 Days", + "lastWeekText": "Last Week", + "lastThirtyDaysText": "Last 30 Days", + "lastMonthText": "Last Month", + "lastThreeMonthsText": "Last 3 Months", + "allTimeText": "All Time", + "customRangeText": "Custom range", + "startDateInputPlaceholder": "Start date", + "EndDateInputPlaceholder": "End date", + "filterButton": "Filter", + + "membersTitle": "Members", + "includeArchivedButton": "Include Archived Projects", + "exportButton": "Export", + "excelButton": "Excel", + "searchByNameInputPlaceholder": "Search by name", + + "memberColumn": "Member", + "tasksProgressColumn": "Tasks Progress", + "tasksAssignedColumn": "Tasks Assigned", + "completedTasksColumn": "Completed Tasks", + "overdueTasksColumn": "Overdue Tasks", + "ongoingTasksColumn": "Ongoing Tasks", + + "tasksAssignedColumnTooltip": "Tasks assigned on selected date range", + "overdueTasksColumnTooltip": "Tasks overdue for end of the selected date range", + "completedTasksColumnTooltip": "Tasks completed on selected date range", + "ongoingTasksColumnTooltip": "Started tasks not completed yet", + + "todoText": "To Do", + "doingText": "Doing", + "doneText": "Done" +} diff --git a/worklenz-frontend/public/locales/pt/reporting-overview-drawer.json b/worklenz-frontend/public/locales/pt/reporting-overview-drawer.json new file mode 100644 index 00000000..af8b06ee --- /dev/null +++ b/worklenz-frontend/public/locales/pt/reporting-overview-drawer.json @@ -0,0 +1,39 @@ +{ + "exportButton": "Exportar", + "projectsButton": "Projetos", + "membersButton": "Membros", + "searchByNameInputPlaceholder": "Pesquisar por nome", + + "overviewTab": "Visão Geral", + "projectsTab": "Projetos", + "membersTab": "Membros", + + "projectsByStatusText": "Projetos Por Status", + "projectsByCategoryText": "Projetos Por Categoria", + "projectsByHealthText": "Projetos Por Saúde", + + "projectsText": "Projetos", + "allText": "Todos", + + "cancelledText": "Cancelado", + "blockedText": "Bloqueado", + "onHoldText": "Em Espera", + "proposedText": "Proposto", + "inPlanningText": "Em Planejamento", + "inProgressText": "Em Andamento", + "completedText": "Concluído", + "continuousText": "Contínuo", + + "notSetText": "Não Definido", + "needsAttentionText": "Necessita de Atenção", + "atRiskText": "Em Risco", + "goodText": "Bom", + + "nameColumn": "Nome", + "emailColumn": "Email", + "projectsColumn": "Projetos", + "tasksColumn": "Tarefas", + "overdueTasksColumn": "Tarefas Atrasadas", + "completedTasksColumn": "Tarefas Concluídas", + "ongoingTasksColumn": "Tarefas em Andamento" +} diff --git a/worklenz-frontend/public/locales/pt/reporting-overview.json b/worklenz-frontend/public/locales/pt/reporting-overview.json new file mode 100644 index 00000000..01681d1a --- /dev/null +++ b/worklenz-frontend/public/locales/pt/reporting-overview.json @@ -0,0 +1,25 @@ +{ + "overviewTitle": "Visão Geral", + "includeArchivedButton": "Incluir Projetos Arquivados", + + "teamCount": "Equipe", + "teamCountPlural": "Equipes", + "projectCount": "Projeto", + "projectCountPlural": "Projetos", + "memberCount": "Membro", + "memberCountPlural": "Membros", + "activeProjectCount": "Projeto Ativo", + "activeProjectCountPlural": "Projetos Ativos", + "overdueProjectCount": "Projeto Atrasado", + "overdueProjectCountPlural": "Projetos Atrasados", + "unassignedMemberCount": "Membro Não Atribuído", + "unassignedMemberCountPlural": "Membros Não Atribuídos", + "memberWithOverdueTaskCount": "Membro Com Tarefa Atrasada", + "memberWithOverdueTaskCountPlural": "Membros Com Tarefas Atrasadas", + + "teamsText": "Equipes", + + "nameColumn": "Nome", + "projectsColumn": "Projetos", + "membersColumn": "Membros" +} diff --git a/worklenz-frontend/public/locales/pt/reporting-projects-drawer.json b/worklenz-frontend/public/locales/pt/reporting-projects-drawer.json new file mode 100644 index 00000000..14bcfaca --- /dev/null +++ b/worklenz-frontend/public/locales/pt/reporting-projects-drawer.json @@ -0,0 +1,59 @@ +{ + "exportButton": "Exportar", + "membersButton": "Membros", + "tasksButton": "Tarefas", + "searchByNameInputPlaceholder": "Pesquisar por nome", + + "overviewTab": "Visão Geral", + "membersTab": "Membros", + "tasksTab": "Tarefas", + + "completedTasksText": "Tarefas Concluídas", + "incompleteTasksText": "Tarefas Incompletas", + "overdueTasksText": "Tarefas Atrasadas", + "allocatedHoursText": "Horas Alocadas", + "loggedHoursText": "Horas Registradas", + + "tasksText": "Tarefas", + "allText": "Todas", + + "tasksByStatusText": "Tarefas Por Status", + "tasksByPriorityText": "Tarefas Por Prioridade", + "tasksByDueDateText": "Tarefas Por Data de Vencimento", + + "todoText": "A Fazer", + "doingText": "Fazendo", + "doneText": "Feita", + "lowText": "Baixa", + "mediumText": "Média", + "highText": "Alta", + "completedText": "Concluída", + "upcomingText": "Próxima", + "overdueText": "Atrasada", + "noDueDateText": "Sem Data de Vencimento", + + "nameColumn": "Nome", + "tasksCountColumn": "Contagem de Tarefas", + "completedTasksColumn": "Tarefas Concluídas", + "incompleteTasksColumn": "Tarefas Incompletas", + "overdueTasksColumn": "Tarefas Atrasadas", + "contributionColumn": "Contribuição", + "progressColumn": "Progresso", + "loggedTimeColumn": "Tempo Registrado", + "taskColumn": "Tarefa", + "projectColumn": "Projeto", + "statusColumn": "Status", + "priorityColumn": "Prioridade", + "phaseColumn": "Fase", + "dueDateColumn": "Data de Vencimento", + "completedDateColumn": "Data de Conclusão", + "estimatedTimeColumn": "Tempo Estimado", + "overloggedTimeColumn": "Tempo Excedido", + "completedOnColumn": "Concluído Em", + "daysOverdueColumn": "Dias Atrasados", + + "groupByText": "Agrupar Por:", + "statusText": "Status", + "priorityText": "Prioridade", + "phaseText": "Fase" +} diff --git a/worklenz-frontend/public/locales/pt/reporting-projects-filters.json b/worklenz-frontend/public/locales/pt/reporting-projects-filters.json new file mode 100644 index 00000000..5d47d282 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/reporting-projects-filters.json @@ -0,0 +1,35 @@ +{ + "searchByNamePlaceholder": "Pesquisar por nome", + "searchByCategoryPlaceholder": "Pesquisar por categoria", + + "statusText": "Status", + "healthText": "Saúde", + "categoryText": "Categoria", + "projectManagerText": "Gerente de Projeto", + "showFieldsText": "Mostrar campos", + + "cancelledText": "Cancelado", + "blockedText": "Bloqueado", + "onHoldText": "Em Espera", + "proposedText": "Proposto", + "inPlanningText": "Em Planejamento", + "inProgressText": "Em Andamento", + "completedText": "Concluído", + "continuousText": "Contínuo", + + "notSetText": "Não Definido", + "needsAttentionText": "Precisa de Atenção", + "atRiskText": "Em Risco", + "goodText": "Bom", + + "nameText": "Projeto", + "estimatedVsActualText": "Estimado Vs Real", + "tasksProgressText": "Progresso das Tarefas", + "lastActivityText": "Última Atividade", + "datesText": "Datas de Início/Fim", + "daysLeftText": "Dias Restantes/Atrasados", + "projectHealthText": "Saúde do Projeto", + "projectUpdateText": "Atualização do Projeto", + "clientText": "Cliente", + "teamText": "Equipe" +} diff --git a/worklenz-frontend/public/locales/pt/reporting-projects.json b/worklenz-frontend/public/locales/pt/reporting-projects.json new file mode 100644 index 00000000..c5035b54 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/reporting-projects.json @@ -0,0 +1,52 @@ +{ + "projectCount": "Projeto", + "projectCountPlural": "Projetos", + "includeArchivedButton": "Incluir Projetos Arquivados", + "exportButton": "Exportar", + "excelButton": "Excel", + + "projectColumn": "Projeto", + "estimatedVsActualColumn": "Estimado Vs Real", + "tasksProgressColumn": "Progresso das Tarefas", + "lastActivityColumn": "Última Atividade", + "statusColumn": "Status", + "datesColumn": "Datas de Início/Fim", + "daysLeftColumn": "Dias Restantes/Atrasados", + "projectHealthColumn": "Saúde do Projeto", + "categoryColumn": "Categoria", + "projectUpdateColumn": "Atualização do Projeto", + "clientColumn": "Cliente", + "teamColumn": "Equipe", + "projectManagerColumn": "Gerente de Projeto", + + "openButton": "Abrir", + + "estimatedText": "Estimado", + "actualText": "Real", + + "todoText": "A Fazer", + "doingText": "Fazendo", + "doneText": "Feito", + + "cancelledText": "Cancelado", + "blockedText": "Bloqueado", + "onHoldText": "Em Espera", + "proposedText": "Proposto", + "inPlanningText": "Em Planejamento", + "inProgressText": "Em Andamento", + "completedText": "Concluído", + "continuousText": "Contínuo", + + "daysLeftText": "dias restantes", + "dayLeftText": "dia restante", + "daysOverdueText": "dias atrasados", + + "notSetText": "Não Definido", + "needsAttentionText": "Precisa de Atenção", + "atRiskText": "Em Risco", + "goodText": "Bom", + + "setCategoryText": "Definir Categoria", + "searchByNameInputPlaceholder": "Pesquisar por nome", + "todayText": "Hoje" +} diff --git a/worklenz-frontend/public/locales/pt/reporting-sidebar.json b/worklenz-frontend/public/locales/pt/reporting-sidebar.json new file mode 100644 index 00000000..e09940f3 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/reporting-sidebar.json @@ -0,0 +1,8 @@ +{ + "overviewText": "Visão Geral", + "projectsText": "Projetos", + "membersText": "Membros", + "timeReportsText": "Relatórios de Tempo", + "estimateVsActualText": "Estimado Vs Real", + "currentOrganizationTooltip": "Organização Atual" +} diff --git a/worklenz-frontend/public/locales/pt/schedule.json b/worklenz-frontend/public/locales/pt/schedule.json new file mode 100644 index 00000000..c2d9fed6 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/schedule.json @@ -0,0 +1,39 @@ +{ + "today": "Hoje", + "week": "Semana", + "month": "Mês", + + "settings": "Configurações", + "workingDays": "Dias de Trabalho", + "monday": "Segunda-feira", + "tuesday": "Terça-feira", + "wednesday": "Quarta-feira", + "thursday": "Quinta-feira", + "friday": "Sexta-feira", + "saturday": "Sábado", + "sunday": "Domingo", + "workingHours": "Horas de Trabalho", + "hours": "horas", + "saveButton": "Salvar", + + "totalAllocation": "Alocação Total", + "timeLogged": "Tempo Registrado", + "remainingTime": "Tempo Restante", + "total": "Total", + "perDay": "Por Dia", + "tasks": "tarefas", + "startDate": "Data de Início", + "endDate": "Data de Fim", + + "hoursPerDay": "Horas Por Dia", + "totalHours": "Horas Totais", + "deleteButton": "Excluir", + "cancelButton": "Cancelar", + + "tabTitle": "Tarefa sem Data de Início & Fim", + + "allocatedTime": "Tempo Alocado", + "totalLogged": "Total Registrado", + "loggedBillable": "Registrado Faturável", + "loggedNonBillable": "Registrado Não Faturável" +} diff --git a/worklenz-frontend/public/locales/pt/settings/categories.json b/worklenz-frontend/public/locales/pt/settings/categories.json new file mode 100644 index 00000000..2d4534c1 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/settings/categories.json @@ -0,0 +1,10 @@ +{ + "categoryColumn": "Categoria", + "deleteConfirmationTitle": "Tem a certeza?", + "deleteConfirmationOk": "Sim", + "deleteConfirmationCancel": "Cancelar", + "associatedTaskColumn": "Tarefa Associada", + "searchPlaceholder": "Pesquisar por nome", + "emptyText": "As categorias podem ser criadas ao atualizar ou criar projetos.", + "colorChangeTooltip": "Clique para mudar a cor" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/settings/change-password.json b/worklenz-frontend/public/locales/pt/settings/change-password.json new file mode 100644 index 00000000..07b993dd --- /dev/null +++ b/worklenz-frontend/public/locales/pt/settings/change-password.json @@ -0,0 +1,15 @@ +{ + "title": "Alterar Senha", + "currentPassword": "Senha Atual", + "newPassword": "Nova Senha", + "confirmPassword": "Confirmar Senha", + "currentPasswordPlaceholder": "Digite sua senha atual", + "newPasswordPlaceholder": "Nova Senha", + "confirmPasswordPlaceholder": "Confirmar Senha", + "currentPasswordRequired": "Por favor, digite sua senha atual!", + "newPasswordRequired": "Por favor, digite sua nova senha!", + "passwordValidationError": "A senha deve ter pelo menos 8 caracteres com uma letra maiúscula, um número e um símbolo.", + "passwordMismatch": "As senhas não coincidem!", + "passwordRequirements": "A nova senha deve ter no mínimo 8 caracteres, com uma letra maiúscula, um número e um símbolo.", + "updateButton": "Atualizar Senha" +} diff --git a/worklenz-frontend/public/locales/pt/settings/clients.json b/worklenz-frontend/public/locales/pt/settings/clients.json new file mode 100644 index 00000000..4f990a6e --- /dev/null +++ b/worklenz-frontend/public/locales/pt/settings/clients.json @@ -0,0 +1,22 @@ +{ + "nameColumn": "Nome", + "projectColumn": "Projeto", + "noProjectsAvailable": "Nenhum projeto disponível", + "deleteConfirmationTitle": "Tem a certeza?", + "deleteConfirmationOk": "Sim", + "deleteConfirmationCancel": "Cancelar", + "searchPlaceholder": "Pesquisar por nome", + "createClient": "Criar Cliente", + "pinTooltip": "Clique para fixar isso no menu principal", + "createClientDrawerTitle": "Criar Cliente", + "updateClientDrawerTitle": "Atualizar Cliente", + "nameLabel": "Nome", + "namePlaceholder": "Nome", + "nameRequiredError": "Por favor, insira um Nome", + "createButton": "Criar", + "updateButton": "Atualizar", + "createClientSuccessMessage": "Criar cliente sucesso!", + "createClientErrorMessage": "Criar cliente falhou!", + "updateClientSuccessMessage": "Atualizar cliente sucesso!", + "updateClientErrorMessage": "Atualizar cliente falhou!" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/settings/job-titles.json b/worklenz-frontend/public/locales/pt/settings/job-titles.json new file mode 100644 index 00000000..9f641ba0 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/settings/job-titles.json @@ -0,0 +1,20 @@ +{ + "nameColumn": "Nome", + "deleteConfirmationTitle": "Tem a certeza?", + "deleteConfirmationOk": "Sim", + "deleteConfirmationCancel": "Cancelar", + "searchPlaceholder": "Pesquisar por nome", + "createJobTitleButton": "Criar Título de Emprego", + "pinTooltip": "Clique para fixar isso no menu principal", + "createJobTitleDrawerTitle": "Criar Título de Emprego", + "updateJobTitleDrawerTitle": "Atualizar Título de Emprego", + "nameLabel": "Nome", + "namePlaceholder": "Nome", + "nameRequiredError": "Por favor, insira um Nome", + "createButton": "Criar", + "updateButton": "Atualizar", + "createJobTitleSuccessMessage": "Criar título de emprego com sucesso!", + "createJobTitleErrorMessage": "Falha ao criar título de emprego!", + "updateJobTitleSuccessMessage": "Atualizar título de emprego com sucesso!", + "updateJobTitleErrorMessage": "Falha ao atualizar título de emprego!" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/settings/labels.json b/worklenz-frontend/public/locales/pt/settings/labels.json new file mode 100644 index 00000000..90c5450f --- /dev/null +++ b/worklenz-frontend/public/locales/pt/settings/labels.json @@ -0,0 +1,11 @@ +{ + "labelColumn": "Rótulo", + "deleteConfirmationTitle": "Tem a certeza?", + "deleteConfirmationOk": "Sim", + "deleteConfirmationCancel": "Cancelar", + "associatedTaskColumn": "Contagem de Tarefas Associadas", + "searchPlaceholder": "Pesquisar por nome", + "emptyText": "Os rótulos podem ser criados ao atualizar ou criar tarefas.", + "pinTooltip": "Clique para fixar isso no menu principal", + "colorChangeTooltip": "Clique para mudar a cor" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/settings/language.json b/worklenz-frontend/public/locales/pt/settings/language.json new file mode 100644 index 00000000..44a2cacc --- /dev/null +++ b/worklenz-frontend/public/locales/pt/settings/language.json @@ -0,0 +1,7 @@ +{ + "language": "Idioma", + "language_required": "O idioma é obrigatório", + "time_zone": "Fuso horário", + "time_zone_required": "O fuso horário é obrigatório", + "save_changes": "Salvar alterações" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/settings/notifications.json b/worklenz-frontend/public/locales/pt/settings/notifications.json new file mode 100644 index 00000000..ca402552 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/settings/notifications.json @@ -0,0 +1,10 @@ +{ + "emailTitle": "Envie-me notificações por email", + "emailDescription": "Isso inclui novas atribuições de tarefas", + "dailyDigestTitle": "Envie-me um resumo diário", + "dailyDigestDescription": "Toda noite, você receberá um resumo da atividade recente nas tarefas.", + "popupTitle": "Notificações pop-up no meu computador quando o Worklenz está aberto", + "popupDescription": "As notificações pop-up podem ser desativadas pelo seu navegador. Altere as configurações do seu navegador para permiti-las.", + "unreadItemsTitle": "Mostrar o número de itens não lidos", + "unreadItemsDescription": "Você verá contagens para cada notificação." +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/settings/profile.json b/worklenz-frontend/public/locales/pt/settings/profile.json new file mode 100644 index 00000000..fd3c3e2c --- /dev/null +++ b/worklenz-frontend/public/locales/pt/settings/profile.json @@ -0,0 +1,13 @@ +{ + "uploadError": "Você só pode fazer upload de arquivos JPG/PNG!", + "uploadSizeError": "A imagem deve ser menor que 2MB!", + "upload": "Carregar", + "nameLabel": "Nome", + "nameRequiredError": "Nome é obrigatório", + "emailLabel": "Email", + "emailRequiredError": "Email é obrigatório", + "saveChanges": "Salvar Alterações", + "profileJoinedText": "Entrou há um mês", + "profileLastUpdatedText": "Última atualização há um mês", + "avatarTooltip": "Clique para carregar um avatar" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/settings/project-templates.json b/worklenz-frontend/public/locales/pt/settings/project-templates.json new file mode 100644 index 00000000..a4a28eef --- /dev/null +++ b/worklenz-frontend/public/locales/pt/settings/project-templates.json @@ -0,0 +1,8 @@ +{ + "nameColumn": "Nome", + "editToolTip": "Editar", + "deleteToolTip": "Excluir", + "confirmText": "Tem a certeza?", + "okText": "Sim", + "cancelText": "Cancelar" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/settings/sidebar.json b/worklenz-frontend/public/locales/pt/settings/sidebar.json new file mode 100644 index 00000000..b9047fae --- /dev/null +++ b/worklenz-frontend/public/locales/pt/settings/sidebar.json @@ -0,0 +1,14 @@ +{ + "profile": "Perfil", + "notifications": "Notificações", + "clients": "Clientes", + "job-titles": "Títulos de Emprego", + "labels": "Rótulos", + "categories": "Categorias", + "project-templates": "Modelos de Projeto", + "task-templates": "Modelos de Tarefa", + "team-members": "Membros da Equipe", + "teams": "Equipes", + "change-password": "Alterar Senha", + "language-and-region": "Idioma e Região" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/settings/task-templates.json b/worklenz-frontend/public/locales/pt/settings/task-templates.json new file mode 100644 index 00000000..0fa425a8 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/settings/task-templates.json @@ -0,0 +1,9 @@ +{ + "nameColumn": "Nome", + "createdColumn": "Criado", + "editToolTip": "Editar", + "deleteToolTip": "Excluir", + "confirmText": "Tem a certeza?", + "okText": "Sim", + "cancelText": "Cancelar" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/settings/team-members.json b/worklenz-frontend/public/locales/pt/settings/team-members.json new file mode 100644 index 00000000..b9ff5696 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/settings/team-members.json @@ -0,0 +1,44 @@ +{ + "nameColumn": "Nome", + "projectsColumn": "Projetos", + "emailColumn": "Email", + "teamAccessColumn": "Acesso à Equipe", + "memberCount": "Membro", + "membersCountPlural": "Membros", + "searchPlaceholder": "Pesquisar membros pelo nome", + "pinTooltip": "Atualizar lista de membros", + "addMemberButton": "Adicionar Novo Membro", + "editTooltip": "Editar membro", + "deactivateTooltip": "Desativar membro", + "activateTooltip": "Ativar membro", + "deleteTooltip": "Deletar membro", + "confirmDeleteTitle": "Tem a certeza de que deseja deletar este membro?", + "confirmActivateTitle": "Tem a certeza de que deseja alterar o status deste membro?", + "okText": "Sim, proceder", + "cancelText": "Não, cancelar", + "deactivatedText": "(Atualmente desativado)", + "pendingInvitationText": "(Convite pendente)", + "addMemberDrawerTitle": "Adicionar Novo Membro da Equipe", + "updateMemberDrawerTitle": "Atualizar Membro da Equipe", + "addMemberEmailHint": "Os membros serão adicionados à equipe independentemente do status de aceitação do convite", + "memberEmailLabel": "Endereço(s) de Email", + "memberEmailPlaceholder": "Insira o endereço de email do membro da equipe", + "memberEmailRequiredError": "Por favor, insira um email válido", + "jobTitleLabel": "Título do Emprego", + "jobTitlePlaceholder": "Selecione ou pesquise o título do emprego (Opcional)", + "memberAccessLabel": "Nível de Acesso", + "addToTeamButton": "Adicionar Membro à Equipe", + "updateButton": "Salvar Alterações", + "resendInvitationButton": "Redirecionar Email de Convite", + "invitationSentSuccessMessage": "Convite para a equipe enviado com sucesso!", + "createMemberSuccessMessage": "Novo membro da equipe adicionado com sucesso!", + "createMemberErrorMessage": "Falha ao adicionar membro da equipe. Por favor, tente novamente.", + "updateMemberSuccessMessage": "Membro da equipe atualizado com sucesso!", + "updateMemberErrorMessage": "Falha ao atualizar membro da equipe. Por favor, tente novamente.", + "memberText": "Membro da Equipe", + "adminText": "Administrador", + "ownerText": "Dono da Equipe", + "addedText": "Adicionado", + "updatedText": "Atualizado", + "noResultFound": "Digite um endereço de email e pressione enter..." +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-info-tab.json b/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-info-tab.json new file mode 100644 index 00000000..48922a52 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-info-tab.json @@ -0,0 +1,29 @@ +{ + "details": { + "task-key": "Chave da tarefa", + "phase": "Fase", + "assignees": "Responsáveis", + "due-date": "Data de vencimento", + "time-estimation": "Estimativa de tempo", + "priority": "Prioridade", + "labels": "Etiquetas", + "billable": "Faturável", + "notify": "Notificar", + "when-done-notify": "Quando concluída, notificar", + "start-date": "Data de início", + "end-date": "Data de término", + "hide-start-date": "Ocultar data de início", + "show-start-date": "Mostrar data de início", + "hours": "Horas", + "minutes": "Minutos" + }, + "description": { + "title": "Descrição", + "placeholder": "Adicionar uma descrição mais detalhada..." + }, + "subTasks": { + "title": "Subtarefas", + "add-sub-task": "+ Adicionar subtarefa", + "refresh-sub-tasks": "Atualizar subtarefas" + } +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/task-drawer/task-drawer.json b/worklenz-frontend/public/locales/pt/task-drawer/task-drawer.json new file mode 100644 index 00000000..d6e8fef6 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/task-drawer/task-drawer.json @@ -0,0 +1,78 @@ +{ + "taskHeader": { + "taskNamePlaceholder": "Digite sua tarefa", + "deleteTask": "Excluir tarefa" + }, + "taskInfoTab": { + "title": "Informações", + "details": { + "title": "Detalhes", + "task-key": "Chave da tarefa", + "phase": "Fase", + "assignees": "Responsáveis", + "due-date": "Data de vencimento", + "time-estimation": "Estimativa de tempo", + "priority": "Prioridade", + "labels": "Etiquetas", + "billable": "Faturável", + "notify": "Notificar", + "when-done-notify": "Quando concluída, notificar", + "start-date": "Data de início", + "end-date": "Data de término", + "hide-start-date": "Ocultar data de início", + "show-start-date": "Mostrar data de início", + "hours": "Horas", + "minutes": "Minutos" + }, + "labels": { + "labelInputPlaceholder": "Pesquisar ou criar", + "labelsSelectorInputTip": "Pressione Enter para criar" + }, + "description": { + "title": "Descrição", + "placeholder": "Adicionar uma descrição mais detalhada..." + }, + "subTasks": { + "title": "Subtarefas", + "addSubTask": "+ Adicionar subtarefa", + "addSubTaskInputPlaceholder": "Digite sua tarefa e pressione enter", + "refreshSubTasks": "Atualizar subtarefas", + "edit": "Editar", + "delete": "Excluir", + "confirmDeleteSubTask": "Tem certeza de que deseja excluir esta subtarefa?", + "deleteSubTask": "Excluir subtarefa" + }, + "dependencies": { + "title": "Dependências", + "addDependency": "+ Adicionar nova dependência", + "blockedBy": "Bloqueado por", + "searchTask": "Digite para pesquisar tarefa", + "noTasksFound": "Nenhuma tarefa encontrada", + "confirmDeleteDependency": "Tem certeza de que deseja excluir?" + }, + "attachments": { + "title": "Anexos", + "chooseOrDropFileToUpload": "Escolha ou arraste um arquivo para carregar", + "uploading": "Carregando..." + }, + "comments": { + "title": "Comentários", + "addComment": "+ Adicionar novo comentário", + "noComments": "Nenhum comentário ainda. Seja o primeiro a comentar!", + "delete": "Excluir", + "confirmDeleteComment": "Tem certeza de que deseja excluir este comentário?" + }, + "searchInputPlaceholder": "Pesquisar por nome", + "pendingInvitation": "Convite pendente" + }, + "taskTimeLogTab": { + "title": "Registro de tempo", + "addTimeLog": "Adicionar novo registro de tempo", + "totalLogged": "Total registrado", + "exportToExcel": "Exportar para Excel", + "noTimeLogsFound": "Nenhum registro de tempo encontrado" + }, + "taskActivityLogTab": { + "title": "Registro de atividade" + } +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/task-list-filters.json b/worklenz-frontend/public/locales/pt/task-list-filters.json new file mode 100644 index 00000000..cf2cb7b0 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/task-list-filters.json @@ -0,0 +1,56 @@ +{ + "searchButton": "Pesquisar", + "resetButton": "Redefinir", + "searchInputPlaceholder": "Pesquisar por nome", + + "sortText": "Ordenar", + "statusText": "Status", + "phaseText": "Fase", + "priorityText": "Prioridade", + "labelsText": "Rótulos", + "membersText": "Membros", + "groupByText": "Agrupar por", + "showArchivedText": "Mostrar arquivados", + "showFieldsText": "Mostrar campos", + "keyText": "Chave", + "taskText": "Tarefa", + "descriptionText": "Descrição", + "phasesText": "Fases", + "progressText": "Progresso", + "timeTrackingText": "Rastreamento de Tempo", + "estimationText": "Estimativa", + "startDateText": "Data de Início", + "endDateText": "Data de Fim", + "dueDateText": "Data de Vencimento", + "completedDateText": "Data de Conclusão", + "createdDateText": "Data de Criação", + "lastUpdatedText": "Última Atualização", + "reporterText": "Relator", + "dueTimeText": "Hora de Vencimento", + "assigneesText": "Atribuições", + "timetrackingText": "Rastreamento de Tempo", + "startdateText": "Data de Início", + "duedateText": "Data de Vencimento", + "completeddateText": "Data de Conclusão", + "createddateText": "Data de Criação", + "lastupdatedText": "Última Atualização", + + "lowText": "Baixa", + "mediumText": "Média", + "highText": "Alta", + + "createStatusButtonTooltip": "Configurações de Status", + "configPhaseButtonTooltip": "Configurações de Fase", + "noLabelsFound": "Nenhum rótulo encontrado", + + "addStatusButton": "Adicionar Status", + "addPhaseButton": "Adicionar Fase", + + "createStatus": "Criar Status", + "name": "Nome", + "category": "Categoria", + "selectCategory": "Selecionar uma categoria", + "pleaseEnterAName": "Por favor, insira um nome", + "pleaseSelectACategory": "Por favor, selecione uma categoria", + "create": "Criar" +} diff --git a/worklenz-frontend/public/locales/pt/task-list-table.json b/worklenz-frontend/public/locales/pt/task-list-table.json new file mode 100644 index 00000000..23240945 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/task-list-table.json @@ -0,0 +1,63 @@ +{ + "keyColumn": "Chave", + "taskColumn": "Tarefa", + "descriptionColumn": "Descrição", + "progressColumn": "Progresso", + "membersColumn": "Membros", + "assigneesColumn": "Atribuídos", + "labelsColumn": "Etiquetas", + "phasesColumn": "Fases", + "phaseColumn": "Fase", + "statusColumn": "Status", + "priorityColumn": "Prioridade", + "timeTrackingColumn": "Acompanhamento de Tempo", + "timetrackingColumn": "Acompanhamento de Tempo", + "estimationColumn": "Estimativa", + "startDateColumn": "Data de Início", + "startdateColumn": "Data de Início", + "dueDateColumn": "Data de Vencimento", + "duedateColumn": "Data de Vencimento", + "completedDateColumn": "Data de Conclusão", + "completeddateColumn": "Data de Conclusão", + "createdDateColumn": "Data de Criação", + "createddateColumn": "Data de Criação", + "lastUpdatedColumn": "Última Atualização", + "lastupdatedColumn": "Última Atualização", + "reporterColumn": "Reportador", + "dueTimeColumn": "Hora de Vencimento", + "todoSelectorText": "A Fazer", + "doingSelectorText": "Fazendo", + "doneSelectorText": "Feito", + + "lowSelectorText": "Baixo", + "mediumSelectorText": "Médio", + "highSelectorText": "Alto", + + "selectText": "Selecionar", + "labelsSelectorInputTip": "Pressione enter para criar!", + + "addTaskText": "+ Adicionar Tarefa", + "addSubTaskText": "+ Adicionar Subtarefa", + "addTaskInputPlaceholder": "Digite sua tarefa e pressione enter", + + "openButton": "Abrir", + "okButton": "Ok", + + "noLabelsFound": "Nenhuma etiqueta encontrada", + "searchInputPlaceholder": "Buscar ou criar", + "assigneeSelectorInviteButton": "Convide um novo membro por e-mail", + "labelInputPlaceholder": "Buscar ou criar", + + "pendingInvitation": "Convite Pendente", + + "contextMenu": { + "assignToMe": "Atribuir a mim", + "moveTo": "Mover para", + "unarchive": "Desarquivar", + "archive": "Arquivar", + "convertToSubTask": "Converter em Subtarefa", + "convertToTask": "Converter em Tarefa", + "delete": "Excluir", + "searchByNameInputPlaceholder": "Buscar por nome" + } +} diff --git a/worklenz-frontend/public/locales/pt/task-template-drawer.json b/worklenz-frontend/public/locales/pt/task-template-drawer.json new file mode 100644 index 00000000..f1358349 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/task-template-drawer.json @@ -0,0 +1,11 @@ +{ + "createTaskTemplate": "Criar Template de Tarefa", + "editTaskTemplate": "Editar Template de Tarefa", + "cancelText": "Cancelar", + "saveText": "Salvar", + "templateNameText": "Nome do Template", + "selectedTasks": "Tarefas Selecionadas", + "removeTask": "Remover", + "cancelButton": "Cancelar", + "saveButton": "Salvar" +} diff --git a/worklenz-frontend/public/locales/pt/tasks/task-table-bulk-actions.json b/worklenz-frontend/public/locales/pt/tasks/task-table-bulk-actions.json new file mode 100644 index 00000000..40147210 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/tasks/task-table-bulk-actions.json @@ -0,0 +1,24 @@ +{ + "taskSelected": "Tarefa selecionada", + "tasksSelected": "Tarefas selecionadas", + "changeStatus": "Alterar Status/ Prioridade/ Fases", + "changeLabel": "Alterar Etiqueta", + "assignToMe": "Atribuir a mim", + "changeAssignees": "Alterar Assignados", + "archive": "Arquivar", + "unarchive": "Desarquivar", + "delete": "Deletar", + "moreOptions": "Mais opções", + "deselectAll": "Desmarcar todas", + "status": "Status", + "priority": "Prioridade", + "phase": "Fase", + "member": "Membro", + "createTaskTemplate": "Criar Modelo de Tarefa", + "apply": "Aplicar", + "createLabel": "+ Criar etiqueta", + "hitEnterToCreate": "Pressione Enter para criar", + "pendingInvitation": "Convite Pendente", + "noMatchingLabels": "Nenhuma etiqueta correspondente", + "noLabels": "Sem etiquetas" +} \ No newline at end of file diff --git a/worklenz-frontend/public/locales/pt/template-drawer.json b/worklenz-frontend/public/locales/pt/template-drawer.json new file mode 100644 index 00000000..cb79d2bf --- /dev/null +++ b/worklenz-frontend/public/locales/pt/template-drawer.json @@ -0,0 +1,19 @@ +{ + "title": "Editar Template de Tarefa", + "cancelText": "Cancelar", + "saveText": "Salvar", + "templateNameText": "Nome do Template", + "selectedTasks": "Tarefas Selecionadas", + "removeTask": "Remover", + "description": "Descrição", + "phase": "Fase", + "statuses": "Status", + "priorities": "Prioridades", + "labels": "Rótulos", + "tasks": "Tarefas", + "noTemplateSelected": "Nenhum template selecionado", + "noDescription": "Sem descrição", + "worklenzTemplates": "Templates de Worklenz", + "yourTemplatesLibrary": "Sua Biblioteca", + "searchTemplates": "Pesquisar Templates" +} diff --git a/worklenz-frontend/public/locales/pt/templateDrawer.json b/worklenz-frontend/public/locales/pt/templateDrawer.json new file mode 100644 index 00000000..c4d970c6 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/templateDrawer.json @@ -0,0 +1,23 @@ +{ + "bugTracking": "Rastreamento de Bugs", + "construction": "Construção", + "designCreative": "Design e Criatividade", + "education": "Educação", + "finance": "Finanças", + "hrRecruiting": "RH e Recrutamento", + "informationTechnology": "Tecnologia da Informação", + "legal": "Jurídico", + "manufacturing": "Manufatura", + "marketing": "Marketing", + "nonprofit": "Sem Fins Lucrativos", + "personalUse": "Uso Pessoal", + "salesCRM": "Vendas e CRM", + "serviceConsulting": "Serviço e Consultoria", + "softwareDevelopment": "Desenvolvimento de Software", + "description": "Descrição", + "phase": "Fase", + "statuses": "Status", + "priorities": "Prioridades", + "labels": "Rótulos", + "tasks": "Tarefas" +} diff --git a/worklenz-frontend/public/locales/pt/time-report.json b/worklenz-frontend/public/locales/pt/time-report.json new file mode 100644 index 00000000..8d09db4c --- /dev/null +++ b/worklenz-frontend/public/locales/pt/time-report.json @@ -0,0 +1,44 @@ +{ + "includeArchivedProjects": "Incluir Projetos Arquivados", + "export": "Exportar", + "timeSheet": "Folha de Tempo", + + "searchByName": "Pesquisar por nome", + "selectAll": "Selecionar Todos", + "teams": "Equipes", + + "searchByProject": "Pesquisar por nome do projeto", + "projects": "Projetos", + + "searchByCategory": "Pesquisar por nome da categoria", + "categories": "Categorias", + + "billable": "Cobrável", + "nonBillable": "Não Cobrável", + + "total": "Total", + + "projectsTimeSheet": "Folha de Tempo dos Projetos", + + "loggedTime": "Tempo Registrado (horas)", + + "exportToExcel": "Exportar para Excel", + "logged": "registrado", + "for": "para", + + "membersTimeSheet": "Folha de Tempo dos Membros", + "member": "Membro", + + "estimatedVsActual": "Estimado vs Real", + "workingDays": "Dias de Trabalho", + "manDays": "Dias-Homem", + "days": "Dias", + "estimatedDays": "Dias Estimados", + "actualDays": "Dias Reais", + + "noCategories": "Nenhuma categoria encontrada", + "noCategory": "Nenhuma Categoria", + "noProjects": "Nenhum projeto encontrado", + "noTeams": "Nenhum time encontrado", + "noData": "Nenhum dado encontrado" +} diff --git a/worklenz-frontend/public/locales/pt/unauthorized.json b/worklenz-frontend/public/locales/pt/unauthorized.json new file mode 100644 index 00000000..fa542df0 --- /dev/null +++ b/worklenz-frontend/public/locales/pt/unauthorized.json @@ -0,0 +1,5 @@ +{ + "title": "¡Não autorizado!", + "subtitle": "Você não tem permissão para acessar esta página", + "button": "Ir para Início" +} \ No newline at end of file diff --git a/worklenz-frontend/public/scheduler-data/dates-list copy.json b/worklenz-frontend/public/scheduler-data/dates-list copy.json new file mode 100644 index 00000000..392dcaea --- /dev/null +++ b/worklenz-frontend/public/scheduler-data/dates-list copy.json @@ -0,0 +1,2268 @@ +{ + "date_data": [ + { + "month": "Jan 2025", + "weeks": [], + "days": [ + { + "day": 1, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 2, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 3, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 4, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 5, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 6, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 7, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 8, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 9, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 10, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 11, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 12, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 13, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 14, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 15, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 16, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 17, + "name": "Fri", + "isWeekend": false, + "isToday": true + }, + { + "day": 18, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 19, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 20, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 21, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 22, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 23, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 24, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 25, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 26, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 27, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 28, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 29, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 30, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 31, + "name": "Fri", + "isWeekend": false, + "isToday": false + } + ] + }, + { + "month": "Feb 2025", + "weeks": [], + "days": [ + { + "day": 1, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 2, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 3, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 4, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 5, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 6, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 7, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 8, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 9, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 10, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 11, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 12, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 13, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 14, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 15, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 16, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 17, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 18, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 19, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 20, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 21, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 22, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 23, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 24, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 25, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 26, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 27, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 28, + "name": "Fri", + "isWeekend": false, + "isToday": false + } + ] + }, + { + "month": "Mar 2025", + "weeks": [], + "days": [ + { + "day": 1, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 2, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 3, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 4, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 5, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 6, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 7, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 8, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 9, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 10, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 11, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 12, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 13, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 14, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 15, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 16, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 17, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 18, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 19, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 20, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 21, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 22, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 23, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 24, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 25, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 26, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 27, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 28, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 29, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 30, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 31, + "name": "Mon", + "isWeekend": false, + "isToday": false + } + ] + }, + { + "month": "Apr 2025", + "weeks": [], + "days": [ + { + "day": 1, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 2, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 3, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 4, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 5, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 6, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 7, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 8, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 9, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 10, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 11, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 12, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 13, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 14, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 15, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 16, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 17, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 18, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 19, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 20, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 21, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 22, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 23, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 24, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 25, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 26, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 27, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 28, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 29, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 30, + "name": "Wed", + "isWeekend": false, + "isToday": false + } + ] + }, + { + "month": "May 2025", + "weeks": [], + "days": [ + { + "day": 1, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 2, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 3, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 4, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 5, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 6, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 7, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 8, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 9, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 10, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 11, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 12, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 13, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 14, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 15, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 16, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 17, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 18, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 19, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 20, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 21, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 22, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 23, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 24, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 25, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 26, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 27, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 28, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 29, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 30, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 31, + "name": "Sat", + "isWeekend": true, + "isToday": false + } + ] + }, + { + "month": "Jun 2025", + "weeks": [], + "days": [ + { + "day": 1, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 2, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 3, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 4, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 5, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 6, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 7, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 8, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 9, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 10, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 11, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 12, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 13, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 14, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 15, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 16, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 17, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 18, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 19, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 20, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 21, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 22, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 23, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 24, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 25, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 26, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 27, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 28, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 29, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 30, + "name": "Mon", + "isWeekend": false, + "isToday": false + } + ] + }, + { + "month": "Jul 2025", + "weeks": [], + "days": [ + { + "day": 1, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 2, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 3, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 4, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 5, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 6, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 7, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 8, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 9, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 10, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 11, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 12, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 13, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 14, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 15, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 16, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 17, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 18, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 19, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 20, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 21, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 22, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 23, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 24, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 25, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 26, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 27, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 28, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 29, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 30, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 31, + "name": "Thu", + "isWeekend": false, + "isToday": false + } + ] + }, + { + "month": "Aug 2025", + "weeks": [], + "days": [ + { + "day": 1, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 2, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 3, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 4, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 5, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 6, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 7, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 8, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 9, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 10, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 11, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 12, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 13, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 14, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 15, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 16, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 17, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 18, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 19, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 20, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 21, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 22, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 23, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 24, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 25, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 26, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 27, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 28, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 29, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 30, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 31, + "name": "Sun", + "isWeekend": true, + "isToday": false + } + ] + }, + { + "month": "Sep 2025", + "weeks": [], + "days": [ + { + "day": 1, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 2, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 3, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 4, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 5, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 6, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 7, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 8, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 9, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 10, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 11, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 12, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 13, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 14, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 15, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 16, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 17, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 18, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 19, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 20, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 21, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 22, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 23, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 24, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 25, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 26, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 27, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 28, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 29, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 30, + "name": "Tue", + "isWeekend": false, + "isToday": false + } + ] + }, + { + "month": "Oct 2025", + "weeks": [], + "days": [ + { + "day": 1, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 2, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 3, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 4, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 5, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 6, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 7, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 8, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 9, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 10, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 11, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 12, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 13, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 14, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 15, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 16, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 17, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 18, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 19, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 20, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 21, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 22, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 23, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 24, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 25, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 26, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 27, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 28, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 29, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 30, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 31, + "name": "Fri", + "isWeekend": false, + "isToday": false + } + ] + }, + { + "month": "Nov 2025", + "weeks": [], + "days": [ + { + "day": 1, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 2, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 3, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 4, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 5, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 6, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 7, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 8, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 9, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 10, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 11, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 12, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 13, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 14, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 15, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 16, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 17, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 18, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 19, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 20, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 21, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 22, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 23, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 24, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 25, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 26, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 27, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 28, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 29, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 30, + "name": "Sun", + "isWeekend": true, + "isToday": false + } + ] + }, + { + "month": "Dec 2025", + "weeks": [], + "days": [ + { + "day": 1, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 2, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 3, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 4, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 5, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 6, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 7, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 8, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 9, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 10, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 11, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 12, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 13, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 14, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 15, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 16, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 17, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 18, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 19, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 20, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 21, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 22, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 23, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 24, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 25, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 26, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 27, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 28, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 29, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 30, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 31, + "name": "Wed", + "isWeekend": false, + "isToday": false + } + ] + } + ], + "chart_start": "2024-01-01", + "chart_end": "2026-01-31" +} diff --git a/worklenz-frontend/public/scheduler-data/dates-list.json b/worklenz-frontend/public/scheduler-data/dates-list.json new file mode 100644 index 00000000..197ac291 --- /dev/null +++ b/worklenz-frontend/public/scheduler-data/dates-list.json @@ -0,0 +1,198 @@ +{ + "date_data": [ + { + "month": "Jan 2025", + "weeks": [], + "days": [ + { + "day": 1, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 2, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 3, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 4, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 5, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 6, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 7, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 8, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 9, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 10, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 11, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 12, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 13, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 14, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 15, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 16, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 17, + "name": "Fri", + "isWeekend": false, + "isToday": true + }, + { + "day": 18, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 19, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 20, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 21, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 22, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 23, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 24, + "name": "Fri", + "isWeekend": false, + "isToday": false + }, + { + "day": 25, + "name": "Sat", + "isWeekend": true, + "isToday": false + }, + { + "day": 26, + "name": "Sun", + "isWeekend": true, + "isToday": false + }, + { + "day": 27, + "name": "Mon", + "isWeekend": false, + "isToday": false + }, + { + "day": 28, + "name": "Tue", + "isWeekend": false, + "isToday": false + }, + { + "day": 29, + "name": "Wed", + "isWeekend": false, + "isToday": false + }, + { + "day": 30, + "name": "Thu", + "isWeekend": false, + "isToday": false + }, + { + "day": 31, + "name": "Fri", + "isWeekend": false, + "isToday": false + } + ] + } + ], + "chart_start": "2024-01-01", + "chart_end": "2025-01-31" +} diff --git a/worklenz-frontend/public/scheduler-data/team-data copy.json b/worklenz-frontend/public/scheduler-data/team-data copy.json new file mode 100644 index 00000000..053434ec --- /dev/null +++ b/worklenz-frontend/public/scheduler-data/team-data copy.json @@ -0,0 +1,1182 @@ +[ + { + "name": "Raveesha Dilanka", + "id": "1", + "projects": [ + { + "name": "Project A", + "id": "1", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-01-01", + "end": "2025-01-05" + }, + "indicator_offset": 0, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project B", + "id": "2", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-01-06", + "end": "2025-01-10" + }, + "indicator_offset": 375, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project C", + "id": "3", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-01-11", + "end": "2025-01-15" + }, + "indicator_offset": 750, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project D", + "id": "4", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-01-16", + "end": "2025-01-20" + }, + "indicator_offset": 1125, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project E", + "id": "5", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-01-21", + "end": "2025-01-25" + }, + "indicator_offset": 1500, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project F", + "id": "6", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-01-26", + "end": "2025-01-30" + }, + "indicator_offset": 1875, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project G", + "id": "7", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-01-31", + "end": "2025-02-04" + }, + "indicator_offset": 2250, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project H", + "id": "8", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-02-05", + "end": "2025-02-09" + }, + "indicator_offset": 2625, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project I", + "id": "9", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-02-10", + "end": "2025-02-14" + }, + "indicator_offset": 3000, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project J", + "id": "10", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-02-15", + "end": "2025-02-19" + }, + "indicator_offset": 3375, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project K", + "id": "11", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-02-20", + "end": "2025-02-24" + }, + "indicator_offset": 3750, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project L", + "id": "12", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-02-25", + "end": "2025-02-29" + }, + "indicator_offset": 4125, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project M", + "id": "13", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-03-01", + "end": "2025-03-05" + }, + "indicator_offset": 4500, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project N", + "id": "14", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-03-06", + "end": "2025-03-10" + }, + "indicator_offset": 4875, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project O", + "id": "15", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-03-11", + "end": "2025-03-15" + }, + "indicator_offset": 5250, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project P", + "id": "16", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-03-16", + "end": "2025-03-20" + }, + "indicator_offset": 5625, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project Q", + "id": "17", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-03-21", + "end": "2025-03-25" + }, + "indicator_offset": 6000, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project R", + "id": "18", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-03-26", + "end": "2025-03-30" + }, + "indicator_offset": 6375, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project S", + "id": "19", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-03-31", + "end": "2025-04-04" + }, + "indicator_offset": 6750, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project T", + "id": "20", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-04-05", + "end": "2025-04-09" + }, + "indicator_offset": 7125, + "indicator_width": 375, + "tasks": [] + } + ] + }, + { + "name": "Sarah Smith", + "id": "2", + "projects": [ + { + "name": "Project X", + "id": "21", + "hours_per_day": 6, + "total_hours": 36, + "date_union": { + "start": "2025-01-15", + "end": "2025-01-20" + }, + "indicator_offset": 1050, + "indicator_width": 450, + "tasks": [] + }, + { + "name": "Project Y", + "id": "22", + "hours_per_day": 5, + "total_hours": 35, + "date_union": { + "start": "2025-01-25", + "end": "2025-01-31" + }, + "indicator_offset": 1800, + "indicator_width": 525, + "tasks": [] + }, + { + "name": "Project Z", + "id": "23", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-02-01", + "end": "2025-02-05" + }, + "indicator_offset": 2325, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project AA", + "id": "24", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-02-06", + "end": "2025-02-10" + }, + "indicator_offset": 2700, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project BB", + "id": "25", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-02-11", + "end": "2025-02-15" + }, + "indicator_offset": 3075, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project CC", + "id": "26", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-02-16", + "end": "2025-02-20" + }, + "indicator_offset": 3450, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project DD", + "id": "27", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-02-21", + "end": "2025-02-25" + }, + "indicator_offset": 3825, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project EE", + "id": "28", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-02-26", + "end": "2025-03-02" + }, + "indicator_offset": 4200, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project FF", + "id": "29", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-03-03", + "end": "2025-03-07" + }, + "indicator_offset": 4575, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project GG", + "id": "30", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-03-08", + "end": "2025-03-12" + }, + "indicator_offset": 4950, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project HH", + "id": "31", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-03-13", + "end": "2025-03-17" + }, + "indicator_offset": 5325, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project II", + "id": "32", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-03-18", + "end": "2025-03-22" + }, + "indicator_offset": 5700, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project JJ", + "id": "33", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-03-23", + "end": "2025-03-27" + }, + "indicator_offset": 6075, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project KK", + "id": "34", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-03-28", + "end": "2025-04-01" + }, + "indicator_offset": 6450, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project LL", + "id": "35", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-04-02", + "end": "2025-04-06" + }, + "indicator_offset": 6825, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project MM", + "id": "36", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-04-07", + "end": "2025-04-11" + }, + "indicator_offset": 7200, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project NN", + "id": "37", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-04-12", + "end": "2025-04-16" + }, + "indicator_offset": 7575, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project OO", + "id": "38", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-04-17", + "end": "2025-04-21" + }, + "indicator_offset": 7950, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project PP", + "id": "39", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-04-22", + "end": "2025-04-26" + }, + "indicator_offset": 8325, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project QQ", + "id": "40", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-04-27", + "end": "2025-05-01" + }, + "indicator_offset": 8700, + "indicator_width": 375, + "tasks": [] + } + ] + }, + { + "name": "John Doe", + "id": "3", + "projects": [ + { + "name": "Project D", + "id": "4", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-01-01", + "end": "2025-01-05" + }, + "indicator_offset": 0, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project E", + "id": "5", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-01-06", + "end": "2025-01-10" + }, + "indicator_offset": 375, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project F", + "id": "6", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-01-11", + "end": "2025-01-15" + }, + "indicator_offset": 750, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project G", + "id": "7", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-01-16", + "end": "2025-01-20" + }, + "indicator_offset": 1125, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project H", + "id": "8", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-01-21", + "end": "2025-01-25" + }, + "indicator_offset": 1500, + "indicator_width": 375, + "tasks": [] + } + ] + }, + { + "name": "Alice Smith", + "id": "4", + "projects": [ + { + "name": "Project I", + "id": "9", + "hours_per_day": 4, + "total_hours": 20, + "date_union": { + "start": "2025-01-03", + "end": "2025-01-07" + }, + "indicator_offset": 125, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project J", + "id": "10", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-01-08", + "end": "2025-01-12" + }, + "indicator_offset": 500, + "indicator_width": 375, + "tasks": [] + } + ] + }, + { + "name": "Bob Johnson", + "id": "5", + "projects": [ + { + "name": "Project K", + "id": "11", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-01-10", + "end": "2025-01-14" + }, + "indicator_offset": 750, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project L", + "id": "12", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-01-15", + "end": "2025-01-19" + }, + "indicator_offset": 1125, + "indicator_width": 375, + "tasks": [] + } + ] + }, + { + "name": "Emma Davis", + "id": "6", + "projects": [ + { + "name": "Project M", + "id": "13", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-01-05", + "end": "2025-01-09" + }, + "indicator_offset": 375, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project N", + "id": "14", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-01-10", + "end": "2025-01-14" + }, + "indicator_offset": 750, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project O", + "id": "15", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-01-15", + "end": "2025-01-19" + }, + "indicator_offset": 1125, + "indicator_width": 375, + "tasks": [] + } + ] + }, + { + "name": "Sophia Taylor", + "id": "7", + "projects": [ + { + "name": "Project P", + "id": "16", + "hours_per_day": 4, + "total_hours": 20, + "date_union": { + "start": "2025-01-02", + "end": "2025-01-06" + }, + "indicator_offset": 125, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project Q", + "id": "17", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-01-07", + "end": "2025-01-11" + }, + "indicator_offset": 500, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project R", + "id": "18", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-01-12", + "end": "2025-01-16" + }, + "indicator_offset": 875, + "indicator_width": 375, + "tasks": [] + } + ] + }, + { + "name": "Liam Brown", + "id": "8", + "projects": [ + { + "name": "Project S", + "id": "19", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-01-05", + "end": "2025-01-09" + }, + "indicator_offset": 375, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project T", + "id": "20", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-01-10", + "end": "2025-01-14" + }, + "indicator_offset": 750, + "indicator_width": 375, + "tasks": [] + } + ] + }, + { + "name": "Mia Wilson", + "id": "9", + "projects": [ + { + "name": "Project U", + "id": "21", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-01-09", + "end": "2025-01-13" + }, + "indicator_offset": 750, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project V", + "id": "22", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-01-14", + "end": "2025-01-18" + }, + "indicator_offset": 1125, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project W", + "id": "23", + "hours_per_day": 4, + "total_hours": 20, + "date_union": { + "start": "2025-01-19", + "end": "2025-01-23" + }, + "indicator_offset": 1500, + "indicator_width": 375, + "tasks": [] + } + ] + }, + { + "name": "Sophia Wilson", + "id": "10", + "projects": [ + { + "name": "Project P", + "id": "10", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-01-01", + "end": "2025-01-05" + }, + "indicator_offset": 0, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project Q", + "id": "11", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-01-06", + "end": "2025-01-10" + }, + "indicator_offset": 375, + "indicator_width": 375, + "tasks": [] + } + ] + }, + { + "name": "Michael Brown", + "id": "11", + "projects": [ + { + "name": "Project R", + "id": "12", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-01-02", + "end": "2025-01-06" + }, + "indicator_offset": 0, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project S", + "id": "13", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-01-07", + "end": "2025-01-11" + }, + "indicator_offset": 375, + "indicator_width": 375, + "tasks": [] + } + ] + }, + { + "name": "James Taylor", + "id": "12", + "projects": [ + { + "name": "Project T", + "id": "14", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-01-03", + "end": "2025-01-07" + }, + "indicator_offset": 0, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project U", + "id": "15", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-01-08", + "end": "2025-01-12" + }, + "indicator_offset": 375, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project V", + "id": "16", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-01-13", + "end": "2025-01-17" + }, + "indicator_offset": 750, + "indicator_width": 375, + "tasks": [] + } + ] + }, + { + "name": "Olivia Martinez", + "id": "13", + "projects": [ + { + "name": "Project W", + "id": "17", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-01-04", + "end": "2025-01-08" + }, + "indicator_offset": 0, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project X", + "id": "18", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-01-09", + "end": "2025-01-13" + }, + "indicator_offset": 375, + "indicator_width": 375, + "tasks": [] + } + ] + }, + { + "name": "Liam Anderson", + "id": "14", + "projects": [ + { + "name": "Project Y", + "id": "19", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-01-01", + "end": "2025-01-05" + }, + "indicator_offset": 0, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project Z", + "id": "20", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-01-06", + "end": "2025-01-10" + }, + "indicator_offset": 375, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project AA", + "id": "21", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-01-11", + "end": "2025-01-15" + }, + "indicator_offset": 750, + "indicator_width": 375, + "tasks": [] + } + ] + }, + { + "name": "Olivia Martin", + "id": "15", + "projects": [ + { + "name": "Project P", + "id": "16", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-01-01", + "end": "2025-01-05" + }, + "indicator_offset": 0, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project Q", + "id": "17", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-01-06", + "end": "2025-01-10" + }, + "indicator_offset": 375, + "indicator_width": 375, + "tasks": [] + } + ] + }, + { + "name": "Sophia White", + "id": "18", + "projects": [ + { + "name": "Project R", + "id": "19", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-01-03", + "end": "2025-01-07" + }, + "indicator_offset": 125, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project S", + "id": "20", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-01-08", + "end": "2025-01-12" + }, + "indicator_offset": 500, + "indicator_width": 375, + "tasks": [] + } + ] + }, + { + "name": "Liam Harris", + "id": "21", + "projects": [ + { + "name": "Project T", + "id": "22", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-01-05", + "end": "2025-01-09" + }, + "indicator_offset": 375, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project U", + "id": "23", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-01-10", + "end": "2025-01-14" + }, + "indicator_offset": 750, + "indicator_width": 375, + "tasks": [] + } + ] + }, + { + "name": "Mason Lee", + "id": "24", + "projects": [ + { + "name": "Project V", + "id": "25", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-01-10", + "end": "2025-01-14" + }, + "indicator_offset": 750, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project W", + "id": "26", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-01-15", + "end": "2025-01-19" + }, + "indicator_offset": 1125, + "indicator_width": 375, + "tasks": [] + } + ] + }, + { + "name": "Isabella Clark", + "id": "27", + "projects": [ + { + "name": "Project X", + "id": "28", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-01-06", + "end": "2025-01-10" + }, + "indicator_offset": 375, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project Y", + "id": "29", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-01-11", + "end": "2025-01-15" + }, + "indicator_offset": 750, + "indicator_width": 375, + "tasks": [] + } + ] + } +] diff --git a/worklenz-frontend/public/scheduler-data/team-data.json b/worklenz-frontend/public/scheduler-data/team-data.json new file mode 100644 index 00000000..65a28a02 --- /dev/null +++ b/worklenz-frontend/public/scheduler-data/team-data.json @@ -0,0 +1,268 @@ +[ + { + "name": "Raveesha Dilanka", + "id": "1", + "projects": [ + { + "name": "Project A", + "id": "1", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-01-01", + "end": "2025-01-05" + }, + "indicator_offset": 0, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project B", + "id": "2", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-01-06", + "end": "2025-01-10" + }, + "indicator_offset": 375, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project C", + "id": "3", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-01-11", + "end": "2025-01-15" + }, + "indicator_offset": 750, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project D", + "id": "4", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-01-16", + "end": "2025-01-20" + }, + "indicator_offset": 1125, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project E", + "id": "5", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-01-21", + "end": "2025-01-25" + }, + "indicator_offset": 1500, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project F", + "id": "6", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-01-26", + "end": "2025-01-30" + }, + "indicator_offset": 1875, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project G", + "id": "7", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-01-31", + "end": "2025-02-04" + }, + "indicator_offset": 2250, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project H", + "id": "8", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-02-05", + "end": "2025-02-09" + }, + "indicator_offset": 2625, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project I", + "id": "9", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-02-10", + "end": "2025-02-14" + }, + "indicator_offset": 3000, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project J", + "id": "10", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-02-15", + "end": "2025-02-19" + }, + "indicator_offset": 3375, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project K", + "id": "11", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-02-20", + "end": "2025-02-24" + }, + "indicator_offset": 3750, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project L", + "id": "12", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-02-25", + "end": "2025-02-29" + }, + "indicator_offset": 4125, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project M", + "id": "13", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-03-01", + "end": "2025-03-05" + }, + "indicator_offset": 4500, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project N", + "id": "14", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-03-06", + "end": "2025-03-10" + }, + "indicator_offset": 4875, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project O", + "id": "15", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-03-11", + "end": "2025-03-15" + }, + "indicator_offset": 5250, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project P", + "id": "16", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-03-16", + "end": "2025-03-20" + }, + "indicator_offset": 5625, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project Q", + "id": "17", + "hours_per_day": 8, + "total_hours": 40, + "date_union": { + "start": "2025-03-21", + "end": "2025-03-25" + }, + "indicator_offset": 6000, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project R", + "id": "18", + "hours_per_day": 6, + "total_hours": 30, + "date_union": { + "start": "2025-03-26", + "end": "2025-03-30" + }, + "indicator_offset": 6375, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project S", + "id": "19", + "hours_per_day": 7, + "total_hours": 35, + "date_union": { + "start": "2025-03-31", + "end": "2025-04-04" + }, + "indicator_offset": 6750, + "indicator_width": 375, + "tasks": [] + }, + { + "name": "Project T", + "id": "20", + "hours_per_day": 5, + "total_hours": 25, + "date_union": { + "start": "2025-04-05", + "end": "2025-04-09" + }, + "indicator_offset": 7125, + "indicator_width": 375, + "tasks": [] + } + ] + } +] diff --git a/worklenz-frontend/scripts/copy-tinymce.js b/worklenz-frontend/scripts/copy-tinymce.js new file mode 100644 index 00000000..8f801c46 --- /dev/null +++ b/worklenz-frontend/scripts/copy-tinymce.js @@ -0,0 +1,39 @@ +const fs = require('fs'); +const path = require('path'); + +// Create the directory if it doesn't exist +const targetDir = path.join(__dirname, '..', 'public', 'tinymce'); +if (!fs.existsSync(path.join(__dirname, '..', 'public'))) { + fs.mkdirSync(path.join(__dirname, '..', 'public')); +} +if (!fs.existsSync(targetDir)) { + fs.mkdirSync(targetDir); +} + +// Copy the tinymce files +const sourceDir = path.join(__dirname, '..', 'node_modules', 'tinymce'); +copyFolderRecursiveSync(sourceDir, path.join(__dirname, '..', 'public')); + +function copyFolderRecursiveSync(source, target) { + const targetFolder = path.join(target, path.basename(source)); + + // Create target folder if it doesn't exist + if (!fs.existsSync(targetFolder)) { + fs.mkdirSync(targetFolder); + } + + // Copy files + if (fs.lstatSync(source).isDirectory()) { + const files = fs.readdirSync(source); + files.forEach(function(file) { + const curSource = path.join(source, file); + if (fs.lstatSync(curSource).isDirectory()) { + copyFolderRecursiveSync(curSource, targetFolder); + } else { + fs.copyFileSync(curSource, path.join(targetFolder, file)); + } + }); + } +} + +console.log('TinyMCE files copied successfully!'); \ No newline at end of file diff --git a/worklenz-frontend/src/App.tsx b/worklenz-frontend/src/App.tsx new file mode 100644 index 00000000..13abc6f8 --- /dev/null +++ b/worklenz-frontend/src/App.tsx @@ -0,0 +1,48 @@ +// Core dependencies +import React, { Suspense, useEffect } from 'react'; +import { RouterProvider } from 'react-router-dom'; +import i18next from 'i18next'; + +// Components +import ThemeWrapper from './features/theme/ThemeWrapper'; +import PreferenceSelector from './components/PreferenceSelector'; + +// Routes +import router from './app/routes'; + +// Hooks & Utils +import { useAppSelector } from './hooks/useAppSelector'; +import { initMixpanel } from './utils/mixpanelInit'; + +// Types & Constants +import { Language } from './features/i18n/localesSlice'; +import logger from './utils/errorLogger'; +import { SuspenseFallback } from './components/suspense-fallback/suspense-fallback'; + +const App: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const themeMode = useAppSelector(state => state.themeReducer.mode); + const language = useAppSelector(state => state.localesReducer.lng); + + initMixpanel(import.meta.env.VITE_MIXPANEL_TOKEN as string); + + useEffect(() => { + document.documentElement.setAttribute('data-theme', themeMode); + }, [themeMode]); + + useEffect(() => { + i18next.changeLanguage(language || Language.EN, err => { + if (err) return logger.error('Error changing language', err); + }); + }, [language]); + + return ( + }> + + + + + + ); +}; + +export default App; diff --git a/worklenz-frontend/src/api/admin-center/admin-center.api.service.ts b/worklenz-frontend/src/api/admin-center/admin-center.api.service.ts new file mode 100644 index 00000000..857efb91 --- /dev/null +++ b/worklenz-frontend/src/api/admin-center/admin-center.api.service.ts @@ -0,0 +1,258 @@ +import { IServerResponse } from '@/types/common.types'; +import apiClient from '../api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { + IOrganization, + IOrganizationUser, + IOrganizationTeam, + IOrganizationUsersGetRequest, + IOrganizationTeamGetRequest, + IOrganizationProjectsGetResponse, + IBillingConfigurationCountry, + IBillingConfiguration, + IBillingAccountInfo, + IUpgradeSubscriptionPlanResponse, + IPricingPlans, + IBillingTransaction, + IBillingChargesResponse, + IStorageInfo, + IFreePlanSettings, + IBillingAccountStorage, +} from '@/types/admin-center/admin-center.types'; +import { IClient } from '@/types/client.types'; +import { toQueryString } from '@/utils/toQueryString'; + +const rootUrl = `${API_BASE_URL}/admin-center`; + +export interface IOrganizationUserRequestParams { + page: number; + pageSize: number; + sort: string; + order: string; + searchTerm: string; +} + +export interface IOrganizationTeamRequestParams { + index: number; + size: number; + field: string | null; + order: string | null; + search: string | null; +} + +export const adminCenterApiService = { + async getOrganizationDetails(): Promise> { + const response = await apiClient.get>(`${rootUrl}/organization`); + return response.data; + }, + + async getOrganizationAdmins(): Promise> { + const response = await apiClient.get>( + `${rootUrl}/organization/admins` + ); + return response.data; + }, + + async updateOrganizationName(body: IClient): Promise> { + const response = await apiClient.put>( + `${rootUrl}/organization`, + body + ); + return response.data; + }, + + async updateOwnerContactNumber(body: { + contact_number: string; + }): Promise> { + const response = await apiClient.put>( + `${rootUrl}/organization/owner/contact-number`, + body + ); + return response.data; + }, + + async getOrganizationUsers( + requestParams: IOrganizationUserRequestParams + ): Promise> { + const params = new URLSearchParams({ + index: requestParams.page.toString(), + size: requestParams.pageSize.toString(), + ...(requestParams.sort && { field: requestParams.sort }), + ...(requestParams.order && { order: requestParams.order }), + ...(requestParams.searchTerm && { search: requestParams.searchTerm }), + }); + const response = await apiClient.get>( + `${rootUrl}/organization/users?${params}` + ); + return response.data; + }, + + async getOrganizationTeams( + requestParams: IOrganizationTeamRequestParams + ): Promise> { + const params = new URLSearchParams({ + index: requestParams.index.toString(), + size: requestParams.size.toString(), + ...(requestParams.field && { field: requestParams.field }), + ...(requestParams.order && { order: requestParams.order }), + ...(requestParams.search && { search: requestParams.search }), + }); + const response = await apiClient.get>( + `${rootUrl}/organization/teams?${params}` + ); + return response.data; + }, + + async getOrganizationTeam(team_id: string): Promise> { + const response = await apiClient.get>( + `${rootUrl}/organization/team/${team_id}` + ); + return response.data; + }, + + async updateTeam( + team_id: string, + team_members: IOrganizationUser[] + ): Promise> { + const response = await apiClient.put>( + `${rootUrl}/organization/team/${team_id}`, + team_members + ); + return response.data; + }, + + async deleteTeam(id: string): Promise> { + const response = await apiClient.delete>( + `${rootUrl}/organization/team/${id}` + ); + return response.data; + }, + + async removeTeamMember(team_member_id: string, team_id: string): Promise> { + const response = await apiClient.put>( + `${rootUrl}/organization/team-member/${team_member_id}`, + { teamId: team_id } + ); + return response.data; + }, + + async getOrganizationProjects( + requestParams: IOrganizationTeamRequestParams + ): Promise> { + const params = new URLSearchParams({ + index: requestParams.index.toString(), + size: requestParams.size.toString(), + ...(requestParams.field && { field: requestParams.field }), + ...(requestParams.order && { order: requestParams.order }), + ...(requestParams.search && { search: requestParams.search }), + }); + const response = await apiClient.get>( + `${rootUrl}/organization/projects?${params}` + ); + return response.data; + }, + + + // Billing - Configuration + async getCountries(): Promise> { + const response = await apiClient.get>( + `${rootUrl}/billing/countries` + ); + return response.data; + }, + + async getBillingConfiguration(): Promise> { + const response = await apiClient.get>( + `${rootUrl}/billing/configuration` + ); + return response.data; + }, + + async updateBillingConfiguration(body: IBillingConfiguration): Promise> { + const response = await apiClient.put>( + `${rootUrl}/billing/configuration`, + body + ); + return response.data; + }, + + // Billing - Current Bill + async getCharges(): Promise> { + const response = await apiClient.get>(`${rootUrl}/billing/charges`); + return response.data; + }, + + async getTransactions(): Promise> { + const response = await apiClient.get>(`${rootUrl}/billing/transactions`); + return response.data; + }, + + async getBillingAccountInfo(): Promise> { + const response = await apiClient.get>(`${rootUrl}/billing/info`); + return response.data; + }, + + async getFreePlanSettings(): Promise> { + const response = await apiClient.get>(`${rootUrl}/billing/free-plan`); + return response.data; + }, + + async upgradePlan(plan: string): Promise> { + const response = await apiClient.get>(`${rootUrl}/billing/upgrade-plan${toQueryString({plan})}`); + return response.data; + }, + + async changePlan(plan: string): Promise> { + const response = await apiClient.get>(`${rootUrl}/billing/change-plan${toQueryString({plan})}`); + return response.data; + }, + + async getPlans(): Promise> { + const response = await apiClient.get>(`${rootUrl}/billing/plans`); + return response.data; + }, + + async getStorageInfo(): Promise> { + const response = await apiClient.get>(`${rootUrl}/billing/storage`); + return response.data; + }, + + async pauseSubscription(): Promise> { + const response = await apiClient.get>(`${rootUrl}/billing/pause-plan`); + return response.data; + }, + + async resumeSubscription(): Promise> { + const response = await apiClient.get>(`${rootUrl}/billing/resume-plan`); + return response.data; + }, + + async cancelSubscription(): Promise> { + const response = await apiClient.get>(`${rootUrl}/billing/cancel-plan`); + return response.data; + }, + + async addMoreSeats(totalSeats: number): Promise> { + const response = await apiClient.post>(`${rootUrl}/billing/purchase-more-seats`, {seatCount: totalSeats}); + return response.data; + }, + + async redeemCode(code: string): Promise> { + const response = await apiClient.post>(`${rootUrl}/billing/redeem`, { + code, + }); + return response.data; + }, + + async getAccountStorage(): Promise> { + const response = await apiClient.get>(`${rootUrl}/billing/account-storage`); + return response.data; + }, + + async switchToFreePlan(teamId: string): Promise> { + const response = await apiClient.get>(`${rootUrl}/billing/switch-to-free-plan/${teamId}`); + return response.data; + }, + +}; + diff --git a/worklenz-frontend/src/api/admin-center/billing.api.service.ts b/worklenz-frontend/src/api/admin-center/billing.api.service.ts new file mode 100644 index 00000000..3d51cb3b --- /dev/null +++ b/worklenz-frontend/src/api/admin-center/billing.api.service.ts @@ -0,0 +1,35 @@ +import { API_BASE_URL } from '@/shared/constants'; +import { IServerResponse } from '@/types/common.types'; +import apiClient from '../api-client'; +import { toQueryString } from '@/utils/toQueryString'; +import { IUpgradeSubscriptionPlanResponse } from '@/types/admin-center/admin-center.types'; + +const rootUrl = `${API_BASE_URL}/billing`; +export const billingApiService = { + async upgradeToPaidPlan(plan: string, seatCount: number): Promise> { + const q = toQueryString({ plan, seatCount }); + const response = await apiClient.get>( + `${rootUrl}/upgrade-to-paid-plan${q}` + ); + return response.data; + }, + + async purchaseMoreSeats(seatCount: number): Promise> { + const response = await apiClient.post>( + `${rootUrl}/purchase-more-seats`, + { seatCount } + ); + return response.data; + }, + + async contactUs(contactNo: string): Promise> { + const response = await apiClient.get>( + `${rootUrl}/contact-us${toQueryString({ contactNo })}` + ); + return response.data; + } + + + + +}; diff --git a/worklenz-frontend/src/api/api-client.ts b/worklenz-frontend/src/api/api-client.ts new file mode 100644 index 00000000..e87e0b61 --- /dev/null +++ b/worklenz-frontend/src/api/api-client.ts @@ -0,0 +1,134 @@ +import axios, { AxiosError } from 'axios'; + +import alertService from '@/services/alerts/alertService'; +import logger from '@/utils/errorLogger'; + +export const getCsrfToken = (): string | null => { + const match = document.cookie.split('; ').find(cookie => cookie.startsWith('XSRF-TOKEN=')); + + if (!match) { + return null; + } + return decodeURIComponent(match.split('=')[1]); +}; + +// Function to refresh CSRF token if needed +export const refreshCsrfToken = async (): Promise => { + try { + // Make a GET request to the server to get a fresh CSRF token + await axios.get(`${import.meta.env.VITE_API_URL}/csrf-token`, { withCredentials: true }); + return getCsrfToken(); + } catch (error) { + console.error('Failed to refresh CSRF token:', error); + return null; + } +}; + +const apiClient = axios.create({ + baseURL: import.meta.env.VITE_API_URL, + withCredentials: true, + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, +}); + +// Request interceptor +apiClient.interceptors.request.use( + config => { + const token = getCsrfToken(); + if (token) { + config.headers['X-CSRF-Token'] = token; + } else { + console.warn('No CSRF token found'); + } + return config; + }, + error => Promise.reject(error) +); + +// Response interceptor with notification handling based on done flag +apiClient.interceptors.response.use( + response => { + // Handle 302 redirect + if (response.status === 302) { + const redirectUrl = response.headers.location; + if (redirectUrl) { + window.location.href = redirectUrl; + return response; + } + } + + if (response.data) { + const { title, message, auth_error, done } = response.data; + + if (message && message.charAt(0) !== '$') { + if (done) { + alertService.success(title || '', message); + } else { + alertService.error(title || '', message); + } + } else if (auth_error) { + alertService.error(title || 'Authentication Error', auth_error); + } + } + return response; + }, + async (error: AxiosError) => { + const { message, code, name } = error || {}; + const errorResponse = error.response; + + // Handle CSRF token errors + if (errorResponse?.status === 403 && + (typeof errorResponse.data === 'object' && + errorResponse.data !== null && + 'message' in errorResponse.data && + errorResponse.data.message === 'Invalid CSRF token' || + (error as any).code === 'EBADCSRFTOKEN')) { + alertService.error('Security Error', 'Invalid security token. Refreshing your session...'); + + // Try to refresh the CSRF token and retry the request + const newToken = await refreshCsrfToken(); + if (newToken && error.config) { + // Update the token in the failed request + error.config.headers['X-CSRF-Token'] = newToken; + // Retry the original request with the new token + return axios(error.config); + } else { + // If token refresh failed, redirect to login + window.location.href = '/auth/login'; + return Promise.reject(error); + } + } + + // Add 401 unauthorized handling + if (error.response?.status === 401) { + alertService.error('Session Expired', 'Please log in again'); + // Redirect to login page or trigger re-authentication + window.location.href = '/auth/login'; // Adjust this path as needed + return Promise.reject(error); + } + + const errorMessage = message || 'An unexpected error occurred'; + const errorTitle = 'Error'; + + if (error.code !== 'ERR_NETWORK') { + alertService.error(errorTitle, errorMessage); + } + + // Development logging + if (import.meta.env.VITE_APP_ENV === 'development') { + logger.error('API Error:', { + code, + name, + message, + headers: error.config?.headers, + cookies: document.cookie, + }); + } + + return Promise.reject(error); + } +); + +export default apiClient; diff --git a/worklenz-frontend/src/api/attachments/attachments.api.service.ts b/worklenz-frontend/src/api/attachments/attachments.api.service.ts new file mode 100644 index 00000000..e6a3c7c8 --- /dev/null +++ b/worklenz-frontend/src/api/attachments/attachments.api.service.ts @@ -0,0 +1,33 @@ +import { IServerResponse } from "@/types/common.types"; +import { IProjectAttachmentsViewModel } from "@/types/tasks/task-attachment-view-model"; +import apiClient from "../api-client"; +import { API_BASE_URL } from "@/shared/constants"; +import { toQueryString } from "@/utils/toQueryString"; + +const rootUrl = `${API_BASE_URL}/attachments`; + +export const attachmentsApiService = { + getTaskAttachments: async (taskId: string): Promise> => { + const response = await apiClient.get>(`${rootUrl}/tasks/${taskId}`); + return response.data; + }, + + getProjectAttachments: async (projectId: string, index: number, size: number): Promise> => { + const q = toQueryString({ index, size }); + const response = await apiClient.get>(`${rootUrl}/project/${projectId}${q}`); + return response.data; + }, + + downloadAttachment: async (id: string, filename: string): Promise> => { + const response = await apiClient.get>(`${rootUrl}/download?id=${id}&file=${filename}`); + return response.data; + }, + + deleteAttachment: async (id: string): Promise> => { + const response = await apiClient.delete>(`${rootUrl}/tasks/${id}`); + return response.data; + }, + +}; + + diff --git a/worklenz-frontend/src/api/auth/auth.api.service.ts b/worklenz-frontend/src/api/auth/auth.api.service.ts new file mode 100644 index 00000000..1e5de412 --- /dev/null +++ b/worklenz-frontend/src/api/auth/auth.api.service.ts @@ -0,0 +1,59 @@ +import { IServerResponse } from '@/types/common.types'; +import apiClient from '../api-client'; +import { + IUserLoginRequest, + IUserLoginResponse, + IAuthorizeResponse, +} from '@/types/auth/login.types'; +import { AUTH_API_BASE_URL } from '@/shared/constants'; + +const rootUrl = `${AUTH_API_BASE_URL}`; + +export const authApiService = { + async login(credentials: IUserLoginRequest): Promise { + const response = await apiClient.post(`${rootUrl}/login`, credentials); + return response.data; + }, + + async logout(): Promise> { + const response = await apiClient.get>(`${rootUrl}/logout`); + return response.data; + }, + + async verify(): Promise { + const response = await apiClient.get(`${rootUrl}/verify`); + return response.data; + }, + + async signUp(body: any): Promise> { + const response = await apiClient.post>(`${rootUrl}/signup`, body); + return response.data; + }, + + async signUpCheck(body: any): Promise> { + const response = await apiClient.post>(`${rootUrl}/signup/check`, body); + return response.data; + }, + + async resetPassword(email: string): Promise> { + const response = await apiClient.post>(`${rootUrl}/reset-password`, { + email, + }); + return response.data; + }, + + async updatePassword(values: any): Promise> { + const response = await apiClient.post>( + `${rootUrl}/update-password`, + values + ); + return response.data; + }, + + async verifyRecaptchaToken(token: string): Promise> { + const response = await apiClient.post>(`${rootUrl}/verify-captcha`, { + token, + }); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/clients/clients.api.service.ts b/worklenz-frontend/src/api/clients/clients.api.service.ts new file mode 100644 index 00000000..507ecedd --- /dev/null +++ b/worklenz-frontend/src/api/clients/clients.api.service.ts @@ -0,0 +1,49 @@ +import { IClient, IClientsViewModel } from '@/types/client.types'; +import apiClient from '@api/api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { IServerResponse } from '@/types/common.types'; +import { toQueryString } from '@/utils/toQueryString'; + +const rootUrl = `${API_BASE_URL}/clients`; + +export const clientsApiService = { + // Get all clients + async getClients( + index: number, + size: number, + field: string | null, + order: string | null, + search?: string | null + ): Promise> { + const s = encodeURIComponent(search || ''); + const queryString = toQueryString({ index, size, field, order, search: s }); + const response = await apiClient.get>( + `${rootUrl}${queryString}` + ); + return response.data; + }, + + // Get single client by ID + async getClientById(id: string): Promise> { + const response = await apiClient.get>(`${rootUrl}/${id}`); + return response.data; + }, + + // Create new client + async createClient(body: IClient): Promise> { + const response = await apiClient.post>(rootUrl, body); + return response.data; + }, + + // Update existing client + async updateClient(id: string, body: IClient): Promise> { + const response = await apiClient.put>(`${rootUrl}/${id}`, body); + return response.data; + }, + + // Delete client + async deleteClient(id: string): Promise> { + const response = await apiClient.delete>(`${rootUrl}/${id}`); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/home-page/home-page.api.service.ts b/worklenz-frontend/src/api/home-page/home-page.api.service.ts new file mode 100644 index 00000000..de83bd70 --- /dev/null +++ b/worklenz-frontend/src/api/home-page/home-page.api.service.ts @@ -0,0 +1,72 @@ +import { BaseQueryFn, createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; +import { API_BASE_URL } from '@/shared/constants'; +import { IServerResponse } from '@/types/common.types'; +import { toQueryString } from '@/utils/toQueryString'; +import { IHomeTasksModel, IHomeTasksConfig } from '@/types/home/home-page.types'; +import { IMyTask } from '@/types/home/my-tasks.types'; +import { IProject } from '@/types/project/project.types'; +import { getCsrfToken } from '../api-client'; + +const rootUrl = '/home'; + +const api = createApi({ + reducerPath: 'homePageApi', + baseQuery: fetchBaseQuery({ + baseUrl: `${import.meta.env.VITE_API_URL}${API_BASE_URL}`, + prepareHeaders: headers => { + headers.set('X-CSRF-Token', getCsrfToken() || ''); + headers.set('Content-Type', 'application/json'); + }, + credentials: 'include', + }), + tagTypes: ['personalTasks', 'projects', 'teamProjects'], + endpoints: builder => ({ + getPersonalTasks: builder.query, void>({ + query: () => `${rootUrl}/personal-tasks`, + }), + createPersonalTask: builder.mutation, IMyTask>({ + query: body => ({ + url: `${rootUrl}/personal-task`, + method: 'POST', + body, + }), + }), + markPersonalTaskAsDone: builder.mutation, string>({ + query: taskId => ({ + url: `${rootUrl}/update-personal-task`, + method: 'PUT', + body: { id: taskId }, + }), + }), + getMyTasks: builder.query, IHomeTasksConfig>({ + query: config => { + const { tasks_group_by, current_tab, is_calendar_view, selected_date, time_zone } = config; + const url = `${rootUrl}/tasks${toQueryString({ + group_by: tasks_group_by, + current_tab, + is_calendar_view, + selected_date: selected_date?.toISOString().split('T')[0], + time_zone, + })}`; + return url; + }, + }), + getProjects: builder.query, { view: number }>({ + query: ({ view }) => `${rootUrl}/projects?view=${view}`, + }), + getProjectsByTeam: builder.query, void>({ + query: () => `${rootUrl}/team-projects`, + }), + }), +}); + +export const { + useCreatePersonalTaskMutation, + useGetMyTasksQuery, + useGetPersonalTasksQuery, + useGetProjectsQuery, + useGetProjectsByTeamQuery, + useMarkPersonalTaskAsDoneMutation, +} = api; + +export default api; diff --git a/worklenz-frontend/src/api/notifications/notifications.api.service.ts b/worklenz-frontend/src/api/notifications/notifications.api.service.ts new file mode 100644 index 00000000..e7b12a75 --- /dev/null +++ b/worklenz-frontend/src/api/notifications/notifications.api.service.ts @@ -0,0 +1,34 @@ +import apiClient from '@api/api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { IServerResponse } from '@/types/common.types'; +import { toQueryString } from '@/utils/toQueryString'; + +import { IMyTask } from '@/types/home/my-tasks.types'; +import { IWorklenzNotification } from '@/types/notifications/notifications.types'; + +const rootUrl = `${API_BASE_URL}/notifications`; + +export const notificationsApiService = { + getNotifications: async (filter: string): Promise> => { + const q = toQueryString({ filter }); + const response = await apiClient.get>( + `${rootUrl}${q}` + ); + return response.data; + }, + + updateNotification: async (id: string): Promise> => { + const response = await apiClient.put>(`${rootUrl}/${id}`); + return response.data; + }, + + readAllNotifications: async (): Promise> => { + const response = await apiClient.put>(`${rootUrl}/read-all`); + return response.data; + }, + + getUnreadCount: async (): Promise> => { + const response = await apiClient.get>(`${rootUrl}/unread-count`); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/project-members/project-members.api.service.ts b/worklenz-frontend/src/api/project-members/project-members.api.service.ts new file mode 100644 index 00000000..3eeac3da --- /dev/null +++ b/worklenz-frontend/src/api/project-members/project-members.api.service.ts @@ -0,0 +1,52 @@ +import { IProjectMemberViewModel } from '@/types/projectMember.types'; +import apiClient from '../api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { IServerResponse } from '@/types/common.types'; +import { toQueryString } from '@/utils/toQueryString'; + +const rootUrl = `${API_BASE_URL}/project-members`; + +export const projectMembersApiService = { + createProjectMember: async ( + body: IProjectMemberViewModel + ): Promise> => { + const q = toQueryString({current_project_id: body.project_id}); + + const response = await apiClient.post>( + `${rootUrl}${q}`, + body + ); + return response.data; + }, + + createByEmail: async (body: { + project_id: string; + email: string; + }): Promise> => { + const response = await apiClient.post>( + `${rootUrl}/invite`, + body + ); + return response.data; + }, + + getByProjectId: async ( + projectId: string + ): Promise> => { + const response = await apiClient.get>( + `${rootUrl}/${projectId}` + ); + return response.data; + }, + + deleteProjectMember: async ( + id: string, + currentProjectId: string + ): Promise> => { + const q = toQueryString({ current_project_id: currentProjectId }); + const response = await apiClient.delete>( + `${rootUrl}/${id}${q}` + ); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/project-templates/project-templates.api.service.ts b/worklenz-frontend/src/api/project-templates/project-templates.api.service.ts new file mode 100644 index 00000000..9dcc8ba8 --- /dev/null +++ b/worklenz-frontend/src/api/project-templates/project-templates.api.service.ts @@ -0,0 +1,58 @@ +import { API_BASE_URL } from '@/shared/constants'; +import { IServerResponse } from '@/types/common.types'; +import { + IAccountSetupRequest, + IAccountSetupResponse, + IProjectTemplate, + IWorklenzTemplate, +} from '@/types/project-templates/project-templates.types'; +import apiClient from '../api-client'; +import { ICustomProjectTemplateCreateRequest } from '@/types/project/projectTemplate.types'; + +const rootUrl = `${API_BASE_URL}/project-templates`; + +export const projectTemplatesApiService = { + getWorklenzTemplates: async (): Promise> => { + const response = await apiClient.get(`${rootUrl}/worklenz-templates`); + return response.data; + }, + + getByTemplateId: async (templateId: string): Promise> => { + const response = await apiClient.get(`${rootUrl}/worklenz-templates/${templateId}`); + return response.data; + }, + + getCustomTemplates: async (): Promise> => { + const response = await apiClient.get(`${rootUrl}/custom-templates`); + return response.data; + }, + + setupAccount: async ( + model: IAccountSetupRequest + ): Promise> => { + const response = await apiClient.post(`${rootUrl}/setup`, model); + return response.data; + }, + + createCustomTemplate: async (body: { template_id: string }): Promise> => { + const response = await apiClient.post(`${rootUrl}/custom-template`, body); + return response.data; + }, + + deleteCustomTemplate: async (id: string): Promise> => { + const response = await apiClient.delete(`${rootUrl}/${id}`); + return response.data; + }, + + createFromWorklenzTemplate: async (body: { template_id: string }): Promise> => { + const response = await apiClient.post(`${rootUrl}/import-template`, body); + return response.data; + }, + + createFromCustomTemplate: async (body: { template_id: string }): Promise> => { + const response = await apiClient.post(`${rootUrl}/import-custom-template`, body); + return response.data; + }, + +}; + diff --git a/worklenz-frontend/src/api/projects/comments/project-comments.api.service.ts b/worklenz-frontend/src/api/projects/comments/project-comments.api.service.ts new file mode 100644 index 00000000..f5a81fe4 --- /dev/null +++ b/worklenz-frontend/src/api/projects/comments/project-comments.api.service.ts @@ -0,0 +1,63 @@ +import { IServerResponse } from '@/types/common.types'; +import apiClient from '@/api/api-client'; +import { API_BASE_URL } from '@/shared/constants'; + +import { + IMentionMemberSelectOption, + IMentionMemberViewModel, + IProjectCommentsCreateRequest, +} from '@/types/project/projectComments.types'; +import { toQueryString } from '@/utils/toQueryString'; +import { IProjectUpdateCommentViewModel } from '@/types/project/project.types'; + +const rootUrl = `${API_BASE_URL}/project-comments`; + +export const projectCommentsApiService = { + createProjectComment: async ( + body: IProjectCommentsCreateRequest + ): Promise> => { + const url = `${rootUrl}`; + const response = await apiClient.post>( + `${url}`, + body + ); + return response.data; + }, + + getMentionMembers: async ( + projectId: string, + index: number, + size: number, + field: string | null, + order: string | null, + search: string | null + ): Promise> => { + const s = encodeURIComponent(search || ''); + const url = `${rootUrl}/project-members/${projectId}${toQueryString({ index, size, field, order, search: s })}`; + const response = await apiClient.get>(`${url}`); + return response.data; + }, + + getCountByProjectId: async (projectId: string): Promise> => { + const url = `${rootUrl}/${projectId}/comments/count`; + const response = await apiClient.get>(`${url}`); + return response.data; + }, + + getByProjectId: async ( + projectId: string, + isLimit: boolean = false + ): Promise> => { + const url = `${rootUrl}/project-comments/${projectId}${toQueryString({ latest: isLimit })}`; + const response = await apiClient.get>( + `${url}` + ); + return response.data; + }, + + deleteComment: async (commentId: string): Promise> => { + const url = `${rootUrl}/delete/${commentId}`; + const response = await apiClient.delete>(`${url}`); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/projects/insights/project-insights.api.service.ts b/worklenz-frontend/src/api/projects/insights/project-insights.api.service.ts new file mode 100644 index 00000000..58d45b24 --- /dev/null +++ b/worklenz-frontend/src/api/projects/insights/project-insights.api.service.ts @@ -0,0 +1,125 @@ +import { IServerResponse } from '@/types/common.types'; +import apiClient from '@/api/api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { toQueryString } from '@/utils/toQueryString'; +import { IProjectViewModel } from '@/types/project/projectViewModel.types'; +import { IDeadlineTaskStats } from '@/types/project/projectInsights.types'; +import { + IInsightTasks, + IProjectInsightsGetRequest, + IProjectLogs, + IProjectMemberStats, +} from '@/types/project/projectInsights.types'; +import { ITaskStatusCounts } from '@/types/project/project-insights.types'; + +const rootUrl = `${API_BASE_URL}/project-insights`; + +export const projectInsightsApiService = { + getProjectInsights: async (id: string): Promise> => { + const url = `${rootUrl}/${id}/insights`; + const response = await apiClient.get>(`${url}`); + return response.data; + }, + + getProjectOverviewData: async ( + id: string, + include_archived: boolean + ): Promise> => { + const url = `${rootUrl}/${id}?archived=${include_archived}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getLastUpdatedTasks: async ( + id: string, + include_archived: boolean + ): Promise> => { + const url = `${rootUrl}/last-updated/${id}?archived=${include_archived}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getProjectLogs: async (id: string): Promise> => { + const url = `${rootUrl}/logs/${id}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getTaskStatusCounts: async ( + id: string, + include_archived: boolean + ): Promise> => { + const url = `${rootUrl}/status-overview/${id}?archived=${include_archived}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getPriorityOverview: async ( + id: string, + include_archived: boolean + ): Promise> => { + const url = `${rootUrl}/priority-overview/${id}?archived=${include_archived}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getOverdueTasks: async ( + id: string, + include_archived: boolean + ): Promise> => { + const url = `${rootUrl}/overdue-tasks/${id}?archived=${include_archived}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getTasksCompletedEarly: async ( + id: string, + include_archived: boolean + ): Promise> => { + const url = `${rootUrl}/early-tasks/${id}?archived=${include_archived}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getTasksCompletedLate: async ( + id: string, + include_archived: boolean + ): Promise> => { + const url = `${rootUrl}/late-tasks/${id}?archived=${include_archived}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getMemberInsightAStats: async ( + id: string, + include_archived: boolean + ): Promise> => { + const url = `${rootUrl}/members/stats/${id}?archived=${include_archived}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getMemberTasks: async (body: any): Promise> => { + const url = `${rootUrl}/members/tasks`; + const response = await apiClient.post>(url, body); + return response.data; + }, + + getProjectDeadlineStats: async ( + id: string, + include_archived: boolean + ): Promise> => { + const url = `${rootUrl}/deadline/${id}?archived=${include_archived}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getOverloggedTasks: async ( + id: string, + include_archived: boolean + ): Promise> => { + const url = `${rootUrl}/overlogged-tasks/${id}?archived=${include_archived}`; + const response = await apiClient.get>(url); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/projects/lookups/projectHealth.api.service.ts b/worklenz-frontend/src/api/projects/lookups/projectHealth.api.service.ts new file mode 100644 index 00000000..f29040b1 --- /dev/null +++ b/worklenz-frontend/src/api/projects/lookups/projectHealth.api.service.ts @@ -0,0 +1,13 @@ +import { IServerResponse } from '@/types/common.types'; +import { API_BASE_URL } from '@/shared/constants'; +import { IProjectHealth } from '@/types/project/projectHealth.types'; +import apiClient from '@api/api-client'; + +const rootUrl = `${API_BASE_URL}/project-healths`; + +export const projectHealthApiService = { + getHealthOptions: async (): Promise> => { + const response = await apiClient.get>(rootUrl); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/projects/lookups/projectStatus.api.service.ts b/worklenz-frontend/src/api/projects/lookups/projectStatus.api.service.ts new file mode 100644 index 00000000..c35bd7f1 --- /dev/null +++ b/worklenz-frontend/src/api/projects/lookups/projectStatus.api.service.ts @@ -0,0 +1,13 @@ +import { IServerResponse } from '@/types/common.types'; +import apiClient from '../../api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { IProjectStatus } from '@/types/project/projectStatus.types'; + +const rootUrl = `${API_BASE_URL}/project-statuses`; + +export const projectStatusesApiService = { + getStatuses: async (): Promise> => { + const response = await apiClient.get>(rootUrl); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/projects/projects.api.service.ts b/worklenz-frontend/src/api/projects/projects.api.service.ts new file mode 100644 index 00000000..a817e76e --- /dev/null +++ b/worklenz-frontend/src/api/projects/projects.api.service.ts @@ -0,0 +1,121 @@ +import { IServerResponse } from '@/types/common.types'; +import apiClient from '../api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { IProjectOverviewStats, IProjectsViewModel } from '@/types/project/projectsViewModel.types'; +import { toQueryString } from '@/utils/toQueryString'; +import { IProjectViewModel } from '@/types/project/projectViewModel.types'; +import { ITeamMemberOverviewGetResponse } from '@/types/project/project-insights.types'; +import { IProjectMembersViewModel } from '@/types/projectMember.types'; +import { IProjectManager } from '@/types/project/projectManager.types'; + +const rootUrl = `${API_BASE_URL}/projects`; + +export const projectsApiService = { + getProjects: async ( + index: number, + size: number, + field: string | null, + order: string | null, + search: string | null, + filter: number | null = null, + statuses: string | null = null, + categories: string | null = null + ): Promise> => { + const s = encodeURIComponent(search || ''); + const url = `${rootUrl}${toQueryString({ index, size, field, order, search: s, filter, statuses, categories })}`; + const response = await apiClient.get>(`${url}`); + return response.data; + }, + + getProject: async (id: string): Promise> => { + const url = `${rootUrl}/${id}`; + const response = await apiClient.get>(`${url}`); + return response.data; + }, + + toggleFavoriteProject: async (id: string): Promise> => { + const url = `${rootUrl}/favorite/${id}`; + const response = await apiClient.get>(`${url}`); + return response.data; + }, + + getOverViewById: async (id: string): Promise> => { + const url = `${rootUrl}/overview/${id}`; + const response = await apiClient.get>(`${url}`); + return response.data; + }, + + getOverViewMembersById: async ( + id: string, + archived = false + ): Promise> => { + const url = `${rootUrl}/overview-members/${id}?archived=${archived}`; + const response = await apiClient.get>( + `${url}` + ); + return response.data; + }, + + getMembers: async ( + id: string, + index: number, + size: number, + field: string | null, + order: string | null, + search: string | null + ): Promise> => { + const s = encodeURIComponent(search || ''); + const url = `${rootUrl}/members/${id}${toQueryString({ index, size, field, order, search: s })}`; + const response = await apiClient.get>(`${url}`); + return response.data; + }, + + createProject: async ( + project: IProjectViewModel + ): Promise> => { + const url = `${rootUrl}`; + const response = await apiClient.post>(`${url}`, project); + return response.data; + }, + + updateProject: async ( + id: string, + project: IProjectViewModel + ): Promise> => { + const q = toQueryString({ current_project_id: id }); + const url = `${rootUrl}/${id}${q}`; + const response = await apiClient.put>(`${url}`, project); + return response.data; + }, + + deleteProject: async (id: string): Promise> => { + const url = `${rootUrl}/${id}`; + const response = await apiClient.delete>(`${url}`); + return response.data; + }, + + toggleArchiveProject: async (id: string): Promise> => { + const url = `${rootUrl}/archive/${id}`; + const response = await apiClient.get>(`${url}`); + return response.data; + }, + + toggleArchiveProjectForAll: async (id: string): Promise> => { + const url = `${rootUrl}/archive-all/${id}`; + const response = await apiClient.get>(`${url}`); + return response.data; + }, + + updateDefaultTab: async (body: { project_id: string; default_view: string }): Promise> => { + const url = `${rootUrl}/update-pinned-view`; + const response = await apiClient.put>(`${url}`, body); + return response.data; + }, + + getProjectManagers: async (): Promise> => { + const url = `${API_BASE_URL}/project-managers`; + const response = await apiClient.get>(`${url}`); + return response.data; + }, +}; + diff --git a/worklenz-frontend/src/api/projects/projects.v1.api.service.ts b/worklenz-frontend/src/api/projects/projects.v1.api.service.ts new file mode 100644 index 00000000..8e6be422 --- /dev/null +++ b/worklenz-frontend/src/api/projects/projects.v1.api.service.ts @@ -0,0 +1,153 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; +import { API_BASE_URL } from '@/shared/constants'; +import { IProjectViewModel } from '@/types/project/projectViewModel.types'; +import { IProjectCategory } from '@/types/project/projectCategory.types'; +import { IProjectsViewModel } from '@/types/project/projectsViewModel.types'; +import { IServerResponse } from '@/types/common.types'; +import { IProjectMembersViewModel } from '@/types/projectMember.types'; +import { getCsrfToken } from '../api-client'; + +const rootUrl = '/projects'; + +export const projectsApi = createApi({ + reducerPath: 'projectsApi', + baseQuery: fetchBaseQuery({ + baseUrl: `${import.meta.env.VITE_API_URL}${API_BASE_URL}`, + prepareHeaders: headers => { + headers.set('X-CSRF-Token', getCsrfToken() || ''); + headers.set('Content-Type', 'application/json'); + }, + credentials: 'include', + }), + tagTypes: ['Projects', 'ProjectCategories', 'ProjectMembers'], + endpoints: builder => ({ + getProjects: builder.query< + IServerResponse, + { + index: number; + size: number; + field: string | null; + order: string | null; + search: string | null; + filter: number | null; + statuses: string | null; + categories: string | null; + } + >({ + query: ({ index, size, field, order, search, filter, statuses, categories }) => { + const params = new URLSearchParams({ + index: index.toString(), + size: size.toString(), + field: field || '', + order: order || '', + search: search || '', + filter: filter?.toString() || '', + statuses: statuses || '', + categories: categories || '', + }); + return `${rootUrl}?${params.toString()}`; + }, + providesTags: result => [{ type: 'Projects', id: 'LIST' }], + }), + + getProject: builder.query, string>({ + query: id => `${rootUrl}/${id}`, + providesTags: (result, error, id) => [{ type: 'Projects', id }], + }), + + createProject: builder.mutation, IProjectViewModel>({ + query: project => ({ + url: rootUrl, + method: 'POST', + body: project, + }), + invalidatesTags: [{ type: 'Projects', id: 'LIST' }], + }), + + updateProject: builder.mutation< + IServerResponse, + { id: string; project: IProjectViewModel } + >({ + query: ({ id, project }) => ({ + url: `${rootUrl}/${id}`, + method: 'PUT', + body: project, + }), + invalidatesTags: (result, error, { id }) => [{ type: 'Projects', id }], + }), + + deleteProject: builder.mutation, string>({ + query: id => ({ + url: `${rootUrl}/${id}`, + method: 'DELETE', + }), + invalidatesTags: [{ type: 'Projects', id: 'LIST' }], + }), + + toggleFavoriteProject: builder.mutation, string>({ + query: id => ({ + url: `${rootUrl}/favorite/${id}`, + method: 'GET', + }), + invalidatesTags: (result, error, id) => [{ type: 'Projects', id }], + }), + + toggleArchiveProject: builder.mutation, string>({ + query: id => ({ + url: `${rootUrl}/archive/${id}`, + method: 'GET', + }), + invalidatesTags: [{ type: 'Projects', id: 'LIST' }], + }), + + toggleArchiveProjectForAll: builder.mutation, string>({ + query: id => ({ + url: `${rootUrl}/archive-all/${id}`, + method: 'GET', + }), + invalidatesTags: [{ type: 'Projects', id: 'LIST' }], + }), + + getProjectCategories: builder.query({ + query: () => `${rootUrl}/categories`, + providesTags: ['ProjectCategories'], + }), + + getProjectMembers: builder.query< + IServerResponse, + { + id: string; + index: number; + size: number; + field: string | null; + order: string | null; + search: string | null; + } + >({ + query: ({ id, index, size, field, order, search }) => { + const params = new URLSearchParams({ + index: index.toString(), + size: size.toString(), + field: field || '', + order: order || '', + search: search || '', + }); + return `${rootUrl}/members/${id}?${params.toString()}`; + }, + providesTags: (result, error, { id }) => [{ type: 'ProjectMembers', id }], + }), + }), +}); + +export const { + useGetProjectsQuery, + useGetProjectQuery, + useCreateProjectMutation, + useUpdateProjectMutation, + useDeleteProjectMutation, + useToggleFavoriteProjectMutation, + useToggleArchiveProjectMutation, + useToggleArchiveProjectForAllMutation, + useGetProjectCategoriesQuery, + useGetProjectMembersQuery, +} = projectsApi; diff --git a/worklenz-frontend/src/api/reporting/reporting-export.api.service.ts b/worklenz-frontend/src/api/reporting/reporting-export.api.service.ts new file mode 100644 index 00000000..6b18fe94 --- /dev/null +++ b/worklenz-frontend/src/api/reporting/reporting-export.api.service.ts @@ -0,0 +1,175 @@ +import { toQueryString } from '@/utils/toQueryString'; +import { API_BASE_URL } from '@/shared/constants'; +import { ITimeLogBreakdownReq } from '@/types/reporting/reporting.types'; + +const rootUrl = `${import.meta.env.VITE_API_URL}${API_BASE_URL}/reporting-export`; + +export const reportingExportApiService = { + exportOverviewProjectsByTeam(teamId: string, teamName: string) { + const params = toQueryString({ + team_id: teamId, + team_name: teamName, + }); + window.location.href = `${rootUrl}/overview/projects${params}`; + }, + + exportOverviewMembersByTeam(teamId: string, teamName: string) { + const params = toQueryString({ + team_id: teamId, + team_name: teamName, + }); + window.location.href = `${rootUrl}/overview/members${params}`; + }, + + exportAllocation( + archived: boolean, + teams: string[], + projects: string[], + duration: string | undefined, + date_range: string[], + billable = true, + nonBillable = true + ) { + const teamsString = teams?.join(','); + const projectsString = projects.join(','); + window.location.href = `${rootUrl}/allocation/export${toQueryString({ + teams: teamsString, + projects: projectsString, + duration: duration, + date_range: date_range, + include_archived: archived, + billable, + nonBillable, + })}`; + }, + + exportProjects(teamName: string | undefined) { + const params = toQueryString({ + team_name: teamName, + }); + window.location.href = `${rootUrl}/projects/export${params}`; + }, + + exportMembers( + teamName: string | undefined, + duration: string | null | undefined, + date_range: string[] | null, + archived: boolean + ) { + const params = toQueryString({ + team_name: teamName, + duration: duration, + date_range: date_range, + archived: archived, + }); + window.location.href = `${rootUrl}/members/export${params}`; + }, + + exportProjectMembers( + projectId: string, + projectName: string, + teamName: string | null | undefined + ) { + const params = toQueryString({ + project_id: projectId, + project_name: projectName, + team_name: teamName ? teamName : null, + }); + window.location.href = `${rootUrl}/project-members/export${params}`; + }, + + exportProjectTasks(projectId: string, projectName: string, teamName: string | null | undefined) { + const params = toQueryString({ + project_id: projectId, + project_name: projectName, + team_name: teamName ? teamName : null, + }); + window.location.href = `${rootUrl}/project-tasks/export${params}`; + }, + + exportMemberProjects( + memberId: string, + teamId: string | null, + memberName: string, + teamName: string | null | undefined, + archived: boolean + ) { + const params = toQueryString({ + team_member_id: memberId, + team_id: teamId, + team_member_name: memberName, + team_name: teamName ? teamName : null, + archived: archived, + }); + window.location.href = `${rootUrl}/member-projects/export${params}`; + }, + + exportMemberTasks( + memberId: string, + memberName: string, + teamName: string | null | undefined, + body: any | null + ) { + const params = toQueryString({ + team_member_id: memberId, + team_member_name: memberName, + team_name: teamName ? teamName : null, + duration: body.duration, + date_range: body.date_range, + only_single_member: body.only_single_member ? body.only_single_member : false, + archived: body.archived ? body.archived : false, + }); + window.location.href = `${rootUrl}/member-tasks/export${params}`; + }, + + exportFlatTasks( + memberId: string, + memberName: string, + projectId: string | null, + projectName: string | null + ) { + const params = toQueryString({ + team_member_id: memberId, + team_member_name: memberName, + project_id: projectId, + project_name: projectName, + }); + window.location.href = `${rootUrl}/flat-tasks/export${params}`; + }, + + exportProjectTimeLogs(body: ITimeLogBreakdownReq, projectName: string) { + const params = toQueryString({ + id: body.id, + duration: body.duration, + date_range: body.date_range, + project_name: projectName, + }); + window.location.href = `${rootUrl}/projects-time-log-breakdown/export${params}`; + }, + + exportMemberTimeLogs(body: any | null) { + const params = toQueryString({ + team_member_id: body.team_member_id, + team_id: body.team_id, + duration: body.duration, + date_range: body.date_range, + member_name: body.member_name, + team_name: body.team_name, + archived: body.archived ? body.archived : false, + }); + window.location.href = `${rootUrl}/member-time-log-breakdown/export${params}`; + }, + + exportMemberActivityLogs(body: any | null) { + const params = toQueryString({ + team_member_id: body.team_member_id, + team_id: body.team_id, + duration: body.duration, + date_range: body.date_range, + member_name: body.member_name, + team_name: body.team_name, + archived: body.archived ? body.archived : false, + }); + window.location.href = `${rootUrl}/member-activity-log-breakdown/export${params}`; + }, +}; diff --git a/worklenz-frontend/src/api/reporting/reporting-members.api.service.ts b/worklenz-frontend/src/api/reporting/reporting-members.api.service.ts new file mode 100644 index 00000000..d8658465 --- /dev/null +++ b/worklenz-frontend/src/api/reporting/reporting-members.api.service.ts @@ -0,0 +1,18 @@ +import { IServerResponse } from '@/types/common.types'; +import { IGetProjectsRequestBody, IRPTMembersViewModel, IRPTOverviewProjectMember, IRPTProjectsViewModel } from '@/types/reporting/reporting.types'; +import apiClient from '../api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { toQueryString } from '@/utils/toQueryString'; + +const rootUrl = `${API_BASE_URL}/reporting/members`; + +export const reportingMembersApiService = { + getMembers: async ( + body: any + ): Promise> => { + const q = toQueryString(body); + const url = `${rootUrl}${q}`; + const response = await apiClient.get>(url); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/reporting/reporting-projects.api.service.ts b/worklenz-frontend/src/api/reporting/reporting-projects.api.service.ts new file mode 100644 index 00000000..8b5e51ec --- /dev/null +++ b/worklenz-frontend/src/api/reporting/reporting-projects.api.service.ts @@ -0,0 +1,43 @@ +import { IServerResponse } from '@/types/common.types'; +import { IGetProjectsRequestBody, IRPTOverviewProjectInfo, IRPTOverviewProjectMember, IRPTProjectsViewModel } from '@/types/reporting/reporting.types'; +import apiClient from '../api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { toQueryString } from '@/utils/toQueryString'; +import { ITaskListGroup } from '@/types/tasks/taskList.types'; + +const rootUrl = `${API_BASE_URL}/reporting/projects`; + +export const reportingProjectsApiService = { + getProjects: async ( + body: IGetProjectsRequestBody + ): Promise> => { + const q = toQueryString(body); + const url = `${rootUrl}${q}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getProjectOverview: async ( + projectId: string + ): Promise> => { + const url = `${API_BASE_URL}/reporting/overview/project/info/${projectId}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getProjectMembers: async ( + projectId: string + ): Promise> => { + const url = `${API_BASE_URL}/reporting/overview/project/members/${projectId}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getTasks: async (projectId: string, groupBy: string): Promise> => { + const q = toQueryString({group: groupBy}) + + const url = `${API_BASE_URL}/reporting/overview/project/tasks/${projectId}${q}`; + const response = await apiClient.get>(url); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/reporting/reporting.api.service.ts b/worklenz-frontend/src/api/reporting/reporting.api.service.ts new file mode 100644 index 00000000..393ff5b7 --- /dev/null +++ b/worklenz-frontend/src/api/reporting/reporting.api.service.ts @@ -0,0 +1,301 @@ +import { IServerResponse } from '@/types/common.types'; +import apiClient from '../api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { toQueryString } from '@/utils/toQueryString'; +import { IProjectViewModel } from '@/types/project/projectViewModel.types'; +import { + IProjectLogsBreakdown, + IRPTMember, + IRPTOverviewMemberInfo, + IRPTOverviewProjectInfo, + IRPTOverviewProjectMember, + IRPTOverviewStatistics, + IRPTOverviewTeamInfo, + IRPTProject, + IRPTProjectsViewModel, + IRPTReportingMemberTask, + IRPTTeam, + ITimeLogBreakdownReq, +} from '@/types/reporting/reporting.types'; +import { IReportingInfo } from '@/types/reporting/reporting.types'; +import { + IMemberProjectsResonse, + IMemberTaskStatGroupResonse, + IRPTMemberProject, + IRPTMemberResponse, + IRPTTimeMember, + IRPTTimeProject, + ISingleMemberActivityLogs, + ISingleMemberLogs, +} from '@/types/reporting/reporting.types'; +import { ITaskListGroup } from '@/types/tasks/taskList.types'; +import { + ISelectableCategory, + ISelectableProject, +} from '../../types/reporting/reporting-filters.types'; +import { IAllocationViewModel } from '@/types/reporting/reporting-allocation.types'; + +const rootUrl = `${API_BASE_URL}/reporting`; + +export const reportingApiService = { + getProject: async (id: string): Promise> => { + const url = `${rootUrl}/${id}`; + const response = await apiClient.get>(`${url}`); + return response.data; + }, + + getInfo: async (): Promise> => { + const url = `${rootUrl}/info`; + const response = await apiClient.get>(url); + return response.data; + }, + + getOverviewStatistics: async ( + includeArchived = false + ): Promise> => { + const q = toQueryString({ archived: includeArchived }); + const url = `${rootUrl}/overview/statistics${q}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getOverviewTeams: async (includeArchived = true): Promise> => { + const q = toQueryString({ archived: includeArchived }); + const url = `${rootUrl}/overview/teams${q}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getOverviewProjects: async ( + body: any | null = null + ): Promise> => { + const q = toQueryString(body); + const url = `${rootUrl}/overview/projects${q}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getOverviewProjectsByTeam: async ( + teamId: string, + teamMemberId?: string + ): Promise> => { + const q = toQueryString({ member: teamMemberId || null }); + const url = `${rootUrl}/overview/projects/${teamId}${q}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getOverviewMembersByTeam: async ( + teamId: string, + archived: boolean + ): Promise> => { + const q = toQueryString({ archived }); + const url = `${rootUrl}/overview/members/${teamId}${q}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getTeamInfo: async ( + teamId: string, + archived = false + ): Promise> => { + const q = toQueryString({ archived }); + const url = `${rootUrl}/overview/team/info/${teamId}${q}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getProjectInfo: async (projectId: string): Promise> => { + const url = `${rootUrl}/overview/project/info/${projectId}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getMemberInfo: async ( + body: any | null = null + ): Promise> => { + const q = toQueryString(body); + const url = `${rootUrl}/overview/member/info/${q}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getTeamMemberInfo: async ( + body: any | null = null + ): Promise> => { + const q = toQueryString(body); + const url = `${rootUrl}/overview/team-member/info/${q}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getProjectMembers: async ( + projectId: string + ): Promise> => { + const url = `${rootUrl}/overview/project/members/${projectId}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getTasks: async ( + projectId: string, + groupBy: string + ): Promise> => { + const q = toQueryString({ group: groupBy }); + const url = `${rootUrl}/overview/project/tasks/${projectId}${q}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getTasksByMember: async ( + teamMemberId: string, + projectId: string | null = null, + isMultiple: boolean, + teamId: string | null = null, + additionalBody: any | null = null + ): Promise> => { + const q = toQueryString({ + project: projectId || null, + is_multiple: isMultiple, + teamId, + only_single_member: additionalBody.only_single_member, + duration: additionalBody.duration, + date_range: additionalBody.date_range, + archived: additionalBody.archived, + }); + const url = `${rootUrl}/overview/member/tasks/${teamMemberId}${q}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getProjects: async (body: any | null = null): Promise> => { + const q = toQueryString(body); + const url = `${rootUrl}/projects${q}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getProjectTimeLogs: async ( + body: ITimeLogBreakdownReq + ): Promise> => { + const url = `${rootUrl}/project-timelogs`; + const response = await apiClient.post>(url, body); + return response.data; + }, + + getCategories: async ( + selectedTeams: string[] + ): Promise> => { + const url = `${rootUrl}/allocation/categories`; + const response = await apiClient.post>( + url, + selectedTeams + ); + return response.data; + }, + + getAllocationProjects: async ( + selectedTeams: string[], + categories: string[], + isNoCategory: boolean + ): Promise> => { + const body = { + selectedTeams: selectedTeams, + selectedCategories: categories, + noCategoryIncluded: isNoCategory, + }; + const url = `${rootUrl}/allocation/projects`; + const response = await apiClient.post>(url, body); + return response.data; + }, + + getAllocationData: async ( + body = {}, + archived = false + ): Promise> => { + const q = toQueryString({ archived }); + const url = `${rootUrl}/allocation${q}`; + const response = await apiClient.post>(url, body); + return response.data; + }, + + getMembers: async (body: any | null = null): Promise> => { + const q = toQueryString(body); + const url = `${rootUrl}/members${q}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getMemberProjects: async ( + body: any | null = null + ): Promise> => { + const q = toQueryString(body); + const url = `${rootUrl}/member-projects${q}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getProjectTimeSheets: async ( + body = {}, + archived = false + ): Promise> => { + const q = toQueryString({ archived }); + const url = `${rootUrl}/time-reports/projects${q}`; + const response = await apiClient.post>(url, body); + return response.data; + }, + + getProjectEstimatedVsActual: async ( + body = {}, + archived = false + ): Promise> => { + const q = toQueryString({ archived }); + const url = `${rootUrl}/time-reports/estimated-vs-actual${q}`; + const response = await apiClient.post>(url, body); + return response.data; + }, + + getMemberTimeSheets: async ( + body = {}, + archived = false + ): Promise> => { + const q = toQueryString({ archived }); + const url = `${rootUrl}/time-reports/members${q}`; + const response = await apiClient.post>(url, body); + return response.data; + }, + + getSingleMemberActivities: async ( + body: any | null = null + ): Promise> => { + const url = `${rootUrl}/members/single-member-activities`; + const response = await apiClient.post>(url, body); + return response.data; + }, + + getSingleMemberTimeLogs: async ( + body: any | null = null + ): Promise> => { + const url = `${rootUrl}/members/single-member-timelogs`; + const response = await apiClient.post>(url, body); + return response.data; + }, + + getMemberTasksStats: async ( + body: any | null = null + ): Promise> => { + const q = toQueryString(body); + const url = `${rootUrl}/members/single-member-task-stats${q}`; + const response = await apiClient.get>(url); + return response.data; + }, + + getSingleMemberProjects: async ( + body: any | null = null + ): Promise> => { + const q = toQueryString(body); + const url = `${rootUrl}/members/single-member-projects${q}`; + const response = await apiClient.get>(url); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/reporting/reporting.timesheet.api.service.ts b/worklenz-frontend/src/api/reporting/reporting.timesheet.api.service.ts new file mode 100644 index 00000000..1529d46b --- /dev/null +++ b/worklenz-frontend/src/api/reporting/reporting.timesheet.api.service.ts @@ -0,0 +1,44 @@ +import { API_BASE_URL } from '@/shared/constants'; +import { toQueryString } from '@/utils/toQueryString'; +import apiClient from '../api-client'; +import { IServerResponse } from '@/types/common.types'; +import { IAllocationViewModel } from '@/types/reporting/reporting-allocation.types'; +import { IProjectLogsBreakdown, IRPTTimeMember, IRPTTimeProject, ITimeLogBreakdownReq } from '@/types/reporting/reporting.types'; + +const rootUrl = `${API_BASE_URL}/reporting`; + +export const reportingTimesheetApiService = { + getTimeSheetData: async (body = {}, archived = false): Promise> => { + const q = toQueryString({ archived }); + const response = await apiClient.post(`${rootUrl}/allocation/${q}`, body); + return response.data; + }, + + getAllocationProjects: async (body = {}) => { + const response = await apiClient.post(`${rootUrl}/allocation/allocation-projects`, { body }); + return response.data; + }, + + getProjectTimeSheets: async (body = {}, archived = false): Promise> => { + const q = toQueryString({ archived }); + const response = await apiClient.post(`${rootUrl}/time-reports/projects/${q}`, body); + return response.data; + }, + + getMemberTimeSheets: async (body = {}, archived = false): Promise> => { + const q = toQueryString({ archived }); + const response = await apiClient.post(`${rootUrl}/time-reports/members/${q}`, body); + return response.data; + }, + + getProjectTimeLogs: async (body: ITimeLogBreakdownReq): Promise> => { + const response = await apiClient.post(`${rootUrl}/project-timelogs`, body); + return response.data; + }, + + getProjectEstimatedVsActual: async (body = {}, archived = false): Promise> => { + const q = toQueryString({ archived }); + const response = await apiClient.post(`${rootUrl}/time-reports/estimated-vs-actual${q}`, body); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/schedule/schedule.api.service.ts b/worklenz-frontend/src/api/schedule/schedule.api.service.ts new file mode 100644 index 00000000..1d54ab1a --- /dev/null +++ b/worklenz-frontend/src/api/schedule/schedule.api.service.ts @@ -0,0 +1,60 @@ +import { API_BASE_URL } from '@/shared/constants'; +import apiClient from '../api-client'; +import { IServerResponse } from '@/types/common.types'; +import { ITeamMemberViewModel } from '@/types/teamMembers/teamMembersGetResponse.types'; +import { DateList, Member, Project, ScheduleData, Settings } from '@/types/schedule/schedule-v2.types'; + +const rootUrl = `${API_BASE_URL}/schedule-gannt-v2`; + +export const scheduleAPIService = { + fetchScheduleSettings: async (): Promise> => { + const response = await apiClient.get>(`${rootUrl}/settings`); + return response.data; + }, + + updateScheduleSettings: async ({ + workingDays, + workingHours, + }: { + workingDays: string[]; + workingHours: number; + }): Promise> => { + const response = await apiClient.put>(`${rootUrl}/settings`, { + workingDays, + workingHours, + }); + return response.data; + }, + + fetchScheduleDates: async ({ + type, + date, + }: { + type: string; + date: string; + }): Promise> => { + const response = await apiClient.get>( + `${rootUrl}/dates/${date}/${type}` + ); + return response.data; + }, + + fetchScheduleMembers: async (): Promise> => { + const response = await apiClient.get>(`${rootUrl}/members`); + return response.data; + }, + + fetchMemberProjects: async ({ id }: { id: string }): Promise> => { + const response = await apiClient.get>(`${rootUrl}/members/projects/${id}`); + return response.data; + }, + + submitScheduleData: async ({ + schedule + }: { + schedule: ScheduleData + }): Promise> => { + const response = await apiClient.post>(`${rootUrl}/schedule`, schedule); + return response.data; + } +}; diff --git a/worklenz-frontend/src/api/settings/categories/categories.api.service.ts b/worklenz-frontend/src/api/settings/categories/categories.api.service.ts new file mode 100644 index 00000000..e0d09c83 --- /dev/null +++ b/worklenz-frontend/src/api/settings/categories/categories.api.service.ts @@ -0,0 +1,54 @@ +import { IServerResponse } from '@/types/common.types'; +import { API_BASE_URL } from '@/shared/constants'; +import { IProjectCategory, IProjectCategoryViewModel } from '@/types/project/projectCategory.types'; +import apiClient from '@api/api-client'; + +const rootUrl = `${API_BASE_URL}/project-categories`; + +export const categoriesApiService = { + getCategories: async (): Promise> => { + const response = await apiClient.get>(rootUrl); + return response.data; + }, + + getCategoriesByTeam: async ( + id: string + ): Promise> => { + const response = await apiClient.get>( + `${rootUrl}/${id}` + ); + return response.data; + }, + + getCategoriesByOrganization: async (): Promise> => { + const response = await apiClient.get>( + `${rootUrl}/org-categories` + ); + return response.data; + }, + + createCategory: async ( + category: Partial + ): Promise> => { + const response = await apiClient.post>( + rootUrl, + category + ); + return response.data; + }, + + updateCategory: async ( + category: IProjectCategoryViewModel + ): Promise> => { + const response = await apiClient.put>( + `${rootUrl}/${category.id}`, + category + ); + return response.data; + }, + + deleteCategory: async (id: string): Promise> => { + const response = await apiClient.delete>(`${rootUrl}/${id}`); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/settings/job-titles/job-titles.api.service.ts b/worklenz-frontend/src/api/settings/job-titles/job-titles.api.service.ts new file mode 100644 index 00000000..d3caeae5 --- /dev/null +++ b/worklenz-frontend/src/api/settings/job-titles/job-titles.api.service.ts @@ -0,0 +1,44 @@ +import apiClient from '@api/api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { IServerResponse } from '@/types/common.types'; +import { IJobTitle, IJobTitlesViewModel } from '@/types/job.types'; +import { toQueryString } from '@/utils/toQueryString'; + +const rootUrl = `${API_BASE_URL}/job-titles`; + +export const jobTitlesApiService = { + async getJobTitles( + index: number, + size: number, + field: string | null, + order: string | null, + search?: string | null + ): Promise> { + const s = encodeURIComponent(search || ''); + const queryString = toQueryString({ index, size, field, order, search: s }); + const response = await apiClient.get>( + `${rootUrl}${queryString}` + ); + return response.data; + }, + + async getJobTitleById(id: string): Promise> { + const response = await apiClient.get>(`${rootUrl}/${id}`); + return response.data; + }, + + async createJobTitle(body: IJobTitle): Promise> { + const response = await apiClient.post>(rootUrl, body); + return response.data; + }, + + async updateJobTitle(id: string, body: IJobTitle): Promise> { + const response = await apiClient.put>(`${rootUrl}/${id}`, body); + return response.data; + }, + + async deleteJobTitle(id: string): Promise> { + const response = await apiClient.delete>(`${rootUrl}/${id}`); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/settings/labels/labels.api.service.ts b/worklenz-frontend/src/api/settings/labels/labels.api.service.ts new file mode 100644 index 00000000..87d640e1 --- /dev/null +++ b/worklenz-frontend/src/api/settings/labels/labels.api.service.ts @@ -0,0 +1,39 @@ +import apiClient from '@api/api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { IServerResponse } from '@/types/common.types'; +import { ITaskLabel } from '@/types/label.type'; +import { toQueryString } from '@/utils/toQueryString'; + +const rootUrl = `${API_BASE_URL}/labels`; + +export const labelsApiService = { + getLabels: async (projectId: string | null = null): Promise> => { + const q = toQueryString({ project: projectId }); + const response = await apiClient.get>(`${rootUrl}${q}`); + return response.data; + }, + + getLabelsByTaskId: async (taskId: string): Promise> => { + const response = await apiClient.get>(`${rootUrl}/tasks/${taskId}`); + return response.data; + }, + + getByProjectId: async (projectId: string): Promise> => { + const response = await apiClient.get>( + `${rootUrl}/project/${projectId}` + ); + return response.data; + }, + + updateColor: async (id: string, color: string): Promise> => { + const response = await apiClient.put>(`${rootUrl}/tasks/${id}`, { + color, + }); + return response.data; + }, + + deleteLabel: async (id: string): Promise> => { + const response = await apiClient.delete>(`${rootUrl}/${id}`); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/settings/language-timezones/language-timezones-api.service.ts b/worklenz-frontend/src/api/settings/language-timezones/language-timezones-api.service.ts new file mode 100644 index 00000000..8975e827 --- /dev/null +++ b/worklenz-frontend/src/api/settings/language-timezones/language-timezones-api.service.ts @@ -0,0 +1,18 @@ +import { API_BASE_URL } from '@/shared/constants'; +import { IServerResponse } from '@/types/common.types'; +import { ITimezone } from '@/types/settings/timezone.types'; +import apiClient from '@api/api-client'; + +const rootUrl = `${API_BASE_URL}/timezones`; + +export const timezonesApiService = { + update: async (body: any): Promise> => { + const response = await apiClient.put>(rootUrl, body); + return response.data; + }, + + get: async (): Promise> => { + const response = await apiClient.get>(rootUrl); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/settings/profile/profile-settings.api.service.ts b/worklenz-frontend/src/api/settings/profile/profile-settings.api.service.ts new file mode 100644 index 00000000..9e269c59 --- /dev/null +++ b/worklenz-frontend/src/api/settings/profile/profile-settings.api.service.ts @@ -0,0 +1,71 @@ +import apiClient from '@api/api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { IServerResponse } from '@/types/common.types'; +import { IProfileSettings } from '@/types/settings/profile.types'; +import { INotificationSettings } from '@/types/settings/notifications.types'; +import { + IAccountSetupRequest, + IAccountSetupResponse, +} from '@/types/project-templates/project-templates.types'; +import { ITeam } from '@/types/teams/team.type'; + +const rootUrl = `${API_BASE_URL}/settings`; + +export const profileSettingsApiService = { + getProfile: async (): Promise> => { + const response = await apiClient.get>(`${rootUrl}/profile`); + return response.data; + }, + + updateProfile: async (body: IProfileSettings): Promise> => { + const response = await apiClient.put>( + `${rootUrl}/profile`, + body + ); + return response.data; + }, + + getNotificationSettings: async (): Promise> => { + const response = await apiClient.get>( + `${rootUrl}/notifications` + ); + return response.data; + }, + + updateNotificationSettings: async ( + body: INotificationSettings + ): Promise> => { + const response = await apiClient.put>( + `${rootUrl}/notifications`, + body + ); + return response.data; + }, + + setupAccount: async ( + body: IAccountSetupRequest + ): Promise> => { + const response = await apiClient.post>( + `${rootUrl}/setup`, + body + ); + return response.data; + }, + + updateTeamName: async (id: string, body: ITeam): Promise> => { + const response = await apiClient.put>(`${rootUrl}/team-name/${id}`, body); + return response.data; + }, + + changePassword: async (body: { + new_password: string; + confirm_password: string; + password: string; + }): Promise> => { + const response = await apiClient.post>( + `${API_BASE_URL}/change-password`, + body + ); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/task-templates/task-templates.api.service.ts b/worklenz-frontend/src/api/task-templates/task-templates.api.service.ts new file mode 100644 index 00000000..2be6b5ff --- /dev/null +++ b/worklenz-frontend/src/api/task-templates/task-templates.api.service.ts @@ -0,0 +1,44 @@ +import { API_BASE_URL } from '@/shared/constants'; +import apiClient from '../api-client'; +import { IServerResponse } from '@/types/common.types'; +import { + ITaskTemplateGetResponse, + ITaskTemplatesGetResponse, +} from '@/types/settings/task-templates.types'; +import { ITask } from '@/types/tasks/task.types'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; + +const rootUrl = `${API_BASE_URL}/task-templates`; + +export const taskTemplatesApiService = { + getTemplates: async (): Promise> => { + const url = `${rootUrl}`; + const response = await apiClient.get>(`${url}`); + return response.data; + }, + getTemplate: async (id: string): Promise> => { + const url = `${rootUrl}/${id}`; + const response = await apiClient.get>(`${url}`); + return response.data; + }, + createTemplate: async (body: { name: string, tasks: IProjectTask[] }): Promise> => { + const url = `${rootUrl}`; + const response = await apiClient.post>(`${url}`, body); + return response.data; + }, + updateTemplate: async (id: string, body: { name: string, tasks: IProjectTask[] }): Promise> => { + const url = `${rootUrl}/${id}`; + const response = await apiClient.put>(`${url}`, body); + return response.data; + }, + deleteTemplate: async (id: string): Promise> => { + const url = `${rootUrl}/${id}`; + const response = await apiClient.delete>(`${url}`); + return response.data; + }, + importTemplate: async (id: string, body: IProjectTask[]): Promise> => { + const url = `${rootUrl}/import/${id}`; + const response = await apiClient.post>(`${url}`, body); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/taskAttributes/labels/labels.api.service.ts b/worklenz-frontend/src/api/taskAttributes/labels/labels.api.service.ts new file mode 100644 index 00000000..c9e36ff4 --- /dev/null +++ b/worklenz-frontend/src/api/taskAttributes/labels/labels.api.service.ts @@ -0,0 +1,40 @@ +import { IServerResponse } from '@/types/common.types'; +import { API_BASE_URL } from '@/shared/constants'; +import apiClient from '@api/api-client'; +import { ITaskLabel } from '@/types/tasks/taskLabel.types'; + +const rootUrl = `${API_BASE_URL}/labels`; + +export const labelsApiService = { + getLabels: async (): Promise> => { + const response = await apiClient.get>(`${rootUrl}`); + return response.data; + }, + + getPriorityByTask: async (taskId: string): Promise> => { + const response = await apiClient.get>( + `${rootUrl}/tasks/${taskId}` + ); + return response.data; + }, + + getPriorityByProject: async (projectId: string): Promise> => { + const response = await apiClient.get>( + `${rootUrl}/project/${projectId}` + ); + return response.data; + }, + + updateColor: async (labelId: string, color: string): Promise> => { + const response = await apiClient.put>( + `${rootUrl}/tasks/${labelId}/color`, + { color } + ); + return response.data; + }, + + deleteById: async (labelId: string): Promise> => { + const response = await apiClient.delete>(`${rootUrl}/team/${labelId}`); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/taskAttributes/phases/phases.api.service.ts b/worklenz-frontend/src/api/taskAttributes/phases/phases.api.service.ts new file mode 100644 index 00000000..c12831ad --- /dev/null +++ b/worklenz-frontend/src/api/taskAttributes/phases/phases.api.service.ts @@ -0,0 +1,72 @@ +import apiClient from '@/api/api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { IServerResponse } from '@/types/common.types'; +import { ITaskPhase } from '@/types/tasks/taskPhase.types'; +import { toQueryString } from '@/utils/toQueryString'; + +const rootUrl = `${API_BASE_URL}/task-phases`; + +interface UpdateSortOrderBody { + from_index: number; + to_index: number; + phases: ITaskPhase[]; + project_id: string; +} + +export const phasesApiService = { + addPhaseOption: async (projectId: string) => { + const q = toQueryString({ id: projectId, current_project_id: projectId }); + const response = await apiClient.post>(`${rootUrl}${q}`); + return response.data; + }, + + getPhasesByProjectId: async (projectId: string) => { + const q = toQueryString({ id: projectId }); + const response = await apiClient.get>(`${rootUrl}${q}`); + return response.data; + }, + + deletePhaseOption: async (phaseOptionId: string, projectId: string) => { + const q = toQueryString({ id: projectId, current_project_id: projectId }); + const response = await apiClient.delete>( + `${rootUrl}/${phaseOptionId}${q}` + ); + return response.data; + }, + + updatePhaseColor: async (projectId: string, body: ITaskPhase) => { + const q = toQueryString({ id: projectId, current_project_id: projectId }); + const response = await apiClient.put>( + `${rootUrl}/change-color/${body.id}${q}`, + body + ); + return response.data; + }, + + updateNameOfPhase: async (phaseId: string, body: ITaskPhase, projectId: string,) => { + const q = toQueryString({ id: projectId, current_project_id: projectId }); + const response = await apiClient.put>( + `${rootUrl}/${phaseId}${q}`, + body + ); + return response.data; + }, + + updatePhaseOrder: async (projectId: string, body: UpdateSortOrderBody) => { + const q = toQueryString({ id: projectId, current_project_id: projectId }); + const response = await apiClient.put>( + `${rootUrl}/update-sort-order${q}`, + body + ); + return response.data; + }, + + updateProjectPhaseLabel: async (projectId: string, phaseLabel: string) => { + const q = toQueryString({ id: projectId, current_project_id: projectId }); + const response = await apiClient.put>( + `${rootUrl}/label/${projectId}${q}`, + { name: phaseLabel } + ); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/taskAttributes/priority/priority.api.service.ts b/worklenz-frontend/src/api/taskAttributes/priority/priority.api.service.ts new file mode 100644 index 00000000..c9d3af43 --- /dev/null +++ b/worklenz-frontend/src/api/taskAttributes/priority/priority.api.service.ts @@ -0,0 +1,22 @@ +import { IServerResponse } from '@/types/common.types'; +import { API_BASE_URL } from '@/shared/constants'; +import { ITaskPrioritiesGetResponse, ITaskPriority } from '@/types/tasks/taskPriority.types'; +import apiClient from '@api/api-client'; + +const rootUrl = `${API_BASE_URL}/task-priorities`; + +export const priorityApiService = { + getPriorities: async (): Promise> => { + const response = await apiClient.get>( + `${rootUrl}` + ); + return response.data; + }, + + getPriorityById: async (priorityId: string): Promise> => { + const response = await apiClient.get>( + `${rootUrl}/${priorityId}` + ); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/taskAttributes/status/status.api.service.ts b/worklenz-frontend/src/api/taskAttributes/status/status.api.service.ts new file mode 100644 index 00000000..282505e2 --- /dev/null +++ b/worklenz-frontend/src/api/taskAttributes/status/status.api.service.ts @@ -0,0 +1,77 @@ +import { IServerResponse } from '@/types/common.types'; +import apiClient from '@api/api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { ITaskStatus, ITaskStatusCategory } from '@/types/status.types'; +import { ITaskStatusCreateRequest } from '@/types/tasks/task-status-create-request'; +import { toQueryString } from '@/utils/toQueryString'; +import { ITaskStatusUpdateModel } from '@/types/tasks/task-status-update-model.types'; + +const rootUrl = `${API_BASE_URL}/statuses`; + +export const statusApiService = { + getStatuses: async (projectId: string): Promise> => { + const response = await apiClient.get>( + `${rootUrl}?project=${projectId}` + ); + return response.data; + }, + + getStatusCategories: async (): Promise> => { + const response = await apiClient.get>( + `${rootUrl}/categories` + ); + return response.data; + }, + + createStatus: async ( + body: ITaskStatusCreateRequest, + currentProjectId: string + ): Promise> => { + const q = toQueryString({ current_project_id: currentProjectId }); + const response = await apiClient.post>(`${rootUrl}${q}`, body); + return response.data; + }, + + updateStatus: async ( + statusId: string, + body: ITaskStatusUpdateModel, + currentProjectId: string + ): Promise> => { + const q = toQueryString({ current_project_id: currentProjectId }); + + const response = await apiClient.put>( + `${rootUrl}/${statusId}${q}`, + body + ); + return response.data; + }, + + updateNameOfStatus: async ( + id: string, + body: ITaskStatusUpdateModel, + currentProjectId: string + ): Promise> => { + const q = toQueryString({ current_project_id: currentProjectId }); + + const response = await apiClient.put>( + `${rootUrl}/name/${id}${q}`, + body + ); + return response.data; + }, + + updateStatusOrder: async ( + body: ITaskStatusCreateRequest, + currentProjectId: string + ): Promise> => { + const q = toQueryString({ current_project_id: currentProjectId }); + const response = await apiClient.put>(`${rootUrl}/order`, body); + return response.data; + }, + + deleteStatus: async (statusId: string, projectId: string, replacingStatusId: string): Promise> => { + const q = toQueryString({ project: projectId, current_project_id: projectId, replace: replacingStatusId || null }); + const response = await apiClient.delete>(`${rootUrl}/${statusId}${q}`); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/tasks/subtasks.api.service.ts b/worklenz-frontend/src/api/tasks/subtasks.api.service.ts new file mode 100644 index 00000000..db028a59 --- /dev/null +++ b/worklenz-frontend/src/api/tasks/subtasks.api.service.ts @@ -0,0 +1,16 @@ +import { API_BASE_URL } from "@/shared/constants"; +import apiClient from "../api-client"; +import { IServerResponse } from "@/types/common.types"; +import { ISubTask } from "@/types/tasks/subTask.types"; + +const root = `${API_BASE_URL}/sub-tasks`; + +export const subTasksApiService = { + getSubTasks: async (parentTaskId: string): Promise> => { + const response = await apiClient.get(`${root}/${parentTaskId}`); + return response.data; + }, + + +}; + diff --git a/worklenz-frontend/src/api/tasks/task-activity-logs.api.service.ts b/worklenz-frontend/src/api/tasks/task-activity-logs.api.service.ts new file mode 100644 index 00000000..7e470255 --- /dev/null +++ b/worklenz-frontend/src/api/tasks/task-activity-logs.api.service.ts @@ -0,0 +1,15 @@ +import { API_BASE_URL } from '@/shared/constants'; +import apiClient from '../api-client'; +import { IServerResponse } from '@/types/common.types'; +import { IActivityLogsResponse } from '@/types/tasks/task-activity-logs-get-request'; + +const rootUrl = `${API_BASE_URL}/activity-logs`; + +export const taskActivityLogsApiService = { + getActivityLogsByTaskId: async ( + taskId: string + ): Promise> => { + const response = await apiClient.get(`${rootUrl}/${taskId}`); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/tasks/task-attachments.api.service.ts b/worklenz-frontend/src/api/tasks/task-attachments.api.service.ts new file mode 100644 index 00000000..774771bf --- /dev/null +++ b/worklenz-frontend/src/api/tasks/task-attachments.api.service.ts @@ -0,0 +1,46 @@ +import { IServerResponse } from '@/types/common.types'; +import { IProjectAttachmentsViewModel, ITaskAttachment, ITaskAttachmentViewModel } from '@/types/tasks/task-attachment-view-model'; +import apiClient from '../api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { IAvatarAttachment } from '@/types/avatarAttachment.types'; +import { toQueryString } from '@/utils/toQueryString'; + +const rootUrl = `${API_BASE_URL}/attachments`; + +const taskAttachmentsApiService = { + + createTaskAttachment: async ( + body: ITaskAttachment + ): Promise> => { + const response = await apiClient.post(`${rootUrl}/tasks`, body); + return response.data; + }, + + createAvatarAttachment: async (body: IAvatarAttachment): Promise> => { + const response = await apiClient.post(`${rootUrl}/avatar`, body); + return response.data; + }, + + getTaskAttachments: async (taskId: string): Promise> => { + const response = await apiClient.get(`${rootUrl}/tasks/${taskId}`); + return response.data; + }, + + getProjectAttachments: async (projectId: string, index: number, size: number ): Promise> => { + const q = toQueryString({ index, size }); + const response = await apiClient.get(`${rootUrl}/project/${projectId}${q}`); + return response.data; + }, + + deleteTaskAttachment: async (attachmentId: string): Promise> => { + const response = await apiClient.delete(`${rootUrl}/tasks/${attachmentId}`); + return response.data; + }, + + downloadTaskAttachment: async (id: string, filename: string): Promise> => { + const response = await apiClient.get(`${rootUrl}/download?id=${id}&file=${filename}`); + return response.data; + }, +}; + +export default taskAttachmentsApiService; diff --git a/worklenz-frontend/src/api/tasks/task-comments.api.service.ts b/worklenz-frontend/src/api/tasks/task-comments.api.service.ts new file mode 100644 index 00000000..711fc50e --- /dev/null +++ b/worklenz-frontend/src/api/tasks/task-comments.api.service.ts @@ -0,0 +1,44 @@ +import apiClient from '@api/api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { IServerResponse } from '@/types/common.types'; +import { toQueryString } from '@/utils/toQueryString'; +import { ITaskComment, ITaskCommentsCreateRequest, ITaskCommentViewModel } from '@/types/tasks/task-comments.types'; + +const taskCommentsApiService = { + create: async (data: ITaskCommentsCreateRequest): Promise> => { + const response = await apiClient.post(`${API_BASE_URL}/task-comments`, data); + return response.data; + }, + + getByTaskId: async (id: string): Promise> => { + const response = await apiClient.get(`${API_BASE_URL}/task-comments/${id}`); + return response.data; + }, + + update: async (id: string, body: ITaskComment): Promise> => { + const response = await apiClient.put(`${API_BASE_URL}/task-comments/${id}`, body); + return response.data; + }, + + deleteAttachment: async (id: string, taskId: string): Promise> => { + const response = await apiClient.delete(`${API_BASE_URL}/task-comments/attachment/${id}/${taskId}`); + return response.data; + }, + + download: async (id: string, filename: string): Promise> => { + const response = await apiClient.get(`${API_BASE_URL}/task-comments/download?id=${id}&file=${filename}`); + return response.data; + }, + + delete: async (id: string, taskId: string): Promise> => { + const response = await apiClient.delete(`${API_BASE_URL}/task-comments/${id}/${taskId}`); + return response.data; + }, + + updateReaction: async (id: string, body: {reaction_type: string, task_id: string}): Promise> => { + const response = await apiClient.put(`${API_BASE_URL}/task-comments/reaction/${id}${toQueryString(body)}`); + return response.data; + }, +}; + +export default taskCommentsApiService; diff --git a/worklenz-frontend/src/api/tasks/task-dependencies.api.service.ts b/worklenz-frontend/src/api/tasks/task-dependencies.api.service.ts new file mode 100644 index 00000000..89faee1e --- /dev/null +++ b/worklenz-frontend/src/api/tasks/task-dependencies.api.service.ts @@ -0,0 +1,21 @@ +import { API_BASE_URL } from "@/shared/constants"; +import apiClient from "../api-client"; +import { ITaskDependency } from "@/types/tasks/task-dependency.types"; +import { IServerResponse } from "@/types/common.types"; + +const rootUrl = `${API_BASE_URL}/task-dependencies`; + +export const taskDependenciesApiService = { + getTaskDependencies: async (taskId: string): Promise> => { + const response = await apiClient.get(`${rootUrl}/${taskId}`); + return response.data; + }, + createTaskDependency: async (body: ITaskDependency): Promise> => { + const response = await apiClient.post(`${rootUrl}`, body); + return response.data; + }, + deleteTaskDependency: async (dependencyId: string): Promise> => { + const response = await apiClient.delete(`${rootUrl}/${dependencyId}`); + return response.data; + }, +}; \ No newline at end of file diff --git a/worklenz-frontend/src/api/tasks/task-list-bulk-actions.api.service.ts b/worklenz-frontend/src/api/tasks/task-list-bulk-actions.api.service.ts new file mode 100644 index 00000000..241d10bb --- /dev/null +++ b/worklenz-frontend/src/api/tasks/task-list-bulk-actions.api.service.ts @@ -0,0 +1,77 @@ +import apiClient from '@api/api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { IServerResponse } from '@/types/common.types'; +import { ITask } from '@/types/tasks/task.types'; +import { + IBulkAssignMembersRequest, + IBulkAssignRequest, + IBulkTasksArchiveRequest, + IBulkTasksDeleteRequest, + IBulkTasksLabelsRequest, + IBulkTasksPhaseChangeRequest, + IBulkTasksPriorityChangeRequest, + IBulkTasksStatusChangeRequest, +} from '@/types/tasks/bulk-action-bar.types'; +import { ITaskAssigneesUpdateResponse } from '@/types/tasks/task-assignee-update-response'; + +const rootUrl = `${API_BASE_URL}/tasks/bulk`; + +export const taskListBulkActionsApiService = { + changeStatus: async ( + body: IBulkTasksStatusChangeRequest, + projectId: string + ): Promise> => { + const response = await apiClient.put(`${rootUrl}/status?project=${projectId}`, body); + return response.data; + }, + changePriority: async ( + body: IBulkTasksPriorityChangeRequest, + projectId: string + ): Promise> => { + const response = await apiClient.put(`${rootUrl}/priority?project=${projectId}`, body); + return response.data; + }, + changePhase: async ( + body: IBulkTasksPhaseChangeRequest, + projectId: string + ): Promise> => { + const response = await apiClient.put(`${rootUrl}/phase?project=${projectId}`, body); + return response.data; + }, + deleteTasks: async ( + body: IBulkTasksDeleteRequest, + projectId: string + ): Promise> => { + const response = await apiClient.put(`${rootUrl}/delete?project=${projectId}`, body); + return response.data; + }, + archiveTasks: async ( + body: IBulkTasksArchiveRequest, + unarchive = false + ): Promise> => { + const response = await apiClient.put( + `${rootUrl}/archive?type=${unarchive ? 'unarchive' : 'archive'}&project=${body.project_id}`, + body + ); + return response.data; + }, + assignTasks: async ( + body: IBulkAssignMembersRequest + ): Promise> => { + const response = await apiClient.put(`${rootUrl}/members?project=${body.project_id}`, body); + return response.data; + }, + assignToMe: async ( + body: IBulkAssignRequest + ): Promise> => { + const response = await apiClient.put(`${rootUrl}/assign-me?project=${body.project_id}`, body); + return response.data; + }, + assignLabels: async ( + body: IBulkTasksLabelsRequest, + projectId: string + ): Promise> => { + const response = await apiClient.put(`${rootUrl}/label?project=${projectId}`, body); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/tasks/task-time-logs.api.service.ts b/worklenz-frontend/src/api/tasks/task-time-logs.api.service.ts new file mode 100644 index 00000000..f4565837 --- /dev/null +++ b/worklenz-frontend/src/api/tasks/task-time-logs.api.service.ts @@ -0,0 +1,32 @@ +import { API_BASE_URL } from "@/shared/constants"; +import apiClient from "../api-client"; +import { IServerResponse } from "@/types/common.types"; +import { ITaskLogViewModel } from "@/types/tasks/task-log-view.types"; + +const rootUrl = `${API_BASE_URL}/task-time-log`; + +export const taskTimeLogsApiService = { + getByTask: async (id: string) : Promise> => { + const response = await apiClient.get(`${rootUrl}/task/${id}`); + return response.data; + }, + + delete: async (id: string, taskId: string) : Promise> => { + const response = await apiClient.delete(`${rootUrl}/${id}?task=${taskId}`); + return response.data; + }, + + create: async (body: {}): Promise> => { + const response = await apiClient.post(`${rootUrl}`, body); + return response.data; + }, + + update: async (id: string, body: {}): Promise> => { + const response = await apiClient.put(`${rootUrl}/${id}`, body); + return response.data; + }, + + exportToExcel(taskId: string) { + window.location.href = `${import.meta.env.VITE_API_URL}${API_BASE_URL}/task-time-log/export/${taskId}`; + }, +}; diff --git a/worklenz-frontend/src/api/tasks/tasks-custom-columns.service.ts b/worklenz-frontend/src/api/tasks/tasks-custom-columns.service.ts new file mode 100644 index 00000000..187a27e2 --- /dev/null +++ b/worklenz-frontend/src/api/tasks/tasks-custom-columns.service.ts @@ -0,0 +1,69 @@ +import { ITaskListColumn } from "@/types/tasks/taskList.types"; +import apiClient from "../api-client"; +import { IServerResponse } from "@/types/common.types"; + +export const tasksCustomColumnsService = { + getCustomColumns: async (projectId: string): Promise> => { + const response = await apiClient.get(`/api/v1/custom-columns/project/${projectId}/columns`); + return response.data; + }, + + updateTaskCustomColumnValue: async ( + taskId: string, + columnKey: string, + value: string | number | boolean, + projectId: string + ): Promise> => { + const response = await apiClient.put(`/api/v1/tasks/${taskId}/custom-column`, { + column_key: columnKey, + value: value, + project_id: projectId + }); + return response.data; + }, + + createCustomColumn: async ( + projectId: string, + columnData: { + name: string; + key: string; + field_type: string; + width: number; + is_visible: boolean; + configuration: any; + } + ): Promise> => { + const response = await apiClient.post('/api/v1/custom-columns', { + project_id: projectId, + ...columnData + }); + return response.data; + }, + + updateCustomColumn: async ( + columnId: string, + columnData: { + name: string; + field_type: string; + width: number; + is_visible: boolean; + configuration: any; + } + ): Promise> => { + const response = await apiClient.put(`/api/v1/custom-columns/${columnId}`, columnData); + return response.data; + }, + + deleteCustomColumn: async (columnId: string): Promise> => { + const response = await apiClient.delete(`/api/v1/custom-columns/${columnId}`); + return response.data; + }, + + updateCustomColumnVisibility: async ( + projectId: string, + item: ITaskListColumn + ): Promise> => { + const response = await apiClient.put(`/api/v1/custom-columns/project/${projectId}/columns`, item); + return response.data; + } +}; diff --git a/worklenz-frontend/src/api/tasks/tasks.api.service.ts b/worklenz-frontend/src/api/tasks/tasks.api.service.ts new file mode 100644 index 00000000..cd3d80dd --- /dev/null +++ b/worklenz-frontend/src/api/tasks/tasks.api.service.ts @@ -0,0 +1,122 @@ +import { + ITaskListColumn, + ITaskListGroup, + ITaskListMemberFilter, +} from '@/types/tasks/taskList.types'; +import apiClient from '@api/api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { IServerResponse } from '@/types/common.types'; +import { toQueryString } from '@/utils/toQueryString'; +import { ITeamMemberViewModel } from '@/types/teamMembers/teamMembersGetResponse.types'; +import { ITaskFormViewModel, ITaskViewModel } from '@/types/tasks/task.types'; +import { InlineMember } from '@/types/teamMembers/inlineMember.types'; + +const rootUrl = `${API_BASE_URL}/tasks`; + +export interface ITaskListConfigV2 { + id: string; + field: string | null; + order: string | null; + search: string | null; + statuses: string | null; + members: string | null; + projects: string | null; + labels?: string | null; + priorities?: string | null; + archived?: boolean; + count?: boolean; + parent_task?: string; + group?: string; + isSubtasksInclude: boolean; +} + +export const tasksApiService = { + getTaskList: async (config: ITaskListConfigV2): Promise> => { + const q = toQueryString(config); + const response = await apiClient.get(`${rootUrl}/list/v2/${config.id}${q}`); + return response.data; + }, + + fetchTaskAssignees: async ( + projectId: string + ): Promise> => { + const response = await apiClient.get(`${rootUrl}/assignees/${projectId}`); + return response.data; + }, + + fetchTaskListColumns: async (projectId: string): Promise> => { + const response = await apiClient.get(`${rootUrl}/list/columns/${projectId}`); + return response.data; + }, + + getFormViewModel: async ( + taskId: string | null, + projectId: string | null + ): Promise> => { + const params = []; + if (taskId) params.push(`task_id=${taskId}`); + if (projectId) params.push(`project_id=${projectId}`); + const q = params.length ? `?${params.join('&')}` : ''; + const response = await apiClient.get(`${rootUrl}/info${q}`); + return response.data; + }, + + deleteTask: async (taskId: string): Promise> => { + const response = await apiClient.delete(`${rootUrl}/${taskId}`); + return response.data; + }, + + toggleColumnVisibility: async ( + projectId: string, + item: ITaskListColumn + ): Promise> => { + const response = await apiClient.put(`${rootUrl}/list/columns/${projectId}`, item); + return response.data; + }, + + getSubscribers: async (taskId: string): Promise> => { + const response = await apiClient.get(`${rootUrl}/subscribers/${taskId}`); + return response.data; + }, + + convertToSubtask: async ( + taskId: string, + projectId: string, + parentTaskId: string, + groupBy: string, + toGroupId: string + ): Promise> => { + const response = await apiClient.post(`${rootUrl}/convert-to-subtask`, { + id: taskId, + project_id: projectId, + parent_task_id: parentTaskId, + group_by: groupBy, + to_group_id: toGroupId, + }); + return response.data; + }, + + convertToTask: async (taskId: string, projectId: string): Promise> => { + const response = await apiClient.post(`${rootUrl}/convert`, { + id: taskId, + project_id: projectId, + }); + return response.data; + }, + + searchTask: async ( + taskId: string, + projectId: string, + searchQuery: string + ): Promise> => { + const q = toQueryString({ taskId, projectId, searchQuery }); + const response = await apiClient.get(`${rootUrl}/search${q}`); + return response.data; + }, + + getTaskDependencyStatus: async (taskId: string, statusId: string): Promise> => { + const q = toQueryString({taskId, statusId}); + const response = await apiClient.get(`${rootUrl}/dependency-status${q}`); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/team-members/teamMembers.api.service.ts b/worklenz-frontend/src/api/team-members/teamMembers.api.service.ts new file mode 100644 index 00000000..f96de294 --- /dev/null +++ b/worklenz-frontend/src/api/team-members/teamMembers.api.service.ts @@ -0,0 +1,99 @@ +import { IServerResponse } from '@/types/common.types'; +import apiClient from '../api-client'; +import { API_BASE_URL } from '@/shared/constants'; +import { ITeamMemberCreateRequest } from '@/types/teamMembers/team-member-create-request'; +import { ITeamMemberViewModel } from '@/types/teamMembers/teamMembersGetResponse.types'; +import { ITeamMembersViewModel } from '@/types/teamMembers/teamMembersViewModel.types'; +import { ITeamMember } from '@/types/teamMembers/teamMember.types'; + +const rootUrl = `${API_BASE_URL}/team-members`; + +export const teamMembersApiService = { + createTeamMember: async ( + body: ITeamMemberCreateRequest + ): Promise> => { + const response = await apiClient.post>(`${rootUrl}`, body); + return response.data; + }, + + get: async ( + index: number, + size: number, + field: string | null, + order: string | null, + search: string | null, + all = false + ): Promise> => { + const s = encodeURIComponent(search || ''); + const params = new URLSearchParams({ + index: index.toString(), + size: size.toString(), + ...(field && { field }), + ...(order && { order }), + ...(s && { search: s }), + ...(all && { all: all.toString() }), + }); + const response = await apiClient.get>( + `${rootUrl}?${params}` + ); + return response.data; + }, + + getById: async (id: string): Promise> => { + const response = await apiClient.get>(`${rootUrl}/${id}`); + return response.data; + }, + + getAll: async (projectId: string | null = null): Promise> => { + const params = new URLSearchParams(projectId ? { project: projectId } : {}); + const response = await apiClient.get>( + `${rootUrl}/all${params.toString() ? '?' + params.toString() : ''}` + ); + return response.data; + }, + + update: async (id: string, body: ITeamMemberCreateRequest): Promise> => { + const response = await apiClient.put>(`${rootUrl}/${id}`, body); + return response.data; + }, + + delete: async (id: string): Promise> => { + const response = await apiClient.delete>(`${rootUrl}/${id}`); + return response.data; + }, + + getTeamMembersByProjectId: async (projectId: string): Promise> => { + const response = await apiClient.get>(`${rootUrl}/project/${projectId}`); + return response.data; + }, + + resendInvitation: async (id: string): Promise> => { + const response = await apiClient.put>(`${rootUrl}/resend-invitation`, { + id, + }); + return response.data; + }, + + toggleMemberActiveStatus: async ( + id: string, + active: boolean, + email: string + ): Promise> => { + const params = new URLSearchParams({ + active: active.toString(), + email, + }); + const response = await apiClient.get>( + `${rootUrl}/deactivate/${id}?${params}` + ); + return response.data; + }, + + addTeamMember: async ( + id: string, + body: ITeamMemberCreateRequest + ): Promise> => { + const response = await apiClient.put>(`${rootUrl}/add-member/${id}`, body); + return response.data; + }, +}; diff --git a/worklenz-frontend/src/api/teams/teams.api.service.ts b/worklenz-frontend/src/api/teams/teams.api.service.ts new file mode 100644 index 00000000..4fb56b00 --- /dev/null +++ b/worklenz-frontend/src/api/teams/teams.api.service.ts @@ -0,0 +1,51 @@ +import { IServerResponse } from '@/types/common.types'; +import apiClient from '../api-client'; +import { + IAcceptTeamInvite, + ITeam, + ITeamActivateResponse, + ITeamGetResponse, + ITeamInvites, +} from '@/types/teams/team.type'; +import { API_BASE_URL } from '@/shared/constants'; +import { IOrganizationTeam } from '@/types/admin-center/admin-center.types'; + +const rootUrl = `${API_BASE_URL}/teams`; + +export const teamsApiService = { + getTeams: async (): Promise> => { + const response = await apiClient.get>( + `${rootUrl}` + ); + return response.data; + + }, + + setActiveTeam: async (teamId: string): Promise> => { + const response = await apiClient.put>( + `${rootUrl}/activate`, + { id: teamId } + ); + return response.data; + }, + + + createTeam: async (team: IOrganizationTeam): Promise> => { + const response = await apiClient.post>(`${rootUrl}`, team); + return response.data; + }, + + + getInvitations: async (): Promise> => { + const response = await apiClient.get>( + `${rootUrl}/invites` + ); + return response.data; + }, + + acceptInvitation: async (body: IAcceptTeamInvite): Promise> => { + const response = await apiClient.put>(`${rootUrl}`, body); + return response.data; + } +}; + diff --git a/worklenz-frontend/src/app/administrator/account-setup/account-setup-routing.module.ts b/worklenz-frontend/src/app/administrator/account-setup/account-setup-routing.module.ts deleted file mode 100644 index ffc02558..00000000 --- a/worklenz-frontend/src/app/administrator/account-setup/account-setup-routing.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {AccountSetupComponent} from "./account-setup/account-setup.component"; -import {TeamsListComponent} from "./teams-list/teams-list.component"; - -const routes: Routes = [ - {path: '', component: AccountSetupComponent}, - {path: 'teams', component: TeamsListComponent} -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class AccountSetupRoutingModule { -} diff --git a/worklenz-frontend/src/app/administrator/account-setup/account-setup.module.ts b/worklenz-frontend/src/app/administrator/account-setup/account-setup.module.ts deleted file mode 100644 index f4f89ced..00000000 --- a/worklenz-frontend/src/app/administrator/account-setup/account-setup.module.ts +++ /dev/null @@ -1,49 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule, NgOptimizedImage} from '@angular/common'; -import {AccountSetupRoutingModule} from './account-setup-routing.module'; -import {AccountSetupComponent} from './account-setup/account-setup.component'; -import {FormsModule, ReactiveFormsModule} from "@angular/forms"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NzSelectModule} from "ng-zorro-antd/select"; -import {NzStepsModule} from "ng-zorro-antd/steps"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzSpaceModule} from 'ng-zorro-antd/space'; -import {NzIconModule} from 'ng-zorro-antd/icon'; -import {NzTypographyModule} from 'ng-zorro-antd/typography'; -import {NzDividerModule} from 'ng-zorro-antd/divider'; -import {NzListModule} from "ng-zorro-antd/list"; -import {TeamsListComponent} from './teams-list/teams-list.component'; -import {NzRadioModule} from "ng-zorro-antd/radio"; -import {ProjectTemplateImportDrawerComponent} from "@admin/components/project-template-import-drawer/project-template-import-drawer.component"; - - -@NgModule({ - declarations: [ - AccountSetupComponent, - TeamsListComponent - ], - imports: [ - CommonModule, - FormsModule, - AccountSetupRoutingModule, - ReactiveFormsModule, - NzInputModule, - NzFormModule, - NzButtonModule, - NzSelectModule, - NzStepsModule, - NzSkeletonModule, - NzSpaceModule, - NzIconModule, - NzTypographyModule, - NzDividerModule, - NzListModule, - NgOptimizedImage, - NzRadioModule, - ProjectTemplateImportDrawerComponent - ] -}) -export class AccountSetupModule { -} diff --git a/worklenz-frontend/src/app/administrator/account-setup/account-setup/account-setup.component.html b/worklenz-frontend/src/app/administrator/account-setup/account-setup/account-setup.component.html deleted file mode 100644 index d5bd2e89..00000000 --- a/worklenz-frontend/src/app/administrator/account-setup/account-setup/account-setup.component.html +++ /dev/null @@ -1,161 +0,0 @@ -
-
-
-
- Worklenz -
-
- Setup your account. -
-
- - - - - - - - - - -
- - - - - -

Name your organization.

- - Pick a name for your Worklenz account. - - - - -
- - - - -

Create your first project.

- - What project are you working on right now? - - - - - -
-

or

-
-
- - - -
- - - -
-

Create your first task.

- - Type few tasks that you are going to do in "{{projectName}}". - - - - -
    - - - -
-
- - - -
-
- - - - -

- Invite your team to work with
- - - "{{workspaceName}}". - -

- - Invite with email - - - - - - -
    - - - -
-
-
- - - -
-
- -
- - -
- - - - -
- -
-
-
-
-
-
-
- -
- - - diff --git a/worklenz-frontend/src/app/administrator/account-setup/account-setup/account-setup.component.scss b/worklenz-frontend/src/app/administrator/account-setup/account-setup/account-setup.component.scss deleted file mode 100644 index 1e710930..00000000 --- a/worklenz-frontend/src/app/administrator/account-setup/account-setup/account-setup.component.scss +++ /dev/null @@ -1,116 +0,0 @@ -nz-steps { - transform: scale(0.83); - @media (max-width: 480px) { - transform: scale(0.6); - } -} - -.setup-wrapper { - width: 100%; - // max-width: 380px; - position: relative; - z-index: 1; -} - -.w-600 { - width: 600px; - @media(max-width: 860px) { - width: 90%; - } -} - -.bg-ant-grey { - background: #FAFAFA; - position: absolute; - bottom: 0; - top: 0; - left: 0; - right: 0; - z-index: 0; -} - -.w-steps { - width: 725px; - @media(max-width: 860px) { - width: 100%; - } -} - -.label-description { - font-size: 14px; - font-weight: 500; - color: #00000073; - margin-bottom: 16px; -} - -.setup-success { - -webkit-animation: scale-in-center 0.15s cubic-bezier(0.250, 0.460, 0.450, 0.940) both; - animation: scale-in-center 0.15s cubic-bezier(0.250, 0.460, 0.450, 0.940) both; -} - -.success-icon { - font-size: 36px; - margin-bottom: 16px; -} - -@-webkit-keyframes scale-in-center { - 0% { - -webkit-transform: scale(0); - transform: scale(0); - opacity: 1; - } - 100% { - -webkit-transform: scale(1); - transform: scale(1); - opacity: 1; - } -} - -@keyframes scale-in-center { - 0% { - -webkit-transform: scale(0); - transform: scale(0); - opacity: 1; - } - 100% { - -webkit-transform: scale(1); - transform: scale(1); - opacity: 1; - } -} - -h2.ant-typography, div.ant-typography-h2, div.ant-typography-h2 > textarea, .ant-typography h2 { - margin-bottom: 1em; -} - -.dynamic-delete-button { - font-size: 20px !important; - color: #999 !important; - cursor: pointer !important; - position: relative !important; - transition: all .3s !important; - margin-left: 5px !important; -} - -.vert-text { - max-width: 40px; - background-color: #fff; - position: relative; - z-index: 99; - margin-left: auto; - margin-right: auto; -} - -.vert-line { - position: absolute; - left: 0; - right: 0; - width: 100%; - content: ''; - height: 1px; - background-color: #00000047; - bottom: 0; - top: 0; - margin-bottom: auto; - margin-top: auto; -} diff --git a/worklenz-frontend/src/app/administrator/account-setup/account-setup/account-setup.component.spec.ts b/worklenz-frontend/src/app/administrator/account-setup/account-setup/account-setup.component.spec.ts deleted file mode 100644 index 22445c1c..00000000 --- a/worklenz-frontend/src/app/administrator/account-setup/account-setup/account-setup.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {AccountSetupComponent} from './account-setup.component'; - -describe('AccountSetupComponent', () => { - let component: AccountSetupComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [AccountSetupComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(AccountSetupComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/account-setup/account-setup/account-setup.component.ts b/worklenz-frontend/src/app/administrator/account-setup/account-setup/account-setup.component.ts deleted file mode 100644 index e975e914..00000000 --- a/worklenz-frontend/src/app/administrator/account-setup/account-setup/account-setup.component.ts +++ /dev/null @@ -1,298 +0,0 @@ -import {AfterViewInit, Component, OnInit, ViewChild} from '@angular/core'; -import { - AbstractControl, - FormArray, - FormBuilder, - FormControl, - FormGroup, - ValidationErrors, - ValidatorFn, - Validators -} from "@angular/forms"; -import {AppService} from "@services/app.service"; -import {AuthService} from "@services/auth.service"; -import {Router} from "@angular/router"; -import {log_error, smallId} from "@shared/utils"; -import {ProfileSettingsApiService} from "@api/profile-settings-api.service"; -import {EMAIL_REGEXP, WHITESPACE_REGEXP} from "@shared/constants"; -import { - ProjectTemplateImportDrawerComponent -} from "@admin/components/project-template-import-drawer/project-template-import-drawer.component"; -import {ProjectTemplateApiService} from "@api/project-template-api.service"; - -export interface IAccountSetupRequest { - team_name?: string; - project_name?: string; - tasks: string[]; - team_members: string[]; -} - -export interface IAccountSetupResponse { - id?: string; - has_invitations?: boolean; -} - -@Component({ - selector: 'worklenz-account-setup', - templateUrl: './account-setup.component.html', - styleUrls: ['./account-setup.component.scss'] -}) -export class AccountSetupComponent implements OnInit, AfterViewInit { - @ViewChild(ProjectTemplateImportDrawerComponent) projectTemplateDrawer!: ProjectTemplateImportDrawerComponent; - - form!: FormGroup; - - inputsMap: { [x: number]: string } = {}; - validateForm!: FormGroup; - validateFormMember!: FormGroup; - loading = false; - verifying = false; - - readonly teamNameId = smallId(6); - readonly projectNameId = smallId(6); - readonly emailInputId = smallId(6); - - skipInviteClicked = false; - selectedTemplateId: string | null = null; - - constructor( - private fb: FormBuilder, - private app: AppService, - private auth: AuthService, - private api: ProfileSettingsApiService, - private templateApi: ProjectTemplateApiService, - private router: Router - ) { - this.form = this.fb.group({ - team_name: [null, [Validators.required, Validators.pattern(WHITESPACE_REGEXP)]], - project_name: [null, [Validators.required, Validators.pattern(WHITESPACE_REGEXP)]], - tasks: this.fb.array([], [Validators.minLength(1), Validators.pattern(WHITESPACE_REGEXP)]), - team_members: this.fb.array([], [Validators.minLength(1), this.validEmail(EMAIL_REGEXP)]) - }) - this.app.setTitle('Setup your account'); - } - - get profile() { - return this.auth.getCurrentSession(); - } - - get teamSetupPlaceholder() { - return `e.g., ${this.profile?.name}'s Team`; - } - - get projectName() { - return this.form.value.project_name; - } - - get workspaceName() { - return this.form.value.team_name; - } - - _index = 0; - - get index() { - return this._index; - } - - set index(i) { - this._index = i; - } - - get getTaskControls() { - return this.form.get('tasks'); - } - - get getTasks() { - return this.form.controls['tasks'] as FormArray; - } - - get getTeamMemberControls() { - return this.form.get('team_members'); - } - - get getTeamMembers() { - return this.form.controls['team_members'] as FormArray; - } - - ngOnInit(): void { - void this.reauthorize(); - this.validateForm = this.fb.group({}); - this.validateFormMember = this.fb.group({}); - this.addNewTaskRow(); - this.addNewTeamMemberRow(); - } - - ngAfterViewInit() { - this.inputsMap = { - 0: this.teamNameId, - 1: this.projectNameId, - 2: 'task-name-input-0', - 3: this.emailInputId - }; - - this.focusInput(); - } - - public async submit() { - if (this.loading) return; - try { - this.loading = true; - const model = this.form.value; - - model.tasks = model.tasks.filter((t: any) => t?.trim().length); - model.template_id = this.selectedTemplateId; - - let res: any; - if (model.template_id) { - res = await this.templateApi.setupAccount(model); - } else { - res = await this.api.setupAccount(model); - } - - await this.auth.authorize(); - if (res.done && res.body.id) { - await this.auth.authorize(); - - const url = (res.body.has_invitations) - ? `/worklenz/setup/teams` - : `/worklenz/projects/${res.body.id}`; - await this.router.navigate([url]); - } - - this.loading = false; - } catch (e) { - log_error(e); - this.loading = false; - } - } - - previous() { - if (this.index > 0) { - this.index -= 1; - } - } - - next() { - if (!this.isValid()) return; - - if ((this.index + 1) > 3) { - void this.submit(); - } else { - this.index++; - this.focusInput(); - } - } - - onIndexChange(index: number) { - this.index = index; - this.focusInput(); - } - - validEmail(pattern: RegExp): ValidatorFn { - return (control: AbstractControl): ValidationErrors | null => { - let valid = false; - if (Array.isArray(control.value)) { - valid = control.value.every(email => pattern.test(email)); - } else { - valid = pattern.test(control.value); - } - return valid ? null : {email: {value: control.value}}; - }; - } - - isValidTasksInput() { - if (this.getTasks.length && this.getTasks.valid) return true; - return this.getTasks.valid; - } - - isValidTeamMembers() { - return this.getTeamMembers.length && this.getTeamMembers.valid; - } - - isValid() { - if (this.index === 0) return this.form.controls["team_name"].valid; - if (this.index === 1) return this.form.controls["project_name"].valid; - if (this.index === 2) return this.isValidTasksInput(); - if (this.index === 3) return this.isValidTeamMembers(); - return false; - } - - addNewTaskRow(event?: MouseEvent): void { - if (event) event.preventDefault(); - const emptyTaskInput = new FormControl(null, [Validators.required]); - this.getTasks.push(emptyTaskInput); - // Focus new input - setTimeout(() => { - const element = document.querySelector(`#task-name-input-${this.getTaskControls.controls.length - 1}`) as HTMLInputElement; - element?.focus(); - }, 100); - } - - removeTaskRow(i: number, e: MouseEvent): void { - e.preventDefault(); - const tasks = this.getTasks; - if (tasks.length > 1) { - tasks.removeAt(i); - } - } - - addNewTeamMemberRow(e?: MouseEvent): void { - if (e) { - e.preventDefault(); - } - - const teamMembers = this.getTeamMembers; - const teamMemberForm = new FormControl('', [Validators.email]); - - teamMembers.push(teamMemberForm); - } - - removeTeamMember(i: number, e: MouseEvent): void { - e.preventDefault(); - const teamMembers = this.getTeamMembers; - if (teamMembers.length > 1) { - teamMembers.removeAt(i); - } - } - - isTeamNameValid() { - return this.form.controls["team_name"].valid - } - - isProjectNameValid() { - return this.form.controls["project_name"].valid - } - - private async reauthorize() { - this.verifying = true; - await this.auth.authorize(); - if (this.auth.getCurrentSession()?.setup_completed) - return this.router.navigate(['/worklenz/home']); - this.verifying = false; - return null; - } - - private focusInput() { - setTimeout(() => { - const id = this.inputsMap[this.index]; - const element = document.querySelector(`#${id}`) as HTMLInputElement; - element?.focus(); - }, 250); - } - - skipInvite() { - this.skipInviteClicked = false; - this.form.controls['team_members'].reset([]); - void this.submit(); - } - - openTemplateSelector() { - this.projectTemplateDrawer.open(); - } - - templateSelected(event: any) { - this.selectedTemplateId = event.template_id; - this.submit(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/account-setup/teams-list/teams-list.component.html b/worklenz-frontend/src/app/administrator/account-setup/teams-list/teams-list.component.html deleted file mode 100644 index 338a4df1..00000000 --- a/worklenz-frontend/src/app/administrator/account-setup/teams-list/teams-list.component.html +++ /dev/null @@ -1,50 +0,0 @@ -
-
-
-
- Worklenz -
- -
- - - -
Teams
- - - {{item.name}} - - - -
- - -
Invitations
- - - {{item.team_name}} - - - -
-
- - - - -
-
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/account-setup/teams-list/teams-list.component.scss b/worklenz-frontend/src/app/administrator/account-setup/teams-list/teams-list.component.scss deleted file mode 100644 index 7298e213..00000000 --- a/worklenz-frontend/src/app/administrator/account-setup/teams-list/teams-list.component.scss +++ /dev/null @@ -1,16 +0,0 @@ -nz-list-item { - cursor: pointer; - - &:hover { - background: #f8f7f9; - } - - &.selected { - background: rgba(24, 144, 255, 0.0784313725) !important; - } -} - -nz-radio-group { - max-height: calc(100vh - 270px); - overflow: auto; -} diff --git a/worklenz-frontend/src/app/administrator/account-setup/teams-list/teams-list.component.spec.ts b/worklenz-frontend/src/app/administrator/account-setup/teams-list/teams-list.component.spec.ts deleted file mode 100644 index c848db12..00000000 --- a/worklenz-frontend/src/app/administrator/account-setup/teams-list/teams-list.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TeamsListComponent} from './teams-list.component'; - -describe('TeamsListComponent', () => { - let component: TeamsListComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TeamsListComponent] - }); - fixture = TestBed.createComponent(TeamsListComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/account-setup/teams-list/teams-list.component.ts b/worklenz-frontend/src/app/administrator/account-setup/teams-list/teams-list.component.ts deleted file mode 100644 index e2e3b263..00000000 --- a/worklenz-frontend/src/app/administrator/account-setup/teams-list/teams-list.component.ts +++ /dev/null @@ -1,132 +0,0 @@ -import {Component, OnInit} from '@angular/core'; -import {TeamsApiService} from "@api/teams-api.service"; -import {log_error} from "@shared/utils"; -import {ITeamGetResponse} from "@interfaces/api-models/team-get-response"; -import {ITeamInvites} from "@interfaces/team"; -import {AuthService} from "@services/auth.service"; -import {Router} from "@angular/router"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {AppService} from "@services/app.service"; - -interface ITeamViewModel extends ITeamGetResponse { - active?: boolean; - pending_invitation?: boolean; -} - -@Component({ - selector: 'worklenz-teams-list', - templateUrl: './teams-list.component.html', - styleUrls: ['./teams-list.component.scss'] -}) -export class TeamsListComponent implements OnInit { - teams: ITeamViewModel[] = []; - invites: ITeamInvites[] = []; - - selectedTeamId: string | undefined = undefined; - - loading = false; - switching = false; - isInvitation = false; - - constructor( - private readonly app: AppService, - private readonly auth: AuthService, - private readonly teamsApi: TeamsApiService, - private readonly router: Router - ) { - this.app.setTitle("Teams & Invitations"); - } - - ngOnInit() { - void this.getData(); - } - - private updateDefaultSelection() { - if (this.teams.length) { - const activeTeam = this.teams.find(t => t.active); - if (activeTeam) { - this.selectedTeamId = activeTeam.id; - } else { - this.selectedTeamId = this.teams[0].id; - } - } else if (this.invites.length) { - this.selectedTeamId = this.invites[0].team_id; - } - } - - private async getData() { - this.loading = true; - await this.getTeams(); - await this.getInvites(); - this.updateDefaultSelection(); - this.loading = false; - } - - private async getTeams() { - try { - const res: IServerResponse = await this.teamsApi.get(); - if (res.done) { - this.teams = res.body.filter(t => !t.pending_invitation); - } - } catch (e) { - log_error(e); - } - } - - private async getInvites() { - try { - const res = await this.teamsApi.getInvites(); - if (res.done) { - this.invites = res.body; - } - } catch (e) { - log_error(e); - } - } - - selectTeam(id: string | undefined, isInvitation: boolean) { - if (id) { - this.selectedTeamId = id; - this.isInvitation = isInvitation; - } - } - - async continueWithSelection() { - if (this.selectedTeamId) { - try { - this.switching = true; - - if (this.isInvitation) { - const accepted = await this.acceptInvitation(); - if (!accepted) { - this.switching = false; - this.app.notify("Request failed!", "Invitation accept failed. Please try again.", false); - return; - } - } - - const res = await this.teamsApi.activate(this.selectedTeamId); - if (res.done) { - await this.handleSelectionDone(); - } - this.switching = false; - } catch (e) { - this.switching = false; - } - } - } - - private async handleSelectionDone() { - await this.auth.authorize(); - await this.router.navigate(["/worklenz"]); - } - - private async acceptInvitation() { - const invitation = this.invites.find(i => i.team_id === this.selectedTeamId); - if (invitation) { - const res = await this.teamsApi.accept({team_member_id: invitation.team_member_id}); - return res.done; - } - return false; - } -} diff --git a/worklenz-frontend/src/app/administrator/admin-center/admin-center-routing.module.ts b/worklenz-frontend/src/app/administrator/admin-center/admin-center-routing.module.ts deleted file mode 100644 index c8db3df0..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/admin-center-routing.module.ts +++ /dev/null @@ -1,28 +0,0 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {OverviewComponent} from './overview/overview.component'; -import {UsersComponent} from './users/users.component'; -import {TeamsComponent} from './teams/teams.component'; -import {LayoutComponent} from './layout/layout.component'; - - -const routes: Routes = [ - { - path: '', - component: LayoutComponent, - children: [ - {path: "", redirectTo: "overview", pathMatch: "full"}, - {path: "overview", component: OverviewComponent}, - {path: "users", component: UsersComponent}, - {path: "teams", component: TeamsComponent}, - ] - } -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class AdminCenterRoutingModule { - -} diff --git a/worklenz-frontend/src/app/administrator/admin-center/admin-center-service.service.ts b/worklenz-frontend/src/app/administrator/admin-center/admin-center-service.service.ts deleted file mode 100644 index b84a56c0..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/admin-center-service.service.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {Injectable} from '@angular/core'; -import {Subject} from "rxjs"; - -@Injectable({ - providedIn: 'root' -}) -export class AdminCenterService { - private readonly _teamCreateSbj$ = new Subject(); - private readonly _teamNameChangeSbj$ = new Subject<{ teamId: string, teamName: string }>(); - - get onCreateTeam() { - return this._teamCreateSbj$.asObservable(); - } - - get onTeamNameChange() { - return this._teamNameChangeSbj$.asObservable(); - } - - public emitCreateTeam() { - this._teamCreateSbj$.next(); - } - - public emitTeamNameChange(response: { teamId: string, teamName: string }) { - this._teamNameChangeSbj$.next(response); - } - -} diff --git a/worklenz-frontend/src/app/administrator/admin-center/admin-center.module.ts b/worklenz-frontend/src/app/administrator/admin-center/admin-center.module.ts deleted file mode 100644 index fc2e9733..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/admin-center.module.ts +++ /dev/null @@ -1,86 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {AdminCenterRoutingModule} from './admin-center-routing.module'; -import {NzLayoutModule} from 'ng-zorro-antd/layout'; -import {NzPageHeaderModule} from 'ng-zorro-antd/page-header'; -import {SidebarComponent} from './sidebar/sidebar.component'; -import {NzMenuModule} from 'ng-zorro-antd/menu'; -import {NzIconModule} from 'ng-zorro-antd/icon'; -import {OverviewComponent} from './overview/overview.component'; -import {NzCardModule} from 'ng-zorro-antd/card'; -import {NzTypographyModule} from 'ng-zorro-antd/typography'; -import {NzTableModule} from 'ng-zorro-antd/table'; -import {UsersComponent} from './users/users.component'; -import {TeamsComponent} from './teams/teams.component'; -import {LayoutComponent} from './layout/layout.component'; -import {NzSpaceModule} from 'ng-zorro-antd/space'; -import {NzFormModule} from 'ng-zorro-antd/form'; -import {NzInputModule} from 'ng-zorro-antd/input'; -import {NzButtonModule} from 'ng-zorro-antd/button'; -import {NzSkeletonModule} from 'ng-zorro-antd/skeleton'; -import {NzAvatarModule} from 'ng-zorro-antd/avatar'; -import {NzBadgeModule} from 'ng-zorro-antd/badge'; -import {NzToolTipModule} from 'ng-zorro-antd/tooltip'; -import {NzDropDownModule} from 'ng-zorro-antd/dropdown'; -import {NzRadioModule} from 'ng-zorro-antd/radio'; -import {NzDrawerModule} from 'ng-zorro-antd/drawer'; -import {NzSelectModule} from 'ng-zorro-antd/select'; -import {FormsModule, ReactiveFormsModule} from "@angular/forms"; -import {AvatarsComponent} from "../components/avatars/avatars.component"; -import {NzTabsModule} from "ng-zorro-antd/tabs"; -import {FirstCharUpperPipe} from "@pipes/first-char-upper.pipe"; -import {NzModalModule} from "ng-zorro-antd/modal"; -import {NzProgressModule} from "ng-zorro-antd/progress"; -import {NzDividerModule} from "ng-zorro-antd/divider"; -import {NzSegmentedModule} from "ng-zorro-antd/segmented"; -import {NzPopconfirmModule} from "ng-zorro-antd/popconfirm"; -import {NzCheckboxModule} from "ng-zorro-antd/checkbox"; -import {NzAutocompleteModule} from "ng-zorro-antd/auto-complete"; - - -@NgModule({ - declarations: [ - SidebarComponent, - OverviewComponent, - UsersComponent, - TeamsComponent, - LayoutComponent - ], - imports: [ - CommonModule, - AdminCenterRoutingModule, - NzLayoutModule, - NzPageHeaderModule, - NzMenuModule, - NzIconModule, - NzCardModule, - NzTypographyModule, - NzTableModule, - NzSpaceModule, - NzFormModule, - NzInputModule, - NzButtonModule, - NzSkeletonModule, - NzBadgeModule, - NzAvatarModule, - NzToolTipModule, - NzDropDownModule, - NzRadioModule, - NzDrawerModule, - NzSelectModule, - ReactiveFormsModule, - AvatarsComponent, - FormsModule, - NzTabsModule, - FirstCharUpperPipe, - NzModalModule, - NzProgressModule, - NzDividerModule, - NzSegmentedModule, - NzPopconfirmModule, - NzCheckboxModule, - NzAutocompleteModule - ] -}) -export class AdminCenterModule { -} diff --git a/worklenz-frontend/src/app/administrator/admin-center/layout/layout.component.html b/worklenz-frontend/src/app/administrator/admin-center/layout/layout.component.html deleted file mode 100644 index 44b1b89d..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/layout/layout.component.html +++ /dev/null @@ -1,13 +0,0 @@ -
- - Admin Center - - - - - - - - - -
diff --git a/worklenz-frontend/src/app/administrator/admin-center/layout/layout.component.scss b/worklenz-frontend/src/app/administrator/admin-center/layout/layout.component.scss deleted file mode 100644 index bab62ed1..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/layout/layout.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -[nz-submenu] { - transition: none !important; -} diff --git a/worklenz-frontend/src/app/administrator/admin-center/layout/layout.component.spec.ts b/worklenz-frontend/src/app/administrator/admin-center/layout/layout.component.spec.ts deleted file mode 100644 index a279466f..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/layout/layout.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {LayoutComponent} from './layout.component'; - -describe('LayoutComponent', () => { - let component: LayoutComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [LayoutComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(LayoutComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/admin-center/layout/layout.component.ts b/worklenz-frontend/src/app/administrator/admin-center/layout/layout.component.ts deleted file mode 100644 index d9a8f5d8..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/layout/layout.component.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {Component} from '@angular/core'; - -@Component({ - selector: 'worklenz-layout', - templateUrl: './layout.component.html', - styleUrls: ['./layout.component.scss'] -}) -export class LayoutComponent { - -} diff --git a/worklenz-frontend/src/app/administrator/admin-center/overview/overview.component.html b/worklenz-frontend/src/app/administrator/admin-center/overview/overview.component.html deleted file mode 100644 index 4f6a37a7..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/overview/overview.component.html +++ /dev/null @@ -1,75 +0,0 @@ - - Overview - - -

Organization Name

-
-

-
-
-
- -

Organization Owner

-
- -

{{organizationDetails.owner_name}}

-
- - - {{organizationDetails.email}} - -
-
- -   - - -
- - {{organizationDetails.contact_number}} - Add Contact Number - -
- -
-
- - - - - -
-
-
-
-
-
-
-
- -

Organization Admins

-
- - - - - {{item.name}} (Owner) - - {{item.email}} - - - - -
-
diff --git a/worklenz-frontend/src/app/administrator/admin-center/overview/overview.component.scss b/worklenz-frontend/src/app/administrator/admin-center/overview/overview.component.scss deleted file mode 100644 index bc0f718b..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/overview/overview.component.scss +++ /dev/null @@ -1,42 +0,0 @@ -.card-content { - padding-top: 8px; -} - -.ant-card-bordered { - // border: 1px solid #d9d9d9; -} - -h4 { - font-size: 16px; -} - -p { - margin-bottom: 8px; -} - -.b-65 { - color: rgba(0, 0, 0, 0.65); -} - -.left-td { - width: 250px; -} - -.number-input { - position: absolute; - left: 0; - top: 0; - min-width: 200px; - width: 100%; -} - -.edit-btn { - cursor: pointer; - color: #1890ff; -} - -.text-btn { - line-height: 32px; - cursor: pointer; - color: #188fff -} diff --git a/worklenz-frontend/src/app/administrator/admin-center/overview/overview.component.spec.ts b/worklenz-frontend/src/app/administrator/admin-center/overview/overview.component.spec.ts deleted file mode 100644 index bcada641..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/overview/overview.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {OverviewComponent} from './overview.component'; - -describe('OverviewComponent', () => { - let component: OverviewComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [OverviewComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(OverviewComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/admin-center/overview/overview.component.ts b/worklenz-frontend/src/app/administrator/admin-center/overview/overview.component.ts deleted file mode 100644 index 4e54c7fc..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/overview/overview.component.ts +++ /dev/null @@ -1,104 +0,0 @@ -import {Component, ElementRef, NgZone, OnInit, ViewChild} from '@angular/core'; -import {AccountCenterApiService} from "@api/account-center-api.service"; -import {log_error} from "@shared/utils"; -import {IOrganization, IOrganizationAdmin} from "@interfaces/account-center"; - -@Component({ - selector: 'worklenz-overview', - templateUrl: './overview.component.html', - styleUrls: ['./overview.component.scss'] -}) -export class OverviewComponent implements OnInit { - @ViewChild('numberInput') private numberInput: ElementRef | undefined; - loadingName = false; - loadingAdmins = false; - isNumberEditing = false; - - organizationDetails: IOrganization = {}; - organizationAdmins: IOrganizationAdmin[] = []; - - constructor( - private api: AccountCenterApiService, - private readonly ngZone: NgZone) { - } - - ngOnInit() { - void this.getOrganizationName(); - void this.getOrganizationAdmins(); - } - - async getOrganizationName() { - try { - this.loadingName = true; - const res = await this.api.getOrganizationName(); - if (res.done) { - this.loadingName = false; - this.organizationDetails = res.body; - } - } catch (e) { - this.loadingName = false; - log_error(e); - } - } - - async getOrganizationAdmins() { - try { - this.loadingAdmins = true; - const res = await this.api.getOrganizationAdmins(); - if (res.done) { - this.loadingAdmins = false; - this.organizationAdmins = res.body; - } - } catch (e) { - this.loadingAdmins = false; - log_error(e); - } - } - - async updateOrganizationName() { - try { - this.loadingName = true; - const res = await this.api.updateOrganizationName({name: this.organizationDetails.name}); - if (res.done) { - this.loadingName = false; - } - } catch (e) { - this.loadingName = false; - log_error(e); - } - await this.getOrganizationName(); - } - - async updateOwnerContactNumber() { - try { - this.loadingName = true; - this.isNumberEditing = false; - const res = await this.api.updateOwnerContactNumber({contact_number: this.organizationDetails.contact_number || ''}); - if (res.done) { - this.loadingName = false; - await this.getOrganizationName(); - } - } catch (e) { - this.loadingName = false; - log_error(e); - } - } - - focusNumberInput() { - this.isNumberEditing = true; - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.numberInput?.nativeElement.focus(); - this.numberInput?.nativeElement.select(); - }, 100) - }); - } - - sanitizeContactNumber(event: any) { - const input = event.target as HTMLInputElement; - const sanitizedValue = input.value.replace(/[^0-9()+ -]/g, ''); - this.organizationDetails.contact_number = sanitizedValue; - - } - -} diff --git a/worklenz-frontend/src/app/administrator/admin-center/sidebar/sidebar.component.html b/worklenz-frontend/src/app/administrator/admin-center/sidebar/sidebar.component.html deleted file mode 100644 index 132ce55b..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/sidebar/sidebar.component.html +++ /dev/null @@ -1,11 +0,0 @@ -
    -
  • -
  • -
  • -
  • -
  • -
  • -
diff --git a/worklenz-frontend/src/app/administrator/admin-center/sidebar/sidebar.component.spec.ts b/worklenz-frontend/src/app/administrator/admin-center/sidebar/sidebar.component.spec.ts deleted file mode 100644 index 98bf5939..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/sidebar/sidebar.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {SidebarComponent} from './sidebar.component'; - -describe('SidebarComponent', () => { - let component: SidebarComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [SidebarComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(SidebarComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/admin-center/sidebar/sidebar.component.ts b/worklenz-frontend/src/app/administrator/admin-center/sidebar/sidebar.component.ts deleted file mode 100644 index 9796fdd6..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/sidebar/sidebar.component.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {Component} from '@angular/core'; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-admin-center-sidebar', - templateUrl: './sidebar.component.html', - styleUrls: ['./sidebar.component.scss'] -}) -export class SidebarComponent { - constructor( - private readonly auth: AuthService - ) { - } - - get profile() { - return this.auth.getCurrentSession(); - } -} diff --git a/worklenz-frontend/src/app/administrator/admin-center/teams/teams.component.html b/worklenz-frontend/src/app/administrator/admin-center/teams/teams.component.html deleted file mode 100644 index 55218444..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/teams/teams.component.html +++ /dev/null @@ -1,260 +0,0 @@ - - Teams - - - {{this.total ? this.total : 0}} teams - - - -
- - - - - - -
- - - -
-
-
- - - - - - Team - Members Count - Members - - - - - - - - - - {{currentTeam.members_count}} - - - - - -
- - - - -
- - - - - {{team.name}} - - - {{team.members_count}} - - - - - -
- - - - -
- - - -
-
-
- - -
-
- -
- No teams found in the organization. -
-
- - - - - - - -
- - - Team Name - - - - - - - Users ({{teamMembers.controls.length}}) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - User - Role - - - - - - - - {{item.value.name}} - - - - - - - - - - - - - - - - -
-
-
- - - -
- - - Team name - - - - - - -
-
-
- - - diff --git a/worklenz-frontend/src/app/administrator/admin-center/teams/teams.component.scss b/worklenz-frontend/src/app/administrator/admin-center/teams/teams.component.scss deleted file mode 100644 index b0445c74..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/teams/teams.component.scss +++ /dev/null @@ -1,11 +0,0 @@ -nz-page-header-subtitle { - color: hsla(0, 0%, 0%, 0.85); - font-weight: 500; - font-size: 16px; -} - - -.no-data-img-holder { - width: 100px; - margin-top: 42px; -} diff --git a/worklenz-frontend/src/app/administrator/admin-center/teams/teams.component.spec.ts b/worklenz-frontend/src/app/administrator/admin-center/teams/teams.component.spec.ts deleted file mode 100644 index f5dbcb93..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/teams/teams.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TeamsComponent} from './teams.component'; - -describe('TeamsComponent', () => { - let component: TeamsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TeamsComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TeamsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/admin-center/teams/teams.component.ts b/worklenz-frontend/src/app/administrator/admin-center/teams/teams.component.ts deleted file mode 100644 index 805677ba..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/teams/teams.component.ts +++ /dev/null @@ -1,293 +0,0 @@ -import {Component} from '@angular/core'; -import {AvatarNamesMap, DEFAULT_PAGE_SIZE} from "@shared/constants"; -import {FormArray, FormBuilder, FormGroup, Validators} from "@angular/forms"; -import {AccountCenterApiService} from "@api/account-center-api.service"; -import {NzTableQueryParams} from "ng-zorro-antd/table"; -import {isValidateEmail, log_error} from "@shared/utils"; -import {IOrganizationTeam, IOrganizationTeamMember, IOrganizationUser} from "@interfaces/account-center"; -import {AppService} from "@services/app.service"; -import {TeamsApiService} from "@api/teams-api.service"; -import {AdminCenterService} from "../admin-center-service.service"; -import {ITeamMemberCreateRequest} from "@interfaces/api-models/team-member-create-request"; -import {TeamMembersApiService} from "@api/team-members-api.service"; -import {ProjectMembersApiService} from "@api/project-members-api.service"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-teams', - templateUrl: './teams.component.html', - styleUrls: ['./teams.component.scss'] -}) -export class TeamsComponent { - visible = false; - visibleNewTeam = false; - teams: IOrganizationTeam[] = []; - currentTeam: IOrganizationTeam | null = null; - - loading = false; - - // Table sorting & pagination - total = 0; - pageSize = DEFAULT_PAGE_SIZE; - pageIndex = 1; - paginationSizes = [5, 10, 15, 20, 50, 100]; - sortField: string | null = null; - sortOrder: string | null = null; - - form!: FormGroup; - editTeamForm!: FormGroup; - searchForm!: FormGroup; - - loadingTeamDetails = false; - teamData: IOrganizationTeam = {}; - selectedTeam: IOrganizationTeam = {}; - updatingTeam = false; - - users: IOrganizationUser[] = [] - totalUsers = 0; - - searchingName: string | null = null; - projectId: string | null = null; - inviting = false; - - get buttonText() { - return this.isValueIsAnEmail() ? 'Invite as a member' : 'Invite a new member by email'; - } - - constructor( - private readonly api: AccountCenterApiService, - private readonly teamMembersApi: TeamMembersApiService, - private readonly teamsApiService: TeamsApiService, - private fb: FormBuilder, - private app: AppService, - private readonly service: AdminCenterService, - private readonly membersApi: ProjectMembersApiService, - private readonly auth: AuthService - ) { - this.app.setTitle("Admin Center - Teams"); - this.form = this.fb.group({ - name: [null, [Validators.required]] - }); - this.editTeamForm = this.fb.group({ - name: [null, [Validators.required]], - teamMembers: this.fb.array([]), - search: [null] - }); - this.searchForm = this.fb.group({search: []}); - this.searchForm.valueChanges.subscribe(() => this.getTeams()); - this.editTeamForm.controls["search"]?.valueChanges.subscribe((value) => this.handleMemberSelect(value)); - } - - get teamMembers() { - return this.editTeamForm.get('teamMembers'); - } - - getColor(name?: string) { - return AvatarNamesMap[name?.charAt(0).toUpperCase() || 'A']; - } - - async onQueryParamsChange(params: NzTableQueryParams) { - const {pageSize, pageIndex, sort} = params; - this.pageIndex = pageIndex; - this.pageSize = pageSize; - - const currentSort = sort.find(item => item.value !== null); - - this.sortField = (currentSort && currentSort.key) || null; - this.sortOrder = (currentSort && currentSort.value) || null; - - await this.getTeams(); - } - - async getTeams() { - try { - this.loading = true; - const res = await this.api.getOrganizationTeams(this.pageIndex, this.pageSize, this.sortField, this.sortOrder, this.searchForm.value.search); - if (res.done) { - this.total = res.body.total || 0; - this.teams = res.body.data?.filter(t => t.id !== this.auth.getCurrentSession()?.team_id) || []; - this.currentTeam = res.body.current_team_data || null; - this.loading = false; - } - } catch (e) { - this.loading = false; - log_error(e); - } - } - - async createTeam() { - if (!this.form.value || !this.form.value.name || this.form.value.name.trim() === "") return; - try { - if (this.form.valid) { - this.loading = true; - const res = await this.teamsApiService.create({name: this.form.value.name}); - if (res.done) { - this.closeNewTeam(); - void this.getTeams(); - this.service.emitCreateTeam(); - } - } else { - this.app.displayErrorsOf(this.form); - } - } catch (e) { - this.loading = false; - log_error(e); - } - this.loading = false; - } - - async openTeamDrawer(team: IOrganizationTeam) { - if (!team.id) return; - try { - this.loadingTeamDetails = true; - this.selectedTeam = team; - - this.getTeamMembers(team); - } catch (e) { - this.loadingTeamDetails = false; - log_error(e); - } - this.visible = true; - } - - async getTeamMembers(team: IOrganizationTeam) { - if (!team.id) return; - try { - const res = await this.api.getOrganizationTeam(team.id); - if (res.done) { - this.teamMembers.clear(); - this.teamData = res.body; - this.total = this.teamData.team_members?.length || 0; - - this.editTeamForm.patchValue({name: this.teamData.name}); - if (this.teamData.team_members?.map((member: IOrganizationTeamMember) => { - const tempForm = this.fb.group({ - id: member.id, - user_id: member.user_id, - name: member.name, - role_name: member.role_name, - avatar_url: member.avatar_url - }); - this.teamMembers.push(tempForm); - })) - this.loadingTeamDetails = false; - } - } catch (e) { - this.loadingTeamDetails = false; - } - } - - close(): void { - this.teamMembers.clear(); - this.editTeamForm.reset(); - this.visible = false; - } - - openNewTeam() { - this.visibleNewTeam = true; - } - - closeNewTeam() { - this.visibleNewTeam = false; - this.form.reset(); - } - - async submit() { - if (!this.teamData.id) return; - - if (!this.editTeamForm.value || !this.editTeamForm.value.name || this.editTeamForm.value.name.trim() === "") return; - - try { - this.updatingTeam = true; - const res = await this.api.updateTeam(this.teamData.id, this.editTeamForm.value); - if (res.done) { - this.service.emitTeamNameChange({teamId: this.teamData.id, teamName: this.editTeamForm.value.name}); - this.close(); - void this.getTeams(); - } - } catch (e) { - log_error(e); - } - } - - async deleteTeam(id: string | undefined) { - if (!id) return; - try { - const res = await this.api.deleteTeam(id); - if (res.done) { - await this.getTeams(); - } - } catch (e) { - log_error(e); - } - } - - async handleMemberSelect(value: string) { - if (!value || !this.selectedTeam.id) return; - if (this.editTeamForm.valid) { - try { - this.loading = true; - const body: ITeamMemberCreateRequest = { - job_title: null, - emails: [value], - is_admin: false - }; - - const res = await this.teamMembersApi.addTeamMember(this.selectedTeam.id, body); - if (res.done) { - this.editTeamForm.controls["search"]?.setValue(null); - this.getTeamMembers(this.selectedTeam); - } - this.loading = false; - } catch (e) { - log_error(e); - this.loading = false; - } - } else { - this.app.displayErrorsOf(this.form); - } - } - - async deleteTeamMember(id: string | undefined) { - if (!id || !this.selectedTeam.id) return; - try { - const res = await this.api.removeTeamMember(id, this.selectedTeam.id); - if (res.done) { - if (id === this.auth.getCurrentSession()?.team_member_id) { - window.location.reload(); - } else { - await this.getTeamMembers(this.selectedTeam); - } - } - } catch (e) { - log_error(e); - } - } - - isValueIsAnEmail() { - if (!this.searchingName) return false; - return isValidateEmail(this.searchingName); - } - - async sendInvitation() { - if (!this.projectId) return; - if (typeof this.searchingName !== "string" || !this.searchingName.length) return; - - try { - const email = this.searchingName.trim().toLowerCase(); - const request = { - project_id: this.projectId, - email - }; - this.inviting = true; - const res = await this.membersApi.createByEmail(request); - this.inviting = false; - if (res.done) { - // this.resetSearchInput(); - } - } catch (e) { - this.inviting = false; - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/admin-center/users/users.component.html b/worklenz-frontend/src/app/administrator/admin-center/users/users.component.html deleted file mode 100644 index 8209d693..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/users/users.component.html +++ /dev/null @@ -1,75 +0,0 @@ - - Users - - - {{total}} users - - - -
- - - - - - -
-
-
-
- - - - - - User - Email - Last Activity - - - - - - - - {{item.name}} - {{item.is_admin ? '(Admin)' : item.is_owner ? '(Owner)' : ''}} - - - - {{item.email}} - - - {{(item.last_logged | date: 'medium') || '-'}} - - - - - - - - -
-
- -
- No users found in the organization. -
-
diff --git a/worklenz-frontend/src/app/administrator/admin-center/users/users.component.scss b/worklenz-frontend/src/app/administrator/admin-center/users/users.component.scss deleted file mode 100644 index 1e29b3e4..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/users/users.component.scss +++ /dev/null @@ -1,45 +0,0 @@ -.you-text { - font-size: 12px; - color: rgba(0, 0, 0, 0.45); -} - -.admin-text { - font-size: 12px; - background-color: rgba(250, 173, 20, 0.1); - font-weight: 500; - padding: 0px 4px; - border-radius: 12px; - color: #FAAD14; -} - -.ant-dropdown { - position: relative; - margin: 0; - padding: 4px 0; - text-align: left; - list-style-type: none; - background-color: #fff; - background-clip: padding-box; - border-radius: 2px; - outline: none; - box-shadow: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d; -} - -.ant-form-item-label > label { - float: left !important; -} - -.role-selector { - margin-top: 24px; -} - -nz-page-header-subtitle { - color: hsla(0, 0%, 0%, 0.85); - font-weight: 500; - font-size: 16px; -} - -.no-data-img-holder { - width: 100px; - margin-top: 42px; -} diff --git a/worklenz-frontend/src/app/administrator/admin-center/users/users.component.spec.ts b/worklenz-frontend/src/app/administrator/admin-center/users/users.component.spec.ts deleted file mode 100644 index 113e4b16..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/users/users.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {UsersComponent} from './users.component'; - -describe('UsersComponent', () => { - let component: UsersComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [UsersComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(UsersComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/admin-center/users/users.component.ts b/worklenz-frontend/src/app/administrator/admin-center/users/users.component.ts deleted file mode 100644 index dcccf2fd..00000000 --- a/worklenz-frontend/src/app/administrator/admin-center/users/users.component.ts +++ /dev/null @@ -1,75 +0,0 @@ -import {Component} from '@angular/core'; -import {IOrganizationUser} from "@interfaces/account-center"; -import {AvatarNamesMap, DEFAULT_PAGE_SIZE} from "@shared/constants"; -import {NzTableQueryParams} from "ng-zorro-antd/table"; -import {log_error} from "@shared/utils"; -import {AccountCenterApiService} from "@api/account-center-api.service"; -import {FormBuilder, FormGroup, Validators} from "@angular/forms"; - -@Component({ - selector: 'worklenz-users', - templateUrl: './users.component.html', - styleUrls: ['./users.component.scss'] -}) -export class UsersComponent { - visible = false; - visibleNewMember = false; - - users: IOrganizationUser[] = [] - loading = false; - - // Table sorting & pagination - total = 0; - pageSize = DEFAULT_PAGE_SIZE; - pageIndex = 1; - paginationSizes = [5, 10, 15, 20, 50, 100]; - sortField: string | null = null; - sortOrder: string | null = null; - - form!: FormGroup; - searchForm!: FormGroup; - - constructor( - private api: AccountCenterApiService, - private fb: FormBuilder, - ) { - this.form = this.fb.group({ - name: [null, [Validators.required]] - }); - this.searchForm = this.fb.group({search: []}); - this.searchForm.valueChanges.subscribe(() => this.getUsers()); - } - - getColor(name?: string) { - return AvatarNamesMap[name?.charAt(0).toUpperCase() || 'A']; - } - - async onQueryParamsChange(params: NzTableQueryParams) { - const {pageSize, pageIndex, sort} = params; - this.pageIndex = pageIndex; - this.pageSize = pageSize; - - const currentSort = sort.find(item => item.value !== null); - - this.sortField = (currentSort && currentSort.key) || null; - this.sortOrder = (currentSort && currentSort.value) || null; - - await this.getUsers(); - } - - async getUsers() { - try { - this.loading = true; - const res = await this.api.getOrganizationUsers(this.pageIndex, this.pageSize, this.sortField, this.sortOrder, this.searchForm.value.search); - if (res.done) { - this.total = res.body.total || 0; - this.users = res.body.data || []; - this.loading = false; - } - } catch (e) { - this.loading = false; - log_error(e); - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/administrator-routing.module.ts b/worklenz-frontend/src/app/administrator/administrator-routing.module.ts deleted file mode 100644 index a89593b1..00000000 --- a/worklenz-frontend/src/app/administrator/administrator-routing.module.ts +++ /dev/null @@ -1,55 +0,0 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {LayoutComponent} from './layout/layout.component'; -import {TeamOwnerOrAdminGuard} from '../guards/team-owner-or-admin-guard.service'; -import {TeamNameGuard} from '../guards/team-name.guard'; - -const routes: Routes = [ - { - path: '', - component: LayoutComponent, - canActivate: [TeamNameGuard], - children: [ - {path: '', redirectTo: 'home', pathMatch: 'full'}, - {path: 'dashboard', redirectTo: 'home', pathMatch: 'full'}, // Remove after a couple releases - { - path: 'home', - loadChildren: () => import('./my-dashboard/my-dashboard.module').then(m => m.MyDashboardModule) - }, - { - path: 'projects', - canActivate: [TeamOwnerOrAdminGuard], - loadChildren: () => import('./projects/projects.module').then(m => m.ProjectsModule) - }, - { - path: 'settings', - loadChildren: () => import('./settings/settings.module').then(m => m.SettingsModule) - }, - { - path: 'schedule', - canActivate: [TeamOwnerOrAdminGuard], - loadChildren: () => import('./schedule/schedule.module').then(m => m.ScheduleModule) - }, - { - path: 'reporting', - canActivate: [TeamOwnerOrAdminGuard], - loadChildren: () => import('./reporting/reporting.module').then(m => m.ReportingModule) - }, { - path: 'admin-center', - canActivate: [TeamOwnerOrAdminGuard], - loadChildren: () => import('./admin-center/admin-center.module').then(m => m.AdminCenterModule) - } - ] - }, - { - path: 'setup', - loadChildren: () => import('./account-setup/account-setup.module').then(m => m.AccountSetupModule) - } -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class AdministratorRoutingModule { -} diff --git a/worklenz-frontend/src/app/administrator/administrator.module.ts b/worklenz-frontend/src/app/administrator/administrator.module.ts deleted file mode 100644 index 9833d00c..00000000 --- a/worklenz-frontend/src/app/administrator/administrator.module.ts +++ /dev/null @@ -1,91 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule, NgOptimizedImage} from '@angular/common'; -import {FormsModule, ReactiveFormsModule} from "@angular/forms"; -import {NotificationsDrawerComponent} from "./layout/notifications-drawer/notifications-drawer.component"; -import {AdministratorRoutingModule} from './administrator-routing.module'; -import {LayoutComponent} from './layout/layout.component'; -import {NzSpinModule} from "ng-zorro-antd/spin"; -import {NzAffixModule} from "ng-zorro-antd/affix"; -import {NzAlertModule} from "ng-zorro-antd/alert"; -import {NzLayoutModule} from "ng-zorro-antd/layout"; -import {NzMenuModule} from "ng-zorro-antd/menu"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import {NzAvatarModule} from "ng-zorro-antd/avatar"; -import {NzBreadCrumbModule} from "ng-zorro-antd/breadcrumb"; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzEmptyModule} from "ng-zorro-antd/empty"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {FromNowPipe} from "@pipes/from-now.pipe"; -import {NzMessageServiceModule} from "ng-zorro-antd/message"; -import {AlertsComponent} from './layout/alerts/alerts.component'; -import {HeaderComponent} from './layout/header/header.component'; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; -import {NzTagModule} from "ng-zorro-antd/tag"; -import { - NotificationTemplateComponent -} from './layout/notifications-drawer/notification-template/notification-template.component'; -import {TagBackgroundPipe} from './layout/notifications-drawer/tag-background.pipe'; -import {NzSegmentedModule} from "ng-zorro-antd/segmented"; -import {NzDividerModule} from "ng-zorro-antd/divider"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport} from "@angular/cdk/scrolling"; -import {LicensingAlertsComponent} from './layout/licensing-alerts/licensing-alerts.component'; -import {NzResultModule} from "ng-zorro-antd/result"; -import {NzListModule} from "ng-zorro-antd/list"; -import {TeamMembersFormComponent} from "@admin/components/team-members-form/team-members-form.component"; - -@NgModule({ - declarations: [ - LayoutComponent, - AlertsComponent, - HeaderComponent, - NotificationTemplateComponent, - TagBackgroundPipe, - LicensingAlertsComponent, - NotificationsDrawerComponent, - ], - exports: [], - imports: [ - CommonModule, - AdministratorRoutingModule, - FormsModule, - ReactiveFormsModule, - NzSpinModule, - NzAffixModule, - NzAlertModule, - NzLayoutModule, - NzMenuModule, - NzTypographyModule, - NzToolTipModule, - NzDropDownModule, - NzIconModule, - NzBadgeModule, - NzAvatarModule, - NzBreadCrumbModule, - NzDrawerModule, - NzEmptyModule, - NzMessageServiceModule, - NzSpaceModule, - NzButtonModule, - FromNowPipe, - NgOptimizedImage, - SafeStringPipe, - NzTagModule, - NzSegmentedModule, - NzDividerModule, - NzSkeletonModule, - CdkVirtualScrollViewport, - CdkVirtualForOf, - CdkFixedSizeVirtualScroll, - NzResultModule, - NzListModule, - TeamMembersFormComponent - ] -}) -export class AdministratorModule { -} diff --git a/worklenz-frontend/src/app/administrator/components/avatars/avatars.component.html b/worklenz-frontend/src/app/administrator/components/avatars/avatars.component.html deleted file mode 100644 index 51aea011..00000000 --- a/worklenz-frontend/src/app/administrator/components/avatars/avatars.component.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/worklenz-frontend/src/app/administrator/components/avatars/avatars.component.spec.ts b/worklenz-frontend/src/app/administrator/components/avatars/avatars.component.spec.ts deleted file mode 100644 index 87c9b5bc..00000000 --- a/worklenz-frontend/src/app/administrator/components/avatars/avatars.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {AvatarsComponent} from './avatars.component'; - -describe('AvatarsComponent', () => { - let component: AvatarsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [AvatarsComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(AvatarsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/avatars/avatars.component.ts b/worklenz-frontend/src/app/administrator/components/avatars/avatars.component.ts deleted file mode 100644 index f749eb9f..00000000 --- a/worklenz-frontend/src/app/administrator/components/avatars/avatars.component.ts +++ /dev/null @@ -1,31 +0,0 @@ -import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; -import {NgForOf, NgSwitch, NgSwitchCase, NgTemplateOutlet} from "@angular/common"; -import {NzAvatarModule} from "ng-zorro-antd/avatar"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; - -import {InlineMember} from "@interfaces/api-models/inline-member"; -import {FirstCharUpperPipe} from "@pipes/first-char-upper.pipe"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; - -@Component({ - selector: 'worklenz-avatars', - templateUrl: './avatars.component.html', - styleUrls: ['./avatars.component.scss'], - imports: [ - NzAvatarModule, - NzToolTipModule, - NgForOf, - FirstCharUpperPipe, - NzBadgeModule, - NgSwitch, - NgTemplateOutlet, - NgSwitchCase - ], - standalone: true, - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class AvatarsComponent { - @Input() names: InlineMember[] = []; - @Input() avatarClass: string | null = null; - @Input() showDot = false; -} diff --git a/worklenz-frontend/src/app/administrator/components/clients-autocomplete/clients-autocomplete.component.html b/worklenz-frontend/src/app/administrator/components/clients-autocomplete/clients-autocomplete.component.html deleted file mode 100644 index 9dd1c39e..00000000 --- a/worklenz-frontend/src/app/administrator/components/clients-autocomplete/clients-autocomplete.component.html +++ /dev/null @@ -1,21 +0,0 @@ -
- - Client - - - - - - - Loading Data... - - - + ADD "{{ newName }}" - {{ item.name }} - - - - -
diff --git a/worklenz-frontend/src/app/administrator/components/clients-autocomplete/clients-autocomplete.component.spec.ts b/worklenz-frontend/src/app/administrator/components/clients-autocomplete/clients-autocomplete.component.spec.ts deleted file mode 100644 index 5714c3d7..00000000 --- a/worklenz-frontend/src/app/administrator/components/clients-autocomplete/clients-autocomplete.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ClientsAutocompleteComponent} from './clients-autocomplete.component'; - -describe('ClientsAutocompleteComponent', () => { - let component: ClientsAutocompleteComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ClientsAutocompleteComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ClientsAutocompleteComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/clients-autocomplete/clients-autocomplete.component.ts b/worklenz-frontend/src/app/administrator/components/clients-autocomplete/clients-autocomplete.component.ts deleted file mode 100644 index 2d5fceb4..00000000 --- a/worklenz-frontend/src/app/administrator/components/clients-autocomplete/clients-autocomplete.component.ts +++ /dev/null @@ -1,88 +0,0 @@ -import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; -import {FormBuilder, FormGroup, ReactiveFormsModule} from "@angular/forms"; -import {IClient} from "@interfaces/client"; -import {ClientsApiService} from "@api/clients-api.service"; -import {log_error} from "@shared/utils"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzAutocompleteModule} from "ng-zorro-antd/auto-complete"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NgForOf, NgIf} from "@angular/common"; -import {NzInputModule} from "ng-zorro-antd/input"; - -@Component({ - selector: 'worklenz-clients-autocomplete', - templateUrl: './clients-autocomplete.component.html', - styleUrls: ['./clients-autocomplete.component.scss'], - imports: [ - ReactiveFormsModule, - NzFormModule, - NzAutocompleteModule, - NzIconModule, - NgIf, - NgForOf, - NzInputModule - ], - standalone: true -}) -export class ClientsAutocompleteComponent implements OnInit { - @Output() nameChange: EventEmitter = new EventEmitter(); - @Input() name: string | null = null; - - form!: FormGroup; - - searching = false; - isNew = false; - - newName: string | null = null; - - clients: IClient[] = []; - - total = 0; - - constructor( - private api: ClientsApiService, - private fb: FormBuilder - ) { - this.form = this.fb.group({ - name: [] - }); - } - - async ngOnInit() { - this.form.controls["name"].setValue(this.name || null); - this.form.get('name')?.valueChanges.subscribe((value) => { - if (value) { - this.newName = value; - this.isNew = !this.clients.some((i) => i.name === value); - return; - } - - this.isNew = false; - }); - await this.get(); - } - - async get() { - try { - const res = await this.api.get(1, 5, null, null, this.form.value.name || null); - if (res.done) { - this.clients = res.body.data || []; - this.total = this.clients.length; - } - } catch (e) { - log_error(e); - } - } - - async search() { - this.emitChange(); - this.searching = true; - await this.get(); - this.searching = false; - } - - private emitChange() { - if (this.form.valid) - this.nameChange.emit(this.form.value.name.trim()); - } -} diff --git a/worklenz-frontend/src/app/administrator/components/convert-to-subtask-modal/convert-to-subtask-modal.component.html b/worklenz-frontend/src/app/administrator/components/convert-to-subtask-modal/convert-to-subtask-modal.component.html deleted file mode 100644 index 86d9c390..00000000 --- a/worklenz-frontend/src/app/administrator/components/convert-to-subtask-modal/convert-to-subtask-modal.component.html +++ /dev/null @@ -1,50 +0,0 @@ - - - -
- -
-
- -
-
-
-
- - diff --git a/worklenz-frontend/src/app/administrator/components/convert-to-subtask-modal/convert-to-subtask-modal.component.scss b/worklenz-frontend/src/app/administrator/components/convert-to-subtask-modal/convert-to-subtask-modal.component.scss deleted file mode 100644 index b48cd909..00000000 --- a/worklenz-frontend/src/app/administrator/components/convert-to-subtask-modal/convert-to-subtask-modal.component.scss +++ /dev/null @@ -1,86 +0,0 @@ -.scrollable { - max-height: 65vh; - overflow-y: auto; -} - -label span { - display: flex; -} - -.hover-bg-change:hover { - background-color: #f5f5f5; -} - -.spinner { - position: absolute; - z-index: 9; - top: 0; - bottom: 0; - left: 0; - right: 0; - background: rgb(255 255 255 / 30%); - display: flex; - align-items: center; - justify-content: center; -} - -.panel { - position: relative; - padding: 0 0; - background-color: white; - max-height: 0px; - overflow: hidden; - transition: max-height 0.1s ease-out; - border-right: 1px solid #f0f0f0; -} - -.panel.show { - transition: max-height 0.1s ease-out; - max-height: 100%; -} - -.panel-left-border { - position: absolute; - content: ''; - top: 0; - bottom: 0; - width: 3px; - z-index: 3; - border-bottom-left-radius: 4px; -} - -.btn { - width: max-content; - padding-left: 16px !important; - padding-right: 32px !important; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; - cursor: pointer; -} - -.btn.active { - border-bottom-left-radius: 0px; - border-bottom-right-radius: 0px; -} - -.btn .accordion-icon { - transform: rotate(0deg); -} - -.btn.active .accordion-icon { - transform: rotate(90deg); -} - -.single-task-cont { - border-top: 1px solid #f0f0f0; - padding-top: 3px; - padding-bottom: 3px; - height: auto; -} - -.border-bottom { - border-bottom: 1px solid #f0f0f0; - border-right: none !important; -} diff --git a/worklenz-frontend/src/app/administrator/components/convert-to-subtask-modal/convert-to-subtask-modal.component.spec.ts b/worklenz-frontend/src/app/administrator/components/convert-to-subtask-modal/convert-to-subtask-modal.component.spec.ts deleted file mode 100644 index 62c6dd5c..00000000 --- a/worklenz-frontend/src/app/administrator/components/convert-to-subtask-modal/convert-to-subtask-modal.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ConvertToSubtaskModalComponent} from './convert-to-subtask-modal.component'; - -describe('ConvertToSubtaskModalComponent', () => { - let component: ConvertToSubtaskModalComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ConvertToSubtaskModalComponent] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(ConvertToSubtaskModalComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/convert-to-subtask-modal/convert-to-subtask-modal.component.ts b/worklenz-frontend/src/app/administrator/components/convert-to-subtask-modal/convert-to-subtask-modal.component.ts deleted file mode 100644 index 4bbf1da6..00000000 --- a/worklenz-frontend/src/app/administrator/components/convert-to-subtask-modal/convert-to-subtask-modal.component.ts +++ /dev/null @@ -1,229 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, NgZone, OnDestroy, Renderer2} from '@angular/core'; -import {NzListModule} from "ng-zorro-antd/list"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NgForOf, NgIf} from "@angular/common"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzSpinModule} from "ng-zorro-antd/spin"; -import {NzModalModule} from 'ng-zorro-antd/modal'; -import {TaskListHashMapService} from "../../modules/task-list-v2/task-list-hash-map.service"; -import {ITaskListConfigV2, ITaskListGroup} from 'app/administrator/modules/task-list-v2/interfaces'; -import {TasksApiService} from '@api/tasks-api.service'; -import {TaskListV2Service} from 'app/administrator/modules/task-list-v2/task-list-v2.service'; -import {IServerResponse} from '@interfaces/api-models/server-response'; -import {NzInputModule} from 'ng-zorro-antd/input'; -import {SearchByNamePipe} from "../../../pipes/search-by-name.pipe"; -import {NzTagModule} from 'ng-zorro-antd/tag'; -import {NzToolTipModule} from 'ng-zorro-antd/tooltip'; -import {SocketEvents} from '@shared/socket-events'; -import {Socket} from 'ngx-socket-io'; -import { - SubtaskConvertService -} from 'app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/subtask-convert-service.service'; -import {Subject, takeUntil} from 'rxjs'; -import {IProjectTask} from '@interfaces/api-models/project-tasks-view-model'; -import { - ISubtaskConvertRequest -} from 'app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/interfaces/convert-subtask-request'; -import {NzMenuModule} from 'ng-zorro-antd/menu'; -import {FormsModule} from '@angular/forms'; -import {NzIconModule} from 'ng-zorro-antd/icon'; -import {KanbanV2Service} from 'app/administrator/modules/kanban-view-v2/kanban-view-v2.service'; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-convert-to-subtask-modal', - templateUrl: './convert-to-subtask-modal.component.html', - styleUrls: ['./convert-to-subtask-modal.component.scss'], - standalone: true, - imports: [ - NzListModule, - NzButtonModule, - NgIf, - NgForOf, - NzSkeletonModule, - NzSpinModule, - NzModalModule, - NzInputModule, - SearchByNamePipe, - NzTagModule, - NzToolTipModule, - NzMenuModule, - FormsModule, - NzIconModule - ], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ConvertToSubtaskModalComponent implements OnDestroy { - - projectId?: string | null; - searchText: string | null = null; - selectedTaskId: string | null = null; - - selectedTask?: IProjectTask | null; - - showConvertTasksModal = false; - loadingGroups = false; - converting = false; - - protected groupIds: string[] = []; - isExpanded: boolean[] = []; - groups: ITaskListGroup[] = []; - - private readonly destroy$ = new Subject(); - - constructor( - private readonly map: TaskListHashMapService, - private readonly api: TasksApiService, - private readonly service: TaskListV2Service, - private readonly subTaskConvertService: SubtaskConvertService, - private readonly cdr: ChangeDetectorRef, - private readonly socket: Socket, - private readonly ngZone: NgZone, - private readonly kanbanService: KanbanV2Service, - private readonly auth: AuthService, - ) { - this.subTaskConvertService.onConvertingSubtask - .pipe(takeUntil(this.destroy$)) - .subscribe(value => { - this.getTaskData(value); - }); - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } - - getTaskData(value: ISubtaskConvertRequest) { - this.projectId = value.projectId; - this.selectedTask = value.selectedTask; - void this.getGroups(); - } - - async getGroups() { - if (!this.projectId) return; - try { - this.map.deselectAll(); - this.loadingGroups = true; - const config = this.getConf(); - const res = await this.api.getTaskListV2(config) as IServerResponse; - if (res.done) { - this.groups = res.body; - this.groupIds = res.body.map(g => g.id); - await this.mapTasks(this.service.groups); - this.showConvertTasksModal = true; - } - this.loadingGroups = false; - } catch (e) { - this.loadingGroups = false; - } - this.cdr.detectChanges(); - } - - private getConf(parentTaskId?: string): ITaskListConfigV2 { - const config: ITaskListConfigV2 = { - id: this.projectId as string, - group: this.service.getCurrentGroup().value, - field: null, - order: null, - search: null, - statuses: null, - members: null, - projects: null, - isSubtasksInclude: false - }; - - if (parentTaskId) - config.parent_task = parentTaskId; - - return config; - } - - private mapTasks(groups: ITaskListGroup[]) { - for (const group of groups) { - this.map.registerGroup(group); - for (const task of group.tasks) { - if (task.start_date) task.start_date = new Date(task.start_date) as any; - if (task.end_date) task.end_date = new Date(task.end_date) as any; - } - } - setTimeout(() => { - // expanding panels after groups loaded - this.isExpanded = this.groups.map(() => true); - }, 50); - } - - async convertToSubTask(toGroupId: string, parentTaskId?: string) { - - const selectedTask = this.selectedTask; - if (!selectedTask) return; - - const groupBy = this.service.getCurrentGroup(); - if (groupBy.value === this.service.GROUP_BY_STATUS_VALUE) { - this.handleStatusChange(toGroupId, this.selectedTask?.id); - } else if (groupBy.value === this.service.GROUP_BY_PRIORITY_VALUE) { - this.handlePriorityChange(toGroupId, this.selectedTask?.id as string); - } - - try { - this.converting = true; - const res = await this.api.convertToSubTask( - selectedTask.id as string, - selectedTask.project_id as string, - parentTaskId as string, - groupBy.value, - toGroupId - ); - if (res.done) { - this.service.updateTaskGroup(res.body, false); - if (groupBy.value === this.service.GROUP_BY_PHASE_VALUE) - this.service.emitRefresh(); - } - this.reset(); - } catch (e) { - this.converting = false; - } - this.kanbanService.emitRefreshGroups(); - this.cdr.detectChanges(); - } - - handleStatusChange(statusId: string, taskId?: string) { - if (!taskId) return; - this.socket.emit(SocketEvents.TASK_STATUS_CHANGE.toString(), JSON.stringify({ - task_id: taskId, - status_id: statusId, - team_id: this.auth.getCurrentSession()?.team_id - })); - this.socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), taskId); - } - - handlePriorityChange(priorityId: string, taskId: string) { - this.socket.emit(SocketEvents.TASK_PRIORITY_CHANGE.toString(), JSON.stringify({ - task_id: taskId, - priority_id: priorityId - })); - } - - closeModal() { - this.showConvertTasksModal = false; - } - - reset() { - this.converting = false; - this.showConvertTasksModal = false; - this.loadingGroups = false; - this.groups = []; - this.groupIds = []; - this.searchText = null; - this.selectedTaskId = null; - } - - toggleGroup(event: MouseEvent, index: number) { - this.ngZone.runOutsideAngular(() => { - const target = event.target as Element; - if (!target) return; - this.isExpanded[index] = !this.isExpanded[index]; - }); - } - -} diff --git a/worklenz-frontend/src/app/administrator/components/import-tasks-template/import-tasks-template.component.html b/worklenz-frontend/src/app/administrator/components/import-tasks-template/import-tasks-template.component.html deleted file mode 100644 index 20ec30b7..00000000 --- a/worklenz-frontend/src/app/administrator/components/import-tasks-template/import-tasks-template.component.html +++ /dev/null @@ -1,48 +0,0 @@ - -
-
-
- - Select Template - - - - - - - - - Selected Tasks ({{tasks.length}}) -
    -
  • - - {{task.name}} -
  • -
- -
-
-
- - -
- - -
-
-
diff --git a/worklenz-frontend/src/app/administrator/components/import-tasks-template/import-tasks-template.component.spec.ts b/worklenz-frontend/src/app/administrator/components/import-tasks-template/import-tasks-template.component.spec.ts deleted file mode 100644 index 9e75ca39..00000000 --- a/worklenz-frontend/src/app/administrator/components/import-tasks-template/import-tasks-template.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ImportTasksTemplateComponent} from './import-tasks-template.component'; - -describe('ImportTasksTemplateComponent', () => { - let component: ImportTasksTemplateComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ImportTasksTemplateComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ImportTasksTemplateComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/import-tasks-template/import-tasks-template.component.ts b/worklenz-frontend/src/app/administrator/components/import-tasks-template/import-tasks-template.component.ts deleted file mode 100644 index 34d975b5..00000000 --- a/worklenz-frontend/src/app/administrator/components/import-tasks-template/import-tasks-template.component.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - NgZone, - OnDestroy, - Output, - ViewChild -} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzGridModule} from "ng-zorro-antd/grid"; -import {NzSelectComponent, NzSelectModule} from "ng-zorro-antd/select"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {TaskTemplatesService} from "@api/task-templates.service"; -import {ITaskTemplatesGetResponse} from "@interfaces/api-models/task-templates-get-response"; -import {FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators} from "@angular/forms"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {NzListModule} from "ng-zorro-antd/list"; -import {NzEmptyModule} from "ng-zorro-antd/empty"; -import {AppService} from "@services/app.service"; -import {NzDividerModule} from "ng-zorro-antd/divider"; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {DRAWER_ANIMATION_INTERVAL} from "@shared/constants"; - -@Component({ - selector: 'worklenz-import-tasks-template', - templateUrl: './import-tasks-template.component.html', - styleUrls: ['./import-tasks-template.component.scss'], - standalone: true, - imports: [CommonModule, NzDrawerModule, NzGridModule, NzSelectModule, NzButtonModule, NzFormModule, FormsModule, NzListModule, NzEmptyModule, ReactiveFormsModule, NzDividerModule, SafeStringPipe, NzTypographyModule], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ImportTasksTemplateComponent implements OnDestroy { - @ViewChild("templateSelect", {static: false}) selectTemplate!: NzSelectComponent; - - @Input() drawerVisible = false; - @Input() projectId: string | null = null; - - @Output() onImportDone = new EventEmitter(); - @Output() onCancel = new EventEmitter(); - - form!: FormGroup; - - selectedId: string | null = null; - - loadingTemplates = false; - loadingData = false; - importing = false; - - templates: ITaskTemplatesGetResponse[] = []; - tasks: IProjectTask[] = []; - - constructor( - private readonly api: TaskTemplatesService, - private readonly ngZone: NgZone, - private readonly app: AppService, - private readonly fb: FormBuilder, - private readonly cdr: ChangeDetectorRef - ) { - this.form = this.fb.group({ - template: [null, Validators.required], - }); - - this.form.get('template')?.valueChanges.subscribe(changes => { - if (changes) { - this.selectedId = changes; - this.templateSelected(); - } - }); - } - - ngOnDestroy() { - this.reset(); - this.cdr.markForCheck(); - } - - open(): void { - this.drawerVisible = true; - this.cdr.markForCheck(); - } - - closeDrawer(): void { - this.form.reset(); - this.tasks = []; - this.onCancel.emit(); - this.cdr.markForCheck(); - } - - onVisibleChange(event: boolean) { - if (event) { - void this.getTaskTemplate(); - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.selectTemplate?.focus(); - }, DRAWER_ANIMATION_INTERVAL) - }); - } - this.cdr.markForCheck(); - } - - async getTaskTemplate() { - try { - this.loadingTemplates = true; - const res = await this.api.get(); - if (res.done) { - this.templates = res.body; - this.loadingTemplates = false; - } - } catch (e) { - this.loadingTemplates = false; - } - - this.cdr.markForCheck(); - } - - async getTemplateData() { - if (!this.selectedId) return; - - try { - this.loadingData = true; - const res = await this.api.getById(this.selectedId); - if (res.done) { - this.tasks = res.body.tasks || []; - this.loadingData = false; - } - } catch (e) { - this.loadingData = false; - } - this.cdr.markForCheck(); - } - - templateSelected() { - void this.getTemplateData(); - } - - removeTask(index: number) { - if (this.tasks.length > 1) { - this.tasks.splice(index, 1); - } else { - this.tasks = []; - } - } - - validateForm() { - for (const controlName in this.form.controls) { - this.form.controls[controlName].updateValueAndValidity(); - } - this.cdr.markForCheck(); - } - - async importFromTemplate() { - if (!this.projectId) return; - - try { - this.validateForm(); - if (this.form.invalid) { - this.form.markAsTouched(); - return; - } - if (this.tasks.length) { - this.importing = true; - const res = await this.api.import(this.projectId, this.tasks); - if (res.done) { - this.api.emitOnImport(); - this.onImportDone.emit(); - this.reset(); - this.drawerVisible = false; - } - this.importing = false; - } else { - this.app.notify("Incomplete request!", "No tasks to import", false); - } - } catch (e) { - this.importing = false; - } - this.cdr.markForCheck(); - } - - private reset() { - this.tasks = []; - this.selectedId = null; - this.form.reset(); - this.cdr.markForCheck(); - } -} diff --git a/worklenz-frontend/src/app/administrator/components/job-titles-autocomplete/job-titles-autocomplete.component.html b/worklenz-frontend/src/app/administrator/components/job-titles-autocomplete/job-titles-autocomplete.component.html deleted file mode 100644 index 2023c1f7..00000000 --- a/worklenz-frontend/src/app/administrator/components/job-titles-autocomplete/job-titles-autocomplete.component.html +++ /dev/null @@ -1,22 +0,0 @@ -
- - Job Title - - - - - - - Loading Data... - - - + ADD "{{ newTitle }}" - {{ item.name }} - - - - - -
diff --git a/worklenz-frontend/src/app/administrator/components/job-titles-autocomplete/job-titles-autocomplete.component.scss b/worklenz-frontend/src/app/administrator/components/job-titles-autocomplete/job-titles-autocomplete.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/components/job-titles-autocomplete/job-titles-autocomplete.component.spec.ts b/worklenz-frontend/src/app/administrator/components/job-titles-autocomplete/job-titles-autocomplete.component.spec.ts deleted file mode 100644 index 404fa219..00000000 --- a/worklenz-frontend/src/app/administrator/components/job-titles-autocomplete/job-titles-autocomplete.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {JobTitlesAutocompleteComponent} from './job-titles-autocomplete.component'; - -describe('JobTitlesAutocompleteComponent', () => { - let component: JobTitlesAutocompleteComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [JobTitlesAutocompleteComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(JobTitlesAutocompleteComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/job-titles-autocomplete/job-titles-autocomplete.component.ts b/worklenz-frontend/src/app/administrator/components/job-titles-autocomplete/job-titles-autocomplete.component.ts deleted file mode 100644 index 279b0bdb..00000000 --- a/worklenz-frontend/src/app/administrator/components/job-titles-autocomplete/job-titles-autocomplete.component.ts +++ /dev/null @@ -1,100 +0,0 @@ -import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; -import {FormBuilder, FormGroup, ReactiveFormsModule} from '@angular/forms'; - -import {JobTitlesApiService} from '@api/job-titles-api.service'; -import {IJobTitle} from '@interfaces/job-title'; -import {log_error} from "@shared/utils"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NgForOf, NgIf} from "@angular/common"; -import {NzAutocompleteModule} from "ng-zorro-antd/auto-complete"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzIconModule} from "ng-zorro-antd/icon"; - -@Component({ - selector: 'worklenz-job-titles-autocomplete', - templateUrl: './job-titles-autocomplete.component.html', - styleUrls: ['./job-titles-autocomplete.component.scss'], - imports: [ - NzInputModule, - NgIf, - ReactiveFormsModule, - NgForOf, - NzAutocompleteModule, - NzFormModule, - NzIconModule - ], - standalone: true -}) -export class JobTitlesAutocompleteComponent implements OnInit { - @Output() titleChange: EventEmitter = new EventEmitter(); - @Input() title: string | null = null; - @Input() placeholder = "Job Title"; - - form!: FormGroup; - - @Input() loading = false; - @Output() loadingChange: EventEmitter = new EventEmitter(); - searching = false; - isNew = false; - - newTitle: string | null = null; - - jobTitles: IJobTitle[] = []; - - total = 0; - - constructor( - private api: JobTitlesApiService, - private fb: FormBuilder - ) { - this.form = this.fb.group({ - name: [null] - }); - } - - async ngOnInit() { - this.form.controls['name'].setValue(this.title || null); - this.form.get('name')?.valueChanges.subscribe((value) => { - if (value) { - this.newTitle = value; - this.isNew = !this.jobTitles.some((i) => i.name === value); - return; - } - - this.isNew = false; - }); - await this.get(); - } - - async get() { - try { - this.setLoading(true); - const res = await this.api.get(1, 5, null, null, this.form.value.name || null); - if (res.done) { - this.jobTitles = res.body.data || []; - this.total = this.jobTitles.length; - } - this.setLoading(false); - } catch (e) { - this.setLoading(false); - log_error(e); - } - } - - async search() { - this.emitChange(); - this.searching = true; - await this.get(); - this.searching = false; - } - - private setLoading(loading: boolean) { - this.loading = loading; - this.loadingChange.emit(this.loading); - } - - private emitChange() { - if (this.form.valid) - this.titleChange.emit(this.form.value.name.trim()); - } -} diff --git a/worklenz-frontend/src/app/administrator/components/na/na.component.html b/worklenz-frontend/src/app/administrator/components/na/na.component.html deleted file mode 100644 index 86b5544e..00000000 --- a/worklenz-frontend/src/app/administrator/components/na/na.component.html +++ /dev/null @@ -1 +0,0 @@ -- diff --git a/worklenz-frontend/src/app/administrator/components/na/na.component.scss b/worklenz-frontend/src/app/administrator/components/na/na.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/components/na/na.component.spec.ts b/worklenz-frontend/src/app/administrator/components/na/na.component.spec.ts deleted file mode 100644 index 2cf202f0..00000000 --- a/worklenz-frontend/src/app/administrator/components/na/na.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { NaComponent } from './na.component'; - -describe('NaComponent', () => { - let component: NaComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [NaComponent] - }); - fixture = TestBed.createComponent(NaComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/na/na.component.ts b/worklenz-frontend/src/app/administrator/components/na/na.component.ts deleted file mode 100644 index 27d0b9d8..00000000 --- a/worklenz-frontend/src/app/administrator/components/na/na.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import {ChangeDetectionStrategy, Component} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {NzTypographyModule} from "ng-zorro-antd/typography"; - -@Component({ - selector: 'worklenz-na', - standalone: true, - imports: [CommonModule, NzTypographyModule], - templateUrl: './na.component.html', - styleUrls: ['./na.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class NaComponent { - -} diff --git a/worklenz-frontend/src/app/administrator/components/personal-overview/personal-overview.component.html b/worklenz-frontend/src/app/administrator/components/personal-overview/personal-overview.component.html deleted file mode 100644 index c637baf3..00000000 --- a/worklenz-frontend/src/app/administrator/components/personal-overview/personal-overview.component.html +++ /dev/null @@ -1,53 +0,0 @@ -
-
- - - - - - {{item.project_name}} - - - {{item.priority}}   - - - - - - - - - - - - - - - - - {{item.project_name}} - - - {{getTimestamp(item.created_at)}} - - - - -
-
- - - - - - {{item.project_name}} - - - {{getTimestamp(item.created_at)}} - - - - - -
-
diff --git a/worklenz-frontend/src/app/administrator/components/personal-overview/personal-overview.component.scss b/worklenz-frontend/src/app/administrator/components/personal-overview/personal-overview.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/components/personal-overview/personal-overview.component.spec.ts b/worklenz-frontend/src/app/administrator/components/personal-overview/personal-overview.component.spec.ts deleted file mode 100644 index cb511de5..00000000 --- a/worklenz-frontend/src/app/administrator/components/personal-overview/personal-overview.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {PersonalOverviewComponent} from './personal-overview.component'; - -describe('PersonalOverviewComponent', () => { - let component: PersonalOverviewComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [PersonalOverviewComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(PersonalOverviewComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/personal-overview/personal-overview.component.ts b/worklenz-frontend/src/app/administrator/components/personal-overview/personal-overview.component.ts deleted file mode 100644 index 37418d7e..00000000 --- a/worklenz-frontend/src/app/administrator/components/personal-overview/personal-overview.component.ts +++ /dev/null @@ -1,93 +0,0 @@ -import {Component, OnInit} from '@angular/core'; -import {PersonalOverviewService} from '@api/personal-overview.service'; -import moment from 'moment'; -import {NzListModule} from "ng-zorro-antd/list"; -import {NzCardModule} from "ng-zorro-antd/card"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NgForOf, NgIf} from "@angular/common"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzIconModule} from "ng-zorro-antd/icon"; - -@Component({ - selector: 'worklenz-personal-project-insights-member-overview', - templateUrl: './personal-overview.component.html', - styleUrls: ['./personal-overview.component.scss'], - imports: [ - NzListModule, - NzCardModule, - NzSpaceModule, - NgForOf, - NgIf, - NzToolTipModule, - NzIconModule - ], - standalone: true -}) -export class PersonalOverviewComponent implements OnInit { - loading = false; - activity_log_loading = false; - tasks_due_today_loading = false; - tasks_due_loading = false; - - activityLog: any = []; - tasksDueToday: any = []; - tasksRemaining: any = []; - - constructor( - private personalOverviewService: PersonalOverviewService - ) { - } - - ngOnInit(): void { - this.getActivityLog().then(r => r); - this.getTasksDueToday().then(r => r); - this.getTasksRemaining().then(r => r); - } - - async getActivityLog() { - try { - this.activity_log_loading = true; - const res = await this.personalOverviewService.getActivityLog(); - if (res.done) { - this.activityLog = res.body; - } - this.activity_log_loading = false; - } catch (e) { - this.activity_log_loading = false; - } - } - - async getTasksDueToday() { - try { - this.tasks_due_today_loading = true; - const res = await this.personalOverviewService.getTasksDueToday(); - if (res.done) { - this.tasksDueToday = res.body; - } - this.tasks_due_today_loading = false; - } catch (e) { - this.tasks_due_today_loading = false; - } - } - - async getTasksRemaining() { - try { - this.tasks_due_loading = true; - const res = await this.personalOverviewService.getRemainingTasks(); - if (res.done) { - this.tasksRemaining = res.body; - } - this.tasks_due_loading = false; - } catch (e) { - this.tasks_due_loading = false; - } - } - - getTimestamp(created_at: string) { - return moment(created_at).fromNow(); - } - - getSpecificTime(created_at: string) { - return moment(created_at).format('YYYY-MM-DD hh:mm:ss A'); - } -} diff --git a/worklenz-frontend/src/app/administrator/components/project-categories-autocomplete/project-categories-autocomplete.component.html b/worklenz-frontend/src/app/administrator/components/project-categories-autocomplete/project-categories-autocomplete.component.html deleted file mode 100644 index 23ceb95a..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-categories-autocomplete/project-categories-autocomplete.component.html +++ /dev/null @@ -1,32 +0,0 @@ - - Category - - - - - - - - - - {{item.name}} - - - - - - - - - diff --git a/worklenz-frontend/src/app/administrator/components/project-categories-autocomplete/project-categories-autocomplete.component.scss b/worklenz-frontend/src/app/administrator/components/project-categories-autocomplete/project-categories-autocomplete.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/components/project-categories-autocomplete/project-categories-autocomplete.component.spec.ts b/worklenz-frontend/src/app/administrator/components/project-categories-autocomplete/project-categories-autocomplete.component.spec.ts deleted file mode 100644 index c8e02e6c..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-categories-autocomplete/project-categories-autocomplete.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ProjectCategoriesAutocompleteComponent} from './project-categories-autocomplete.component'; - -describe('ProjectCategoriesAutocompleteComponent', () => { - let component: ProjectCategoriesAutocompleteComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ProjectCategoriesAutocompleteComponent] - }); - fixture = TestBed.createComponent(ProjectCategoriesAutocompleteComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/project-categories-autocomplete/project-categories-autocomplete.component.ts b/worklenz-frontend/src/app/administrator/components/project-categories-autocomplete/project-categories-autocomplete.component.ts deleted file mode 100644 index 97cfad7e..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-categories-autocomplete/project-categories-autocomplete.component.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - Input, - NgZone, - OnInit, - Output, - ViewChild -} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzGridModule} from "ng-zorro-antd/grid"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzSelectModule} from "ng-zorro-antd/select"; -import {NzWaveModule} from "ng-zorro-antd/core/wave"; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; -import {FormsModule, ReactiveFormsModule} from "@angular/forms"; -import {IProjectCategory} from "@interfaces/project-category"; -import {log_error} from "@shared/utils"; -import {ProjectCategoriesApiService} from "@api/project-categories-api.service"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {ProjectFormService} from "@services/project-form-service.service"; - -@Component({ - selector: 'worklenz-project-categories-autocomplete', - standalone: true, - imports: [ - CommonModule, - NzButtonModule, - NzFormModule, - NzGridModule, - NzIconModule, - NzSelectModule, - NzWaveModule, - SafeStringPipe, - ReactiveFormsModule, - FormsModule, - NzInputModule - ], - templateUrl: './project-categories-autocomplete.component.html', - styleUrls: ['./project-categories-autocomplete.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectCategoriesAutocompleteComponent implements OnInit { - @ViewChild("nameInput", {static: false}) nameInput!: ElementRef; - - @Input() categoryId: string | null = null; - @Output() categoryIdChange = new EventEmitter(); - - @Input() disabled = false; - - loading = false; - creating = false; - showCategoryInput = false; - categories: IProjectCategory[] = []; - newCategoryName: string | null = null; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: ProjectCategoriesApiService, - private readonly ngZone: NgZone, - ) { - } - - ngOnInit() { - void this.get(); - } - - async get() { - try { - this.loading = true; - const res = await this.api.get(); - if (res.done) { - this.categories = res.body; - } - this.loading = false; - } catch (e) { - this.loading = false; - log_error(e); - } - - this.cdr.markForCheck(); - } - - newCategory() { - if (this.showCategoryInput) return; - this.newCategoryName = null; - this.showCategoryInput = true; - this.focusInput(); - this.cdr.markForCheck(); - } - - onCategoryChange(categoryId: string) { - this.categoryId = categoryId; - this.categoryIdChange.emit(this.categoryId); - } - - resetInputMode() { - this.showCategoryInput = false; - this.newCategoryName = null; - } - - async create() { - if (!this.newCategoryName?.trim() || this.creating) return; - try { - this.creating = true; - const body = { - name: this.newCategoryName - }; - const res = await this.api.create(body); - if (res.done) { - await this.get(); - this.handleCreate(res.body); - } - this.creating = false; - } catch (e) { - this.creating = false; - } - - this.cdr.markForCheck(); - } - - private handleCreate(category: IProjectCategory) { - this.onCategoryChange(category.id as string); - this.resetInputMode(); - } - - private focusInput() { - this.ngZone.runOutsideAngular(() => { - // wait for html to display - setTimeout(() => { - this.nameInput.nativeElement?.focus(); - }); - }); - } - -} diff --git a/worklenz-frontend/src/app/administrator/components/project-folders-autocomplete/project-folders-autocomplete.component.html b/worklenz-frontend/src/app/administrator/components/project-folders-autocomplete/project-folders-autocomplete.component.html deleted file mode 100644 index 964b18fc..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-folders-autocomplete/project-folders-autocomplete.component.html +++ /dev/null @@ -1,21 +0,0 @@ -
- - Folder - - - - - - + Add "{{ searchValue }}" - - - - {{item.name}} - - - - - - -
diff --git a/worklenz-frontend/src/app/administrator/components/project-folders-autocomplete/project-folders-autocomplete.component.scss b/worklenz-frontend/src/app/administrator/components/project-folders-autocomplete/project-folders-autocomplete.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/components/project-folders-autocomplete/project-folders-autocomplete.component.spec.ts b/worklenz-frontend/src/app/administrator/components/project-folders-autocomplete/project-folders-autocomplete.component.spec.ts deleted file mode 100644 index 7d502f06..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-folders-autocomplete/project-folders-autocomplete.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ProjectFoldersAutocompleteComponent} from './project-folders-autocomplete.component'; - -describe('ProjectFoldersAutocompleteComponent', () => { - let component: ProjectFoldersAutocompleteComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ProjectFoldersAutocompleteComponent] - }); - fixture = TestBed.createComponent(ProjectFoldersAutocompleteComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/project-folders-autocomplete/project-folders-autocomplete.component.ts b/worklenz-frontend/src/app/administrator/components/project-folders-autocomplete/project-folders-autocomplete.component.ts deleted file mode 100644 index 81982fe5..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-folders-autocomplete/project-folders-autocomplete.component.ts +++ /dev/null @@ -1,91 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit} from '@angular/core'; -import {CommonModule, NgForOf, NgIf} from '@angular/common'; -import {FormBuilder, FormGroup, ReactiveFormsModule} from "@angular/forms"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzAutocompleteModule} from "ng-zorro-antd/auto-complete"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {log_error} from "@shared/utils"; -import {ProjectFoldersApiService} from "@api/project-folders-api.service"; -import {IProjectFolder} from "@interfaces/project-folder"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; - -@Component({ - selector: 'worklenz-project-folders-autocomplete', - templateUrl: './project-folders-autocomplete.component.html', - styleUrls: ['./project-folders-autocomplete.component.scss'], - imports: [ - CommonModule, - ReactiveFormsModule, - NzFormModule, - NzAutocompleteModule, - NzIconModule, - NgIf, - NgForOf, - NzInputModule, - SearchByNamePipe - ], - standalone: true, - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectFoldersAutocompleteComponent implements OnInit { - form!: FormGroup; - isNew = false; - searchValue: string | null = null; - folders: IProjectFolder[] = []; - - constructor( - private readonly api: ProjectFoldersApiService, - private readonly fb: FormBuilder, - private readonly cdr: ChangeDetectorRef - ) { - this.form = this.fb.group({ - folder: [] - }); - } - - async ngOnInit() { - const control = this.form.controls["folder"]; - control.valueChanges.subscribe((value) => { - this.searchValue = value; - if (value) { - this.isNew = !this.folders.some((i) => i.name === value); - return; - } - - this.isNew = false; - }); - await this.get(); - } - - addFolder() { - void this.create(); - } - - private async get() { - try { - const res = await this.api.get(); - if (res.done) { - this.folders = res.body || []; - } - } catch (e) { - log_error(e); - } - this.cdr.markForCheck(); - } - - private async create() { - if (!this.searchValue) return; - try { - const body = { - name: this.searchValue - }; - const res = await this.api.create(body); - if (res.done) { - void this.get(); - } - } catch (e) { - log_error(e); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/components/project-form-modal/project-form-modal.component.html b/worklenz-frontend/src/app/administrator/components/project-form-modal/project-form-modal.component.html deleted file mode 100644 index efc20233..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-form-modal/project-form-modal.component.html +++ /dev/null @@ -1,280 +0,0 @@ - - - - - -
- - - Name - - - - - - - Key - - - - - - - Project color -   - - - -
    -
  • -   - -
  • -
-
- - - - - - - -
- - - Status - - - - - {{item.name}} - - - - - - - Health - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Notes - - - - - - - - - - Project Manager -
-
- - - {{projectManager.name}} - - - - - - - -
- - -
    -
  • - -
  • -
  • -
    - -
    - {{item.name}} - - {{item.email}} (Pending - Invitation) - -
    -
    -
  • -
-
-
-
-
- -
- - -
-
- - Start date - - - - -
-
- - End date - - - - -
-
- - Estimated working days - - - - -
-
- - Estimated man days - - - - -
-
- - Hours per day - - - - -
-
-
- - - - - -
- - - -
-
- - Created {{model.created_at | fromNow}} by {{model.project_owner}} - -
-
- - Updated {{model.updated_at | fromNow}} - -
-
-
-
-
-
- - diff --git a/worklenz-frontend/src/app/administrator/components/project-form-modal/project-form-modal.component.scss b/worklenz-frontend/src/app/administrator/components/project-form-modal/project-form-modal.component.scss deleted file mode 100644 index f004fcc7..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-form-modal/project-form-modal.component.scss +++ /dev/null @@ -1,36 +0,0 @@ -.remove-icon { - color: #00000040; - background: #fff; - opacity: 0; - cursor: pointer; - transition: 0.25s all; - &:hover { - color: #00000073; - } -} - -.manager-input { - border-width: 1px; - border-style: solid; - border-color: transparent; - border-radius: 6px; - transition: 0.25s all; - margin-left: 8px; - min-height: 32px; - - &:hover { - border-color: #40a9ff; - & .remove-icon { - opacity: 1; - } - } - - &.highlight { - border-color: #40a9ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); - } -} - -.z-top { - z-index: 1; -} diff --git a/worklenz-frontend/src/app/administrator/components/project-form-modal/project-form-modal.component.spec.ts b/worklenz-frontend/src/app/administrator/components/project-form-modal/project-form-modal.component.spec.ts deleted file mode 100644 index 27cd8118..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-form-modal/project-form-modal.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ProjectFormModalComponent} from './project-form-modal.component'; - -describe('ProjectFormModalComponent', () => { - let component: ProjectFormModalComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ProjectFormModalComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ProjectFormModalComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/project-form-modal/project-form-modal.component.ts b/worklenz-frontend/src/app/administrator/components/project-form-modal/project-form-modal.component.ts deleted file mode 100644 index 951369f1..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-form-modal/project-form-modal.component.ts +++ /dev/null @@ -1,594 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - NgZone, - Output, - ViewChild -} from '@angular/core'; -import {FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators} from "@angular/forms"; -import {ProjectsDefaultColorCodes} from "@shared/constants"; -import {ITeamMemberViewModel} from "@interfaces/api-models/team-members-get-response"; -import {ITeamMember} from "@interfaces/team-member"; -import {ITask} from "@interfaces/task"; -import {ProjectsApiService} from "@api/projects-api.service"; -import {AppService} from "@services/app.service"; -import {TeamMembersApiService} from "@api/team-members-api.service"; -import {IProject} from "@interfaces/project"; -import {dispatchProjectChange} from "@shared/events"; -import {UtilsService} from "@services/utils.service"; -import {log_error} from "@shared/utils"; -import {ProjectStatusesApiService} from "@api/project-statuses-api.service"; -import {IProjectStatus} from "@interfaces/project-status"; -import {NzSelectModule} from "ng-zorro-antd/select"; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzTagModule} from "ng-zorro-antd/tag"; -import {NzSpinModule} from "ng-zorro-antd/spin"; -import {ClientsAutocompleteComponent} from "../clients-autocomplete/clients-autocomplete.component"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {DatePipe, NgForOf, NgIf} from "@angular/common"; -import {NzPopconfirmModule} from "ng-zorro-antd/popconfirm"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {TeamMembersFormComponent} from "../team-members-form/team-members-form.component"; -import {NzDatePickerModule} from "ng-zorro-antd/date-picker"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; -import {FromNowPipe} from "@pipes/from-now.pipe"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {IProjectViewModel} from "@interfaces/api-models/project-view-model"; -import {NzDividerModule} from "ng-zorro-antd/divider"; -import {AuthService} from "@services/auth.service"; -import {NzAlertModule} from "ng-zorro-antd/alert"; -import {ProjectFoldersApiService} from "@api/project-folders-api.service"; -import {IProjectFolder} from "@interfaces/project-folder"; -import { - ProjectFoldersAutocompleteComponent -} from "@admin/components/project-folders-autocomplete/project-folders-autocomplete.component"; -import { - ProjectsFolderFormDrawerService -} from "../../projects/projects/projects-folder-form-drawer/projects-folder-form-drawer.service"; -import { - ProjectCategoriesAutocompleteComponent -} from "@admin/components/project-categories-autocomplete/project-categories-autocomplete.component"; -import {ProjectFormService} from "@services/project-form-service.service"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import {IProjectHealth} from "@interfaces/project-health"; -import {ProjectHealthsApiService} from "@api/project-healths-api.service"; -import moment from "moment"; -import {NzInputNumberModule} from "ng-zorro-antd/input-number"; -import {AvatarsComponent} from "@admin/components/avatars/avatars.component"; -import {FirstCharUpperPipe} from "@pipes/first-char-upper.pipe"; -import {NzAvatarModule} from "ng-zorro-antd/avatar"; -import {NzCheckboxModule} from "ng-zorro-antd/checkbox"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {ProjectsService} from "../../projects/projects.service"; - -@Component({ - selector: 'worklenz-project-form-modal', - templateUrl: './project-form-modal.component.html', - styleUrls: ['./project-form-modal.component.scss'], - imports: [ - NzSelectModule, - NzDrawerModule, - NzTagModule, - NzSpinModule, - ClientsAutocompleteComponent, - ReactiveFormsModule, - NzInputModule, - NzButtonModule, - NgIf, - NzPopconfirmModule, - NzFormModule, - NgForOf, - NzIconModule, - TeamMembersFormComponent, - NzDatePickerModule, - NzDropDownModule, - SafeStringPipe, - DatePipe, - FromNowPipe, - NzToolTipModule, - NzTypographyModule, - NzDividerModule, - NzAlertModule, - ProjectFoldersAutocompleteComponent, - ProjectCategoriesAutocompleteComponent, - NzBadgeModule, - NzInputNumberModule, - AvatarsComponent, - FirstCharUpperPipe, - NzAvatarModule, - NzCheckboxModule, - SearchByNamePipe, - FormsModule - ], - standalone: true, - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectFormModalComponent { - @ViewChild('projectName', {static: false}) projectName!: ElementRef; - @ViewChild('memberSearchInput', {static: false}) memberSearchInput!: ElementRef; - @ViewChild('projectManagerSelector', {static: false}) projectManagerSelector!: ElementRef; - @ViewChild('outsideClicker', {static: false}) outsideClicker!: ElementRef; - - form!: FormGroup; - - @Output() onCreate = new EventEmitter(); - @Output() onUpdate = new EventEmitter(); - @Output() onProjectManagerUpdate = new EventEmitter(); - @Output() onDelete = new EventEmitter(); - - readonly COLOR_CODES = ProjectsDefaultColorCodes; - - show = false; - edit = false; - loading = false; - searching = false; - showTeamMemberModal = false; - updatingProject = false; - deletingProject = false; - loadingTeamMembers = false; - loadingProjStatuses = false; - loadingProjHealths = false; - isMember = false; - isManager = false; - loadingFolders = false; - - clientName: string | null = null; - projectManager: ITeamMemberViewModel | null = null; - projectId: string | null = null; - - teamMembers: ITeamMemberViewModel[] = []; - projectMembers: ITeamMemberViewModel[] = []; - removedMembersList: ITeamMember[] = []; - removedTasks: ITask[] = []; - newTasks: ITask[] = []; - statuses: IProjectStatus[] = []; - folders: IProjectFolder[] = []; - healths: IProjectHealth[] = []; - - public searchingName: string | null = null; - model: IProjectViewModel = {}; - - get categoryId() { - return this.form.controls["category_id"].value || null; - } - - set categoryId(value: string | null) { - this.form.controls["category_id"].setValue(value); - } - - get startDate() { - return this.form.value.start_date || null; - } - - get endDate() { - return this.form.value.end_date || null; - } - - get title() { - return this.projectId ? "Update Project" : "Create Project"; - } - - get submitButtonText() { - return this.projectId ? "Save Changes" : "Create"; - } - - get activeColorCode() { - return this.form.controls['color_code'].value; - } - - constructor( - private readonly api: ProjectsApiService, - private readonly fb: FormBuilder, - private readonly membersApi: TeamMembersApiService, - private readonly app: AppService, - private readonly statusesApi: ProjectStatusesApiService, - private readonly auth: AuthService, - private readonly ngZone: NgZone, - private readonly foldersApi: ProjectFoldersApiService, - private readonly folderFormService: ProjectsFolderFormDrawerService, - private readonly cdr: ChangeDetectorRef, - public readonly utils: UtilsService, - private readonly projectFormService: ProjectFormService, - private readonly healthsApi: ProjectHealthsApiService, - private readonly projectsService: ProjectsService - ) { - this.createForm(); - } - - private createForm() { - this.form = this.fb.group({ - name: [null, [Validators.required]], - key: [null, [Validators.max(5)]], - notes: [null, []], - start_date: [], - project_manager: [null, []], - end_date: [], - status_id: [], - health_id: [], - folder_id: [], - category_id: [], - color_code: [ProjectsDefaultColorCodes[1], [Validators.required]], - working_days: [0, [Validators.required]], - man_days: [0, [Validators.required]], - hours_per_day: [8, [Validators.required]], - // Internal use - _select_team_member_input: [null, []] - }); - - if (this.isMember) { - this.form.disable(); - } - - this.form.controls["_select_team_member_input"] - .valueChanges.subscribe((value) => { - this.searchingName = value; - void this.searchMembers(); - }); - } - - reset() { - this.clientName = null; - this.projectId = null; - this.projectManager = null; - this.teamMembers = []; - this.projectMembers = []; - this.removedMembersList = []; - this.removedTasks = []; - this.newTasks = []; - this.deletingProject = false; - this.updatingProject = false; - } - - handleClose() { - this.reset(); - this.show = false; - } - - isOwnerOrAdmin() { - return this.auth.getCurrentSession()?.owner || this.auth.getCurrentSession()?.is_admin; - } - - isProjectManager() { - if (this.projectsService.projectOwnerTeamMemberId) return this.auth.getCurrentSession()?.team_member_id === this.projectsService.projectOwnerTeamMemberId; - return false; - } - - public async open(id?: string, edit = false) { - this.isMember = !this.isOwnerOrAdmin() && !this.isProjectManager(); - - this.show = true; - this.edit = edit; - this.createForm(); - - void this.getProjectStatuses(); - void this.getProjectHealths(); - - if (id) { - this.projectId = id; - void this.get(this.projectId); - } - - void this.getTeamMembers(); - } - - isLoading() { - return this.loadingTeamMembers; - } - - async getFolders() { - try { - this.loadingFolders = true; - const res = await this.foldersApi.get(); - if (res.done) { - this.folders = res.body; - } - this.loadingFolders = false; - } catch (e) { - this.loadingFolders = false; - log_error(e); - } - - this.cdr.markForCheck(); - } - - async getTeamMembers() { - try { - this.loadingTeamMembers = true; - const res = await this.membersApi.get(1, 5, null, null, this.memberSearchInput ? this.memberSearchInput.nativeElement.value : null, true); - if (res.done) { - this.teamMembers = res.body.data || []; - this.teamMembers = this.teamMembers.filter(m => m.active); - this.teamMembers.sort((a, b) => { - return Number(a.pending_invitation) - Number(b.pending_invitation); - }); - } - this.loadingTeamMembers = false; - } catch (e) { - this.loadingTeamMembers = false; - log_error(e); - } - - this.cdr.markForCheck(); - } - - async getProjectStatuses() { - try { - this.loadingProjStatuses = true; - const res = await this.statusesApi.get(); - if (res.done) { - this.statuses = res.body; - const defaultStatus = this.statuses.find(s => s.is_default); - // Set default status in create mode - if (!this.projectId && defaultStatus && defaultStatus.id) - this.form.controls["status_id"].setValue(defaultStatus.id); - } - this.loadingProjStatuses = false; - } catch (e) { - this.loadingProjStatuses = false; - log_error(e); - } - - this.cdr.markForCheck(); - } - - async getProjectHealths() { - try { - this.loadingProjHealths = true; - const res = await this.healthsApi.get(); - if (res) { - this.healths = res.body; - const defaultHealth = this.healths.find(s => s.is_default); - // Set default health in create mode - if (!this.projectId && defaultHealth && defaultHealth.id) - this.form.controls["health_id"].setValue(defaultHealth.id); - } - this.loadingProjHealths = false; - } catch (e) { - this.loadingProjHealths = false; - log_error(e); - } - this.cdr.markForCheck(); - } - - async delete() { - if (!this.projectId) return; - try { - this.deletingProject = true; - const res = await this.api.delete(this.projectId); - if (res.done) { - this.handleClose(); - this.onDelete?.emit(); - } - this.deletingProject = false; - } catch (e) { - this.deletingProject = false; - log_error(e); - } - - this.cdr.markForCheck(); - } - - async create() { - try { - this.updatingProject = true; - const body = { - name: this.form.controls['name'].value, - client_name: this.clientName, - notes: this.form.controls['notes'].value, - project_manager: this.projectManager, - color_code: this.form.controls['color_code'].value, - status_id: this.form.controls['status_id'].value, - health_id: this.form.controls['health_id'].value, - start_date: this.form.controls["start_date"].value, - end_date: this.form.controls["end_date"].value, - folder_id: this.form.controls["folder_id"].value, - category_id: this.form.controls["category_id"].value, - working_days: this.form.controls["working_days"].value, - man_days: this.form.controls["man_days"].value, - hours_per_day: this.form.controls["hours_per_day"].value - }; - const res = await this.api.create(body); - if (res.done) { - this.handleClose(); - this.onCreate.emit(res.body); - dispatchProjectChange(); - } - this.updatingProject = false; - } catch (e) { - this.updatingProject = false; - log_error(e); - } - - this.cdr.markForCheck(); - } - - async update(id: string) { - try { - this.updatingProject = true; - const body = { - name: this.form.controls['name'].value, - client_name: this.clientName, - notes: this.form.controls['notes'].value, - project_manager: this.projectManager, - key: this.form.controls['key'].value, - color_code: this.form.controls['color_code'].value, - status_id: this.form.controls['status_id'].value, - health_id: this.form.controls['health_id'].value, - start_date: this.form.controls["start_date"].value, - end_date: this.form.controls["end_date"].value, - folder_id: this.form.controls["folder_id"].value, - category_id: this.form.controls["category_id"].value, - working_days: this.form.controls["working_days"].value, - man_days: this.form.controls["man_days"].value, - hours_per_day: this.form.controls["hours_per_day"].value - }; - - const res = await this.api.update(id, body); - if (res.done) { - this.handleClose(); - this.onUpdate.emit(res.body); - dispatchProjectChange(); - this.projectFormService.emitProjectUpdate(); - return true; - } - this.updatingProject = false; - } catch (e) { - this.updatingProject = false; - log_error(e); - } - - this.cdr.markForCheck(); - return false; - } - - async get(id: string | undefined) { - if (!id) return; - try { - this.loading = true; - const res = await this.api.getById(id); - if (res.done) { - this.model = res.body; - this.form.patchValue(this.model); - this.clientName = res.body.client_name as string; - this.projectManager = this.model.project_manager ? this.model.project_manager : null; - this.projectMembers = this.model.members ?? []; - this.newTasks = this.model.tasks ?? []; - this.categoryId = this.model.category_id || null; - } - this.loading = false; - } catch (e) { - this.loading = false; - log_error(e); - } - - this.cdr.markForCheck(); - } - - async submit() { - if (this.isMember) return; - if (this.form.valid) { - if (this.projectId) { - const updated = await this.update(this.projectId); - // if (updated) - // window.location.reload(); - } else { - void this.create(); - } - } else { - this.app.displayErrorsOf(this.form); - } - } - - async searchMembers() { - this.searching = true; - await this.getTeamMembers(); - this.searching = false; - - this.cdr.markForCheck(); - } - - onVisibilityChange(visible: boolean) { - if (visible) { - void this.getFolders(); - if (this.isMember) return; - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - const element = this.projectName.nativeElement as HTMLInputElement; - if (element) - element.focus(); - }, 100); - }); - } - } - - handleOwnerVisibleChange(visible: boolean) { - if (visible) { - try { - setTimeout(() => { - this.projectManagerSelector.nativeElement.classList.add('highlight'); - }, 100) - this.cdr.markForCheck(); - } catch (e) { - log_error(e); - this.cdr.markForCheck(); - } - } else { - this.projectManagerSelector.nativeElement.classList.remove('highlight'); - this.cdr.markForCheck(); - } - } - - setColorCode(colorCode: string) { - this.form.controls["color_code"].setValue(colorCode); - } - - onNameChangeSubmit(name: string) { - this.clientName = name || null; - } - - onKeyChange() { - const value = this.form.controls["key"].value; - if (value) - this.form.controls["key"].setValue(value.toUpperCase()); - } - - newFolder() { - this.folderFormService.create((folder?: IProjectFolder) => { - if (folder) { - this.updateFolders(folder); - this.form.controls["folder_id"]?.setValue(folder.id); - this.cdr.markForCheck(); - } - }); - } - - private updateFolders(folder: IProjectFolder) { - const folders = [...this.folders]; - folders.push(folder); - folders.sort((a, b) => a.name.localeCompare(b.name)); - this.folders = folders; - } - - calculateManDays() { - const start = this.form.controls["start_date"].value; - const end = this.form.controls["end_date"].value; - if (start && end) { - const s = moment(start); - const e = moment(end); - let days = e.diff(s, "days") + 1; - if (e.isoWeekday() > 5) days -= e.isoWeekday() % 5; - if (s.isoWeekday() > 5) days -= (3 - (s.isoWeekday() % 5)); - if (days > 5) { - const weeks = (days - (days % 7)) / 7; - days -= (weeks * 2); - } - this.form.controls["working_days"].setValue(days); - } - } - - trackById(index: number, item: ITeamMemberViewModel) { - return item.id; - } - - handleMemberChange(item: ITeamMemberViewModel | null) { - if (item?.pending_invitation || this.isMember || (!this.isOwnerOrAdmin() && this.isProjectManager())) return; - this.projectManager = item; - this.focusOut(); - this.cdr.markForCheck(); - } - - focusOut() { - setTimeout(() => { - this.outsideClicker.nativeElement.click(); - }, 50) - this.cdr.markForCheck(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/components/project-members-form/project-members-form.component.html b/worklenz-frontend/src/app/administrator/components/project-members-form/project-members-form.component.html deleted file mode 100644 index ad776947..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-members-form/project-members-form.component.html +++ /dev/null @@ -1,58 +0,0 @@ - - - -
- -
- - -
- -
- {{member.name}} - {{member.email}} -
-
- - -
-
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/components/project-members-form/project-members-form.component.scss b/worklenz-frontend/src/app/administrator/components/project-members-form/project-members-form.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/components/project-members-form/project-members-form.component.spec.ts b/worklenz-frontend/src/app/administrator/components/project-members-form/project-members-form.component.spec.ts deleted file mode 100644 index 32fbccbb..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-members-form/project-members-form.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ProjectMembersFormComponent} from './project-members-form.component'; - -describe('ProjectMembersFormComponent', () => { - let component: ProjectMembersFormComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ProjectMembersFormComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ProjectMembersFormComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/project-members-form/project-members-form.component.ts b/worklenz-frontend/src/app/administrator/components/project-members-form/project-members-form.component.ts deleted file mode 100644 index 1bef5eb0..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-members-form/project-members-form.component.ts +++ /dev/null @@ -1,162 +0,0 @@ -import {ChangeDetectorRef, Component, EventEmitter, Input, Output, ViewChild} from '@angular/core'; -import {ProjectMembersApiService} from "@api/project-members-api.service"; -import {IProjectMemberViewModel} from "@interfaces/task-form-view-model"; -import {TeamMembersAutocompleteComponent} from "../team-members-autocomplete/team-members-autocomplete.component"; -import {log_error} from "@shared/utils"; -import {NzSpinModule} from "ng-zorro-antd/spin"; -import {NzListModule} from "ng-zorro-antd/list"; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NgForOf, NgIf} from "@angular/common"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzPopconfirmModule} from "ng-zorro-antd/popconfirm"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzAvatarModule} from "ng-zorro-antd/avatar"; -import {FirstCharUpperPipe} from "@pipes/first-char-upper.pipe"; -import {TaskListV2Service} from "../../modules/task-list-v2/task-list-v2.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; -import {ProjectFormService} from "@services/project-form-service.service"; -import {AuthService} from "@services/auth.service"; -import {ProjectsService} from "../../projects/projects.service"; - -@Component({ - selector: 'worklenz-project-members-form', - templateUrl: './project-members-form.component.html', - styleUrls: ['./project-members-form.component.scss'], - imports: [ - NzSpinModule, - NzListModule, - NzDrawerModule, - NgIf, - NzToolTipModule, - NzTypographyModule, - NgForOf, - NzPopconfirmModule, - NzFormModule, - NzButtonModule, - NzIconModule, - TeamMembersAutocompleteComponent, - NzAvatarModule, - FirstCharUpperPipe, - SafeStringPipe - ], - standalone: true -}) -export class ProjectMembersFormComponent { - @ViewChild(TeamMembersAutocompleteComponent) teamMembersAutocomplete!: TeamMembersAutocompleteComponent; - - @Input() show = false; - @Output() showChange = new EventEmitter(); - @Output() onUpdate = new EventEmitter(); - - @Input() projectId: string | null = null; - @Input() isProjectManager = false; - - loading = false; - autofocus = false; - - members: IProjectMemberViewModel[] = []; - isProjectManagerAndAdmin = false; - - constructor( - private readonly api: ProjectMembersApiService, - private readonly list: TaskListV2Service, - private readonly projectFormService: ProjectFormService, - private cdr: ChangeDetectorRef, - private readonly auth: AuthService - ) { - this.list.onInviteClick$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.autofocus = true; - this.show = true; - this.showChange.emit(true); - }); - } - - closeModal() { - this.projectFormService.emitMemberAssignOrRemoveReProject(); - this.autofocus = false; - this.show = false; - this.showChange.emit(false); - } - - adminAndManager() { - if(this.isProjectManager && this.auth.isOwnerOrAdmin()) { - return this.isProjectManagerAndAdmin = false - } - if(this.isProjectManager && !this.auth.isOwnerOrAdmin()) { - return this.isProjectManagerAndAdmin = true - } - return false; - } - - async getMembers() { - if (!this.projectId) return; - try { - this.loading = true; - const res = await this.api.getByProjectId(this.projectId); - if (res.done) { - this.members = res.body; - } - this.loading = false; - } catch (e) { - log_error(e); - this.loading = false; - } - this.cdr.markForCheck(); - } - - async addMember(teamMemberId: string) { - if (!teamMemberId || !this.projectId) return; - try { - this.teamMembersAutocomplete.reset(); - const res = await this.api.create({team_member_id: teamMemberId, project_id: this.projectId}); - if (res.done) { - this.onUpdate?.emit(); - void this.getMembers(); - this.projectFormService.emitMemberAssignOrRemoveReProject(); - } - } catch (e) { - log_error(e); - } - this.cdr.markForCheck(); - } - - onVisibleChange(visible: boolean) { - if (visible) { - void this.getMembers(); - } - } - - membersChange(memberId: string | string[]) { - if (Array.isArray(memberId) && !memberId.length) return; - const id = (Array.isArray(memberId) && memberId.length) ? memberId[0] : memberId; - if (id) { - void this.addMember(id as string); - this.cdr.markForCheck(); - } - } - - async removeMember(id?: string) { - if (!id) return; - try { - const res = await this.api.deleteById(id, this.projectId as string); - if (res.done) { - this.onUpdate?.emit(); - await this.getMembers(); - this.projectFormService.emitMemberAssignOrRemoveReProject(); - this.cdr.markForCheck(); - } - } catch (e) { - log_error(e); - } - } - - trackById(index: number, item: IProjectMemberViewModel) { - return item.id; - } -} diff --git a/worklenz-frontend/src/app/administrator/components/project-template-create-drawer/project-template-create-drawer.component.html b/worklenz-frontend/src/app/administrator/components/project-template-create-drawer/project-template-create-drawer.component.html deleted file mode 100644 index d8f2e955..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-template-create-drawer/project-template-create-drawer.component.html +++ /dev/null @@ -1,95 +0,0 @@ - -
- - Template name - - - Name cannot be empty! - - - -
- What should be included in the template from the project ? -
-
- -
-
- -
-
- -
-
-
- -
- What should be included in tasks ? -
-
- -
-
- -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
-
-
- - -
- - -
-
- -
diff --git a/worklenz-frontend/src/app/administrator/components/project-template-create-drawer/project-template-create-drawer.component.scss b/worklenz-frontend/src/app/administrator/components/project-template-create-drawer/project-template-create-drawer.component.scss deleted file mode 100644 index d4c3446e..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-template-create-drawer/project-template-create-drawer.component.scss +++ /dev/null @@ -1,18 +0,0 @@ -.required-star{ - display: inline-block; - margin-right: 4px; - color: #ff4d4f; - font-size: 14px; - font-family: SimSun, sans-serif; - line-height: 1; - content: "*"; -} - -.empty-error-text { - color: #ff4d4f; -} - -.error-input { - border-color: #ff4d4f; - box-shadow: none; -} diff --git a/worklenz-frontend/src/app/administrator/components/project-template-create-drawer/project-template-create-drawer.component.spec.ts b/worklenz-frontend/src/app/administrator/components/project-template-create-drawer/project-template-create-drawer.component.spec.ts deleted file mode 100644 index 92d21076..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-template-create-drawer/project-template-create-drawer.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ProjectTemplateCreateDrawerComponent } from './project-template-create-drawer.component'; - -describe('ProjectTemplateCreateDrawerComponent', () => { - let component: ProjectTemplateCreateDrawerComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ProjectTemplateCreateDrawerComponent] - }); - fixture = TestBed.createComponent(ProjectTemplateCreateDrawerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/project-template-create-drawer/project-template-create-drawer.component.ts b/worklenz-frontend/src/app/administrator/components/project-template-create-drawer/project-template-create-drawer.component.ts deleted file mode 100644 index 9f87df3b..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-template-create-drawer/project-template-create-drawer.component.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - Input, - NgZone, - ViewChild -} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {FormsModule} from "@angular/forms"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzCheckboxModule} from "ng-zorro-antd/checkbox"; -import {NzInputModule} from 'ng-zorro-antd/input'; -import {NzButtonModule} from 'ng-zorro-antd/button'; -import {log_error} from "@shared/utils"; -import {ICustomProjectTemplateCreateRequest} from "@interfaces/project-template"; -import {ProjectTemplateApiService} from "@api/project-template-api.service"; - -@Component({ - selector: 'worklenz-project-template-create-drawer', - standalone: true, - imports: [CommonModule, NzDrawerModule, NzFormModule, FormsModule, NzTypographyModule, NzCheckboxModule, NzInputModule, NzButtonModule], - templateUrl: './project-template-create-drawer.component.html', - styleUrls: ['./project-template-create-drawer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class ProjectTemplateCreateDrawerComponent { - @ViewChild('templateNameInput') templateNameInput!: ElementRef; - @Input({required: true}) projectId: string | null = null; - - templateName: string | null = null; - - show = false; - checked = true; - showErrorText = false; - disableBtnActive = true; - - pStatusCheck = true; - pPhasesCheck = true; - pLabelsCheck = true; - - tStatusCheck = true; - tPhaseCheck = true; - tEstimationCheck = true; - tLabelsCheck = true; - tDescriptionCheck = true; - tSubTasksCheck = true; - - creating = false; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: ProjectTemplateApiService, - private readonly ngZone: NgZone, - ) { - } - - public async open() { - this.show = true; - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.templateNameInput.nativeElement.focus(); - }, 100) - }) - this.cdr.markForCheck(); - } - - close() { - this.show = false; - this.cdr.markForCheck(); - } - - nameChange(text: string) { - if (text.trim() === "") { - this.showErrorText = true; - return; - } - this.disableBtnActive = false; - this.showErrorText = false; - this.cdr.markForCheck(); - } - - projectCheckChange(key: string, value: boolean) { - switch (key) { - case 'pStatuses': - if (!value) - this.tStatusCheck = false; - break; - case 'pPhases': - if (!value) - this.tPhaseCheck = false; - break; - case 'pLabels': - if (!value) - this.tLabelsCheck = false; - break; - } - this.cdr.markForCheck(); - } - - async saveTemplate() { - if (!this.templateName || this.templateName.trim() === "" || !this.projectId) { - this.showErrorText = true; - return; - } - - try { - this.creating = true; - const body: ICustomProjectTemplateCreateRequest = { - project_id: this.projectId, - templateName: this.templateName, - projectIncludes: { - statuses: this.pStatusCheck, - phases: this.pPhasesCheck, - labels: this.pLabelsCheck - }, - taskIncludes: { - status: this.tStatusCheck, - phase: this.tPhaseCheck, - labels: this.tLabelsCheck, - estimation: this.tEstimationCheck, - description: this.tDescriptionCheck, - subtasks: this.tSubTasksCheck - } - } - const res = await this.api.createCustomTemplate(body); - if (res.done) { - this.creating = false; - this.show = false; - } - this.creating = false; - this.cdr.markForCheck(); - } catch (e) { - log_error(e); - this.cdr.markForCheck(); - } - - } - -} diff --git a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/custom-template-list/custom-template-list.component.html b/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/custom-template-list/custom-template-list.component.html deleted file mode 100644 index b764c222..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/custom-template-list/custom-template-list.component.html +++ /dev/null @@ -1,28 +0,0 @@ -
- - - - - - - -
- - - - - - - {{item.name}} - - - - - - - No custom templates are found! - - diff --git a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/custom-template-list/custom-template-list.component.scss b/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/custom-template-list/custom-template-list.component.scss deleted file mode 100644 index 1527c247..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/custom-template-list/custom-template-list.component.scss +++ /dev/null @@ -1,31 +0,0 @@ -.w-35 { - max-width: 35%; -} - -.actions-row { - cursor: pointer; - transition: 0.25s background; - - &:hover { - & .template-name { - color: #188fff; - } - } -} - -nz-list-item { - height: 48px; -} - -.selected { - background: #e6f7ff; - - & .template-name { - color: #188fff; - } -} - -.temp-details { - max-height: calc(100vh - 245px); - overflow: auto; -} diff --git a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/custom-template-list/custom-template-list.component.spec.ts b/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/custom-template-list/custom-template-list.component.spec.ts deleted file mode 100644 index 1be8aaf3..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/custom-template-list/custom-template-list.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { CustomTemplateListComponent } from './custom-template-list.component'; - -describe('CustomTemplateListComponent', () => { - let component: CustomTemplateListComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [CustomTemplateListComponent] - }); - fixture = TestBed.createComponent(CustomTemplateListComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/custom-template-list/custom-template-list.component.ts b/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/custom-template-list/custom-template-list.component.ts deleted file mode 100644 index 726a387e..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/custom-template-list/custom-template-list.component.ts +++ /dev/null @@ -1,74 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, OnInit, Output} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {log_error} from "@shared/utils"; -import {ProjectTemplateApiService} from "@api/project-template-api.service"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzListModule} from "ng-zorro-antd/list"; -import {FormsModule} from "@angular/forms"; -import {NzCheckboxModule} from "ng-zorro-antd/checkbox"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NzMenuModule} from "ng-zorro-antd/menu"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {ICustomTemplate} from "@interfaces/api-models/project-template"; -import {NzEmptyModule} from "ng-zorro-antd/empty"; - -@Component({ - selector: 'worklenz-custom-template-list', - standalone: true, - imports: [CommonModule, NzSkeletonModule, NzListModule, FormsModule, NzCheckboxModule, NzInputModule, NzMenuModule, SearchByNamePipe, NzBadgeModule, NzSpaceModule, NzIconModule, NzEmptyModule], - templateUrl: './custom-template-list.component.html', - styleUrls: ['./custom-template-list.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class CustomTemplateListComponent implements OnInit { - @Output() selectTemplate: EventEmitter = new EventEmitter(); - - teamSearchText: string | null = null; - - loading = false; - - templateList: ICustomTemplate[] = []; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: ProjectTemplateApiService - ) { - } - - ngOnInit() { - void this.get(); - } - - async get() { - try { - this.loading = true; - const res = await this.api.getWorklenzCustomTemplates(); - if(res.done) { - this.templateList = res.body; - this.loading = false; - } - this.loading = false; - } catch (e) { - log_error(e) - } - this.cdr.markForCheck(); - } - - changeSelectedTemplate(templateId: string | undefined, index: number) { - for (let i = 0; i < this.templateList.length; i++) { - this.templateList[i].selected = false; - } - - this.templateList[index].selected = true; - this.selectTemplate.emit(templateId); - this.cdr.markForCheck(); - } - - detectChanges() { - this.cdr.markForCheck(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/project-template-import-drawer.component.html b/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/project-template-import-drawer.component.html deleted file mode 100644 index 0cfb2979..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/project-template-import-drawer.component.html +++ /dev/null @@ -1,37 +0,0 @@ - - -
- - - - - - - - - - -
- -
- - -
-
-
diff --git a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/project-template-import-drawer.component.scss b/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/project-template-import-drawer.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/project-template-import-drawer.component.spec.ts b/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/project-template-import-drawer.component.spec.ts deleted file mode 100644 index 7a432b3c..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/project-template-import-drawer.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ProjectTemplateImportDrawerComponent } from './project-template-import-drawer.component'; - -describe('ProjectTemplateImportDrawerComponent', () => { - let component: ProjectTemplateImportDrawerComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ProjectTemplateImportDrawerComponent] - }); - fixture = TestBed.createComponent(ProjectTemplateImportDrawerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/project-template-import-drawer.component.ts b/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/project-template-import-drawer.component.ts deleted file mode 100644 index a53273a3..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/project-template-import-drawer.component.ts +++ /dev/null @@ -1,138 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzWaveModule} from "ng-zorro-antd/core/wave"; -import {NzTabsModule} from "ng-zorro-antd/tabs"; -import {log_error} from "@shared/utils"; -import { - WorklenzTemplateListComponent -} from "@admin/components/project-template-import-drawer/worklenz-template-list/worklenz-template-list.component"; -import { - CustomTemplateListComponent -} from "@admin/components/project-template-import-drawer/custom-template-list/custom-template-list.component"; -import {ProjectTemplateApiService} from "@api/project-template-api.service"; -import {Router} from "@angular/router"; - -@Component({ - selector: 'worklenz-project-template-import-drawer', - standalone: true, - imports: [CommonModule, NzDrawerModule, NzButtonModule, NzWaveModule, NzTabsModule, WorklenzTemplateListComponent, CustomTemplateListComponent], - templateUrl: './project-template-import-drawer.component.html', - styleUrls: ['./project-template-import-drawer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush - -}) -export class ProjectTemplateImportDrawerComponent { - @Input({required: true}) showBothTabs = true; - @Output() importProject: EventEmitter = new EventEmitter(); - - selectedWorklenzTemplateId: string | null = null; - selectedCustomTemplateId: string | null = null; - - selectedTabIndex = 0; - - show = false; - loading = false; - creating = false; - - drawerVisible = false; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: ProjectTemplateApiService, - private readonly router: Router - ) { - } - - public open() { - this.reset(); - this.show = true; - this.cdr.markForCheck(); - } - - close() { - this.show = false; - this.cdr.markForCheck(); - } - - async create() { - if (this.showBothTabs) { - if (this.selectedTabIndex === 0) { - await this.createFromWorklenzLib(); - return; - } - if (this.selectedTabIndex === 1) { - await this.createFromCustomLib(); - return; - } - } else { - this.importProject.emit({template_id: this.selectedWorklenzTemplateId}); - } - } - - async createFromWorklenzLib() { - if (!this.selectedWorklenzTemplateId) return; - try { - this.creating = true; - const res = await this.api.createFromTemplate({template_id: this.selectedWorklenzTemplateId}); - if (res.done) { - this.creating = false; - this.close(); - this.importProject.emit({project_id: res.body.project_id}); - await this.router.navigate([`/worklenz/projects/${res.body.project_id}`]); - this.cdr.markForCheck(); - } - this.creating = false; - } catch (e) { - log_error(e); - this.creating = false; - this.cdr.markForCheck(); - } - } - - async createFromCustomLib() { - if (!this.selectedCustomTemplateId) return; - try { - this.creating = true; - const res = await this.api.createFromCustomTemplate({template_id: this.selectedCustomTemplateId}); - if (res.done) { - this.creating = false; - this.close(); - this.importProject.emit({project_id: res.body.project_id}); - await this.router.navigate([`/worklenz/projects/${res.body.project_id}`]); - this.cdr.markForCheck(); - } - this.cdr.markForCheck(); - } catch (e) { - log_error(e); - this.creating = false; - this.cdr.markForCheck(); - } - } - - changeSelectedWorklenzTemp(templateId: string) { - this.selectedWorklenzTemplateId = templateId; - this.selectedCustomTemplateId = null; - this.cdr.markForCheck(); - } - - changeSelectedCustomTemp(templateId: string) { - this.selectedCustomTemplateId = templateId; - this.selectedWorklenzTemplateId = null; - this.cdr.markForCheck(); - } - - reset() { - this.selectedWorklenzTemplateId = null; - this.selectedCustomTemplateId = null; - this.loading = false; - this.creating = false; - this.selectedTabIndex = 0; - this.cdr.markForCheck(); - } - - onDrawerVisibilityChange(event: boolean) { - this.drawerVisible = event; - } -} diff --git a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/worklenz-template-list/worklenz-template-list.component.html b/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/worklenz-template-list/worklenz-template-list.component.html deleted file mode 100644 index 2bd02114..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/worklenz-template-list/worklenz-template-list.component.html +++ /dev/null @@ -1,93 +0,0 @@ -
-
- -
    -
  • - {{item.name}} -
  • -
-
-
-
-

Details

- - - -
- preview -
- - -
-
- Description -
-
- {{templateDetails.description}} -
-
- - -
-
- Phases -
-
- {{item.name}} -
-
- - -
-
- Statuses -
-
- {{item.name}} -
-
- - -
-
- Priorities -
-
- {{item.name}} -
-
- - -
-
- Labels -
-
- {{item.name}} -
-
- - - -
- Tasks -
-
-
    -
  • - {{item.name}} -
  • -
-
- -
-
-
diff --git a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/worklenz-template-list/worklenz-template-list.component.scss b/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/worklenz-template-list/worklenz-template-list.component.scss deleted file mode 100644 index 878b18d3..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/worklenz-template-list/worklenz-template-list.component.scss +++ /dev/null @@ -1,33 +0,0 @@ -.side-menu { - min-width: 250px; - max-width: 250px; - max-height: calc(100vh - 200px); - overflow-y: auto; -} - -.temp-details { - max-height: calc(100vh - 200px); - overflow: auto; -} - -.worklenz_key_sec { - min-width: 120px; - max-width: 120px; - - & span { - font-weight: 500; - } -} - -.worklenz_value_sec { - max-width: 520px; -} - -.preview { - max-width: 600px; -} - -nz-tag { - color: #000; - margin-bottom: 8px; -} diff --git a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/worklenz-template-list/worklenz-template-list.component.spec.ts b/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/worklenz-template-list/worklenz-template-list.component.spec.ts deleted file mode 100644 index b77ebca3..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/worklenz-template-list/worklenz-template-list.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { WorklenzTemplateListComponent } from './worklenz-template-list.component'; - -describe('WorklenzTemplateListComponent', () => { - let component: WorklenzTemplateListComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [WorklenzTemplateListComponent] - }); - fixture = TestBed.createComponent(WorklenzTemplateListComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/worklenz-template-list/worklenz-template-list.component.ts b/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/worklenz-template-list/worklenz-template-list.component.ts deleted file mode 100644 index 89c6dfd7..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-template-import-drawer/worklenz-template-list/worklenz-template-list.component.ts +++ /dev/null @@ -1,84 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, OnInit, Output} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {log_error} from "@shared/utils"; -import {ProjectTemplateApiService} from "@api/project-template-api.service"; -import {NzMenuModule} from 'ng-zorro-antd/menu'; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzTagModule} from "ng-zorro-antd/tag"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzListModule} from "ng-zorro-antd/list"; -import {IProjectTemplate, IWorklenzTemplate} from "@interfaces/api-models/project-template"; - -@Component({ - selector: 'worklenz-template-list', - standalone: true, - imports: [CommonModule, NzMenuModule, NzSkeletonModule, NzTypographyModule, NzTagModule, NzSpaceModule, NzListModule], - templateUrl: './worklenz-template-list.component.html', - styleUrls: ['./worklenz-template-list.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class WorklenzTemplateListComponent implements OnInit { - @Output() selectTemplate: EventEmitter = new EventEmitter(); - - loadingList = false; - loadingDetails = false; - - selectedTemplateIndex = 0; - - templateList: IWorklenzTemplate[] = []; - - templateDetails: IProjectTemplate = {} - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: ProjectTemplateApiService, - ) { - } - - ngOnInit() { - void this.get(); - } - - async get() { - try { - this.loadingList = true; - const res = await this.api.getWorklenzTemplates(); - if (res.done) { - this.templateList = res.body; - this.loadingList = false; - await this.emitTemplateSelect(); - this.cdr.markForCheck(); - } - } catch (e) { - log_error(e) - this.cdr.markForCheck(); - } - } - - onSelectTemplate(templateId: string, selectedIndex: number) { - if (!templateId) return; - this.selectedTemplateIndex = selectedIndex; - setTimeout(() => { - void this.emitTemplateSelect(); - }, 500) - this.cdr.markForCheck(); - } - - async emitTemplateSelect() { - const selectedTemplateId = this.templateList[this.selectedTemplateIndex].id; - this.selectTemplate.emit(selectedTemplateId); - - if (selectedTemplateId) { - this.loadingDetails = true; - const res = await this.api.getWorklenzTemplateById(selectedTemplateId); - if (res.done) { - this.templateDetails = res.body; - this.loadingDetails = false; - } - this.loadingDetails = false; - } - this.cdr.markForCheck(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/components/project-updates-drawer/project-updates-drawer.component.html b/worklenz-frontend/src/app/administrator/components/project-updates-drawer/project-updates-drawer.component.html deleted file mode 100644 index 54bd74e6..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-updates-drawer/project-updates-drawer.component.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - diff --git a/worklenz-frontend/src/app/administrator/components/project-updates-drawer/project-updates-drawer.component.scss b/worklenz-frontend/src/app/administrator/components/project-updates-drawer/project-updates-drawer.component.scss deleted file mode 100644 index 4a91a3c7..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-updates-drawer/project-updates-drawer.component.scss +++ /dev/null @@ -1,24 +0,0 @@ -.comments-block { - position: absolute; - bottom: 0; - left: 24px; - right: 24px; - z-index: 1; - background: #FAFAFA; - margin-left: -1.5rem; - margin-right: -1.5rem; - border-top: 1px solid rgba(0, 0, 0, 0.06); -} - -nz-comment-content p { - user-select: text; -} - -nz-skeleton { - margin-top: -16px; -} - -worklenz-project-updates-input { - position: sticky; - bottom: 0; -} diff --git a/worklenz-frontend/src/app/administrator/components/project-updates-drawer/project-updates-drawer.component.spec.ts b/worklenz-frontend/src/app/administrator/components/project-updates-drawer/project-updates-drawer.component.spec.ts deleted file mode 100644 index 5e509f0f..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-updates-drawer/project-updates-drawer.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ProjectUpdatesDrawerComponent } from './project-updates-drawer.component'; - -describe('ProjectUpdatesDrawerComponent', () => { - let component: ProjectUpdatesDrawerComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ProjectUpdatesDrawerComponent] - }); - fixture = TestBed.createComponent(ProjectUpdatesDrawerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/project-updates-drawer/project-updates-drawer.component.ts b/worklenz-frontend/src/app/administrator/components/project-updates-drawer/project-updates-drawer.component.ts deleted file mode 100644 index 80757734..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-updates-drawer/project-updates-drawer.component.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ViewChild -} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzSpinModule} from "ng-zorro-antd/spin"; -import {FirstCharUpperPipe} from "@pipes/first-char-upper.pipe"; -import {FromNowPipe} from "@pipes/from-now.pipe"; -import {NzAvatarModule} from "ng-zorro-antd/avatar"; -import {NzCommentModule} from "ng-zorro-antd/comment"; -import {NzPopconfirmModule} from "ng-zorro-antd/popconfirm"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {IProjectUpdateCommentViewModel} from "@interfaces/project"; -import {UtilsService} from "@services/utils.service"; -import {ProjectUpdatesListComponent} from "@admin/components/project-updates-list/project-updates-list.component"; - -@Component({ - selector: 'worklenz-project-updates-drawer', - standalone: true, - imports: [ - CommonModule, - NzDrawerModule, - NzSpinModule, - FirstCharUpperPipe, - FromNowPipe, - NzAvatarModule, - NzCommentModule, - NzPopconfirmModule, - NzSkeletonModule, - ProjectUpdatesListComponent - ], - templateUrl: './project-updates-drawer.component.html', - styleUrls: ['./project-updates-drawer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectUpdatesDrawerComponent { - - @ViewChild(ProjectUpdatesListComponent) list!: ProjectUpdatesListComponent; - - show = false; - projectId: string | null = null; - - constructor( - private readonly cdr: ChangeDetectorRef, - public readonly utils: UtilsService, - ) { - } - - public async open(projectId: string) { - this.show = true; - await this.list.loadDataOnDrawer(projectId); - this.cdr.markForCheck(); - } - - handleClose() { - this.reset(); - this.show = false; - } - - onVisibleChange(visible: boolean) { - if (visible) { - this.cdr.markForCheck(); - } - } - - reset() { - this.projectId = null; - } - -} diff --git a/worklenz-frontend/src/app/administrator/components/project-updates-input/project-updates-input.component.html b/worklenz-frontend/src/app/administrator/components/project-updates-input/project-updates-input.component.html deleted file mode 100644 index 8b137891..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-updates-input/project-updates-input.component.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/worklenz-frontend/src/app/administrator/components/project-updates-input/project-updates-input.component.scss b/worklenz-frontend/src/app/administrator/components/project-updates-input/project-updates-input.component.scss deleted file mode 100644 index 8b137891..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-updates-input/project-updates-input.component.scss +++ /dev/null @@ -1 +0,0 @@ - diff --git a/worklenz-frontend/src/app/administrator/components/project-updates-input/project-updates-input.component.spec.ts b/worklenz-frontend/src/app/administrator/components/project-updates-input/project-updates-input.component.spec.ts deleted file mode 100644 index e404b475..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-updates-input/project-updates-input.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ProjectUpdatesInputComponent } from './project-updates-input.component'; - -describe('ProjectUpdatesInputComponent', () => { - let component: ProjectUpdatesInputComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ProjectUpdatesInputComponent] - }); - fixture = TestBed.createComponent(ProjectUpdatesInputComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/project-updates-input/project-updates-input.component.ts b/worklenz-frontend/src/app/administrator/components/project-updates-input/project-updates-input.component.ts deleted file mode 100644 index 91cc4c01..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-updates-input/project-updates-input.component.ts +++ /dev/null @@ -1,31 +0,0 @@ -import {ChangeDetectionStrategy, Component} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {FormsModule, ReactiveFormsModule} from "@angular/forms"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NzMentionModule} from "ng-zorro-antd/mention"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzWaveModule} from "ng-zorro-antd/core/wave"; - -@Component({ - selector: 'worklenz-project-updates-input', - standalone: true, - imports: [ - CommonModule, - FormsModule, - NzButtonModule, - NzFormModule, - NzInputModule, - NzSpaceModule, - NzWaveModule, - ReactiveFormsModule, - NzMentionModule - ], - templateUrl: './project-updates-input.component.html', - styleUrls: ['./project-updates-input.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectUpdatesInputComponent { - -} diff --git a/worklenz-frontend/src/app/administrator/components/project-updates-list/project-updates-list.component.html b/worklenz-frontend/src/app/administrator/components/project-updates-list/project-updates-list.component.html deleted file mode 100644 index 70b5cf02..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-updates-list/project-updates-list.component.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - -

-
- - - Delete - - -
-
-
- - - - - -
- - - - -
-
-
diff --git a/worklenz-frontend/src/app/administrator/components/project-updates-list/project-updates-list.component.scss b/worklenz-frontend/src/app/administrator/components/project-updates-list/project-updates-list.component.scss deleted file mode 100644 index 7a3f4d94..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-updates-list/project-updates-list.component.scss +++ /dev/null @@ -1,10 +0,0 @@ -textarea { - box-shadow: none !important; -} -form { - background-color: white; - position: sticky; - bottom: 0px; - padding-top: 12px; - padding-bottom: 18px; -} diff --git a/worklenz-frontend/src/app/administrator/components/project-updates-list/project-updates-list.component.spec.ts b/worklenz-frontend/src/app/administrator/components/project-updates-list/project-updates-list.component.spec.ts deleted file mode 100644 index de446f9a..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-updates-list/project-updates-list.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ProjectUpdatesListComponent } from './project-updates-list.component'; - -describe('ProjectUpdatesListComponent', () => { - let component: ProjectUpdatesListComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ProjectUpdatesListComponent] - }); - fixture = TestBed.createComponent(ProjectUpdatesListComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/project-updates-list/project-updates-list.component.ts b/worklenz-frontend/src/app/administrator/components/project-updates-list/project-updates-list.component.ts deleted file mode 100644 index 1b014a36..00000000 --- a/worklenz-frontend/src/app/administrator/components/project-updates-list/project-updates-list.component.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Input, - OnDestroy, - OnInit -} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {FirstCharUpperPipe} from "../../../pipes/first-char-upper.pipe"; -import {FromNowPipe} from "../../../pipes/from-now.pipe"; -import {NzAvatarModule} from "ng-zorro-antd/avatar"; -import {NzCommentModule} from "ng-zorro-antd/comment"; -import {NzPopconfirmModule} from "ng-zorro-antd/popconfirm"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {ProjectUpdatesInputComponent} from "../project-updates-input/project-updates-input.component"; -import {IProjectUpdateCommentViewModel} from "../../../interfaces/project"; -import {AuthService} from "../../../services/auth.service"; -import {UtilsService} from "../../../services/utils.service"; -import {ProjectCommentsApiService} from "../../../services/api/project-comments-api.service"; -import {log_error} from "../../../shared/utils"; -import {ProjectUpdatesService} from "@services/project-updates.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzWaveModule} from "ng-zorro-antd/core/wave"; -import {IMentionMember, IMentionMemberViewModel} from "@interfaces/project-comments"; -import {AppService} from "@services/app.service"; -import {ProjectMembersApiService} from "@api/project-members-api.service"; -import {IProjectCommentsCreateRequest} from "@interfaces/api-models/project-comment-create-request"; -import {FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators} from "@angular/forms"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {MentionOnSearchTypes, NzMentionModule} from "ng-zorro-antd/mention"; -import {DomSanitizer} from "@angular/platform-browser"; - -@Component({ - selector: 'worklenz-project-updates-list', - standalone: true, - imports: [ - CommonModule, - FirstCharUpperPipe, - FromNowPipe, - NzAvatarModule, - NzCommentModule, - NzPopconfirmModule, - NzSkeletonModule, - ProjectUpdatesInputComponent, - NzButtonModule, - NzFormModule, - NzInputModule, - NzMentionModule, - NzSpaceModule, - NzWaveModule, - ReactiveFormsModule, - FormsModule - ], - templateUrl: './project-updates-list.component.html', - styleUrls: ['./project-updates-list.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectUpdatesListComponent implements OnInit, OnDestroy { - @Input() projectId: string | null = null; - @Input() isLimit = false; - - loading = false; - commentsInputFocused = false; - loadingMembers = false; - creatingComment = false; - - search: string | null = null; - - updates: IProjectUpdateCommentViewModel[] = []; - projectMembers: IMentionMemberViewModel[] = []; - mentions: IMentionMember[] = []; - - form!: FormGroup; - - valueWith = (data: { name: string; }): string => data.name; - - constructor( - private readonly fb: FormBuilder, - private readonly app: AppService, - private readonly cdr: ChangeDetectorRef, - private readonly auth: AuthService, - public readonly utils: UtilsService, - private readonly api: ProjectCommentsApiService, - private readonly service: ProjectUpdatesService, - ) { - this.form = this.fb.group({ - content: [null, [Validators.required, Validators.maxLength(2000)]] - }); - - this.service.onRefresh - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.get(); - }); - } - - ngOnInit() { - this.get(); - this.getProjectMembers(); - this.getCount(); - this.service.emitBadgeDisable(); - } - - ngOnDestroy() { - this.projectMembers = []; - this.search = null; - this.mentions = []; - } - - get rows() { - this.cdr.markForCheck(); - return this.commentsInputFocused ? 4 : 1; - } - - updateLocalCount(newCount: string) { - return localStorage.setItem("worklenz.project.updates-" + this.projectId, newCount); - } - - async loadDataOnDrawer(projectId: string) { - this.projectId = projectId; - await this.get(); - await this.getProjectMembers(); - await this.getCount(); - } - - private async get() { - if (!this.projectId) return; - try { - this.loading = true; - const res = await this.api.getByProjectId(this.projectId, this.isLimit); - if (res) { - this.updates = res.body; - this.loading = false; - this.cdr.markForCheck(); - } - } catch (e) { - log_error(e); - this.loading = false; - this.cdr.markForCheck(); - } - } - - private async getCount() { - if (!this.projectId) return; - try { - const res = await this.api.getCountByProjectId(this.projectId); - if (res) { - this.updateLocalCount(res.body.toString()); - this.cdr.markForCheck(); - } - } catch (e) { - log_error(e) - this.cdr.markForCheck(); - } - } - - async getProjectMembers() { - if (!this.projectId) return; - try { - this.loadingMembers = true; - const res = await this.api.getMembers(this.projectId, 1, 5, null, null, this.search); - if (res.done) { - this.projectMembers = res.body || []; - } - this.loadingMembers = false; - this.cdr.markForCheck(); - } catch (e) { - this.loadingMembers = false; - log_error(e); - this.cdr.markForCheck(); - } - } - - setFocus(focused: boolean) { - this.commentsInputFocused = focused; - } - - async create() { - if (!this.projectId || !this.form.value.content || this.form.value.content.trim() === "") return; - try { - this.creatingComment = true; - const session = this.auth.getCurrentSession(); - const body: IProjectCommentsCreateRequest = { - project_id: this.projectId, - team_id: session?.team_id, - content: this.form.value.content, - mentions: [...new Set(this.mentions || [])] - } - const res = await this.api.create(body); - if (res) { - await this.get(); - await this.getCount(); - this.service.emitGetLastUpdate(); - this.reset(); - } - this.creatingComment = false; - this.cdr.markForCheck(); - } catch (e) { - log_error(e); - this.creatingComment = false; - this.cdr.markForCheck(); - } - } - - onSearchChange({value}: MentionOnSearchTypes): void { - this.search = value; - } - - onSelect(member: IMentionMemberViewModel) { - if (!member || !member.id || !member.name) return; - if (!this.mentions.some(mention => mention.id === member.id)) - this.mentions.push( - { - id: member.id, - name: member.name - } - ); - } - - cancel() { - this.setFocus(false); - this.form.reset(); - } - - isValid() { - return this.form.valid; - } - - submit() { - if (this.form.valid) { - this.create(); - } else { - this.app.displayErrorsOf(this.form); - } - } - - canDelete(userId?: string) { - if (!userId) return false; - return userId === this.auth.getCurrentSession()?.id; - } - - async deleteComment(id?: string) { - if (!id) return; - try { - const res = await this.api.deleteById(id); - if (res) { - await this.get(); - await this.getCount(); - this.service.emitGetLastUpdate(); - } - } catch (e) { - log_error(e); - } - } - - trackById(updateComment: IProjectUpdateCommentViewModel) { - return updateComment.id; - } - - reset() { - this.mentions = []; - this.form.reset(); - this.creatingComment = false; - } - -} diff --git a/worklenz-frontend/src/app/administrator/components/projects-autocomplete/projects-autocomplete.component.html b/worklenz-frontend/src/app/administrator/components/projects-autocomplete/projects-autocomplete.component.html deleted file mode 100644 index 61e13778..00000000 --- a/worklenz-frontend/src/app/administrator/components/projects-autocomplete/projects-autocomplete.component.html +++ /dev/null @@ -1,26 +0,0 @@ -
- - {{label}} - - - - - - - - Loading Data... - - - - -
diff --git a/worklenz-frontend/src/app/administrator/components/projects-autocomplete/projects-autocomplete.component.scss b/worklenz-frontend/src/app/administrator/components/projects-autocomplete/projects-autocomplete.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/components/projects-autocomplete/projects-autocomplete.component.spec.ts b/worklenz-frontend/src/app/administrator/components/projects-autocomplete/projects-autocomplete.component.spec.ts deleted file mode 100644 index b5df9d85..00000000 --- a/worklenz-frontend/src/app/administrator/components/projects-autocomplete/projects-autocomplete.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ProjectsAutocompleteComponent} from './projects-autocomplete.component'; - -describe('ProjectsAutocompleteComponent', () => { - let component: ProjectsAutocompleteComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ProjectsAutocompleteComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ProjectsAutocompleteComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/projects-autocomplete/projects-autocomplete.component.ts b/worklenz-frontend/src/app/administrator/components/projects-autocomplete/projects-autocomplete.component.ts deleted file mode 100644 index 03562fc0..00000000 --- a/worklenz-frontend/src/app/administrator/components/projects-autocomplete/projects-autocomplete.component.ts +++ /dev/null @@ -1,95 +0,0 @@ -import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; -import {FormBuilder, FormGroup, ReactiveFormsModule} from "@angular/forms"; -import {ProjectsApiService} from "@api/projects-api.service"; -import {IProject} from "@interfaces/project"; -import {log_error} from "@shared/utils"; -import {NzSelectModule} from "ng-zorro-antd/select"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NgForOf, NgIf} from "@angular/common"; -import {NzIconModule} from "ng-zorro-antd/icon"; - -@Component({ - selector: 'worklenz-projects-autocomplete', - templateUrl: './projects-autocomplete.component.html', - styleUrls: ['./projects-autocomplete.component.scss'], - imports: [ - NzSelectModule, - NzFormModule, - NgIf, - ReactiveFormsModule, - NgForOf, - NzIconModule - ], - standalone: true -}) -export class ProjectsAutocompleteComponent implements OnInit { - @Output() projectNamesChange: EventEmitter = new EventEmitter(); - @Input() projectNames: string[] = []; - - @Input() placeholder = 'Select Projects'; - @Input() label = 'Projects'; - - @Input() multiple = false; - @Input() disabled = false; - - form!: FormGroup; - - loading = false; - searching = false; - - projects: IProject[] = []; - - total = 0; - - searchingName: string | null = null; - - constructor( - private api: ProjectsApiService, - private fb: FormBuilder - ) { - this.form = this.fb.group({ - project_names: [] - }); - - this.form.controls["project_names"]?.valueChanges.subscribe(value => { - this.projectNames = value; - this.projectNamesChange.emit(this.projectNames); - }); - } - - async ngOnInit() { - this.form.controls["project_names"].setValue(this.projectNames || []); - void this.init(); - } - - async init() { - this.loading = true; - await this.get(); - this.loading = false; - } - - async get() { - try { - const res = await this.api.get(1, 5, null, null, this.searchingName); - if (res.done) { - this.projects = res.body.data || []; - this.total = this.projects.length; - } - } catch (e) { - log_error(e); - } - } - - async search(value: string) { - this.searchingName = value; - this.searching = true; - await this.get(); - this.searching = false; - } - - public reset() { - this.form.reset(); - this.searchingName = null; - void this.init(); - } -} diff --git a/worklenz-frontend/src/app/administrator/components/resource-gantt/resource-gantt.component.html b/worklenz-frontend/src/app/administrator/components/resource-gantt/resource-gantt.component.html deleted file mode 100644 index c1c72f1f..00000000 --- a/worklenz-frontend/src/app/administrator/components/resource-gantt/resource-gantt.component.html +++ /dev/null @@ -1,54 +0,0 @@ - -
- - - - - - -
-
-
-
- - {{month.month_name}} - -
-
- {{date.date | date: 'dd'}} -
-
- -
-
-
    -
  • - - -
    -

    Project Timeline

    -

    {{project.min_date | date:'mediumDate'}} - - {{project.max_date | date:'mediumDate'}} -

    -
    -
    - No timeline available. -
    -
    -
  • -
-
-
-
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/components/resource-gantt/resource-gantt.component.scss b/worklenz-frontend/src/app/administrator/components/resource-gantt/resource-gantt.component.scss deleted file mode 100644 index 8299db1b..00000000 --- a/worklenz-frontend/src/app/administrator/components/resource-gantt/resource-gantt.component.scss +++ /dev/null @@ -1,110 +0,0 @@ -$column_count: var(--column_count); -$column_width: var(--column_width); -$top_margin: var(--top_margin); - -.container { - max-width: 100%; - margin: 0 auto; -} - -.chart { - display: grid; - border: 1px solid #e3e3e3; - height: calc(100vh - $top_margin); - position: relative; - overflow: auto; - grid-auto-rows: 40px; -} - -.chart-row { - display: grid; - grid-template-columns: 1fr; - background-color: #fff; -} - -.chart-period { - color: black; - background-color: #e3e3e3 !important; - border-bottom: 1px solid silver; - grid-template-columns: repeat($column_count, $column_width); -} - -.chart-month { - color: black; - background-color: #f1f1f1 !important; - border-bottom: 1px solid silver; - text-align: center; - z-index: 5; - top: 0; - position: sticky; - grid-template-columns: repeat($column_count, $column_width); -} - -.chart-lines { - position: absolute; - height: 100%; - width: 100%; - background-color: transparent; - grid-template-columns: repeat($column_count, $column_width); -} - -.chart-period > span { - text-align: center; - font-size: 11px; - align-self: center; - font-weight: 500; - padding: 3px 0; - max-height: 30px; -} - -.chart-lines > span { - display: block; - border-right: 1px solid #f1f1f1; -} - -.chart-row-item { - background-color: #808080; - border: 1px solid #000; - border-top: 0; - border-left: 0; - padding: 20px 0; - font-size: 15px; - font-weight: bold; - text-align: center; -} - -.chart-row-bars { - list-style: none; - display: grid; - padding: 15px 0; - margin: 0; - grid-template-columns: repeat($column_count, $column_width); - grid-gap: 10px 0; - border-bottom: 1px solid #f1f1f1; -} - -li { - font-weight: 450; - text-align: left; - font-size: 13px; - min-height: 15px; - background-color: #708090; - padding: 0 15px; - color: #fff; - overflow: hidden; - position: relative; - cursor: pointer; - border-radius: 2px; -} - -.today-header { - background-color: #1890ff; - border-color: #1890ff; - color: white; -} - -.today { - border-left: 2px solid #1890ff; - background-repeat: no-repeat; - background-position: center center; -} diff --git a/worklenz-frontend/src/app/administrator/components/resource-gantt/resource-gantt.component.spec.ts b/worklenz-frontend/src/app/administrator/components/resource-gantt/resource-gantt.component.spec.ts deleted file mode 100644 index b3291195..00000000 --- a/worklenz-frontend/src/app/administrator/components/resource-gantt/resource-gantt.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ResourceGanttComponent} from './resource-gantt.component'; - -describe('ResourceGanttComponent', () => { - let component: ResourceGanttComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ResourceGanttComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ResourceGanttComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/resource-gantt/resource-gantt.component.ts b/worklenz-frontend/src/app/administrator/components/resource-gantt/resource-gantt.component.ts deleted file mode 100644 index 4feb2d5a..00000000 --- a/worklenz-frontend/src/app/administrator/components/resource-gantt/resource-gantt.component.ts +++ /dev/null @@ -1,119 +0,0 @@ -import {Component, OnInit} from '@angular/core'; -import {EResourceGanttColumn, EResourceGanttViewModes} from '@interfaces/gantt-chart'; -import moment from 'moment/moment'; -import {PersonalOverviewService} from '@api/personal-overview.service'; -import {ITasksOverview} from '@interfaces/personal-overview'; -import {GanttUtilsService} from '@services/gantt-utils.service'; -import {format} from 'date-fns'; -import {NzLayoutModule} from "ng-zorro-antd/layout"; -import {NzSegmentedModule} from "ng-zorro-antd/segmented"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {DatePipe, NgForOf, NgIf} from "@angular/common"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzPopoverModule} from "ng-zorro-antd/popover"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; - -@Component({ - selector: 'worklenz-resource-gantt', - templateUrl: './resource-gantt.component.html', - styleUrls: ['./resource-gantt.component.scss'], - imports: [ - NzLayoutModule, - NzSegmentedModule, - NzSkeletonModule, - NgIf, - NgForOf, - NzTypographyModule, - NzPopoverModule, - NzButtonModule, - DatePipe, - SafeStringPipe - ], - standalone: true -}) -export class ResourceGanttComponent implements OnInit { - options = ['Weekly', 'Monthly', 'Quarterly']; - viewMode: EResourceGanttViewModes = 0; - columnWidth: number = EResourceGanttColumn.WEEKS; - ganttChartScaleModes = EResourceGanttViewModes; - - todayIndex = 0; - - chartStartDate: string = moment().subtract(4, 'months').format('YYYY-MM-DD'); - chartEndDate: string = moment().add(4, 'months').format('YYYY-MM-DD'); - - dates: { isSunday: boolean, isWeekend: boolean, isToday: string, isLastDayOfWeek: string, date: Date }[] = []; - weeks: any = []; - months: any[] = []; - - loadingTasks = false; - - projects: ITasksOverview[] = []; - - constructor( - private personalOverviewService: PersonalOverviewService, - private dateUtilsService: GanttUtilsService - ) { - } - - ngOnInit(): void { - this.getTasksRange().then(r => r); - } - - async getTasksRange() { - try { - this.loadingTasks = true; - const res = await this.personalOverviewService.getTasksOverview(this.chartStartDate, this.chartEndDate); - if (res.done) { - this.projects = res.body; - this.dates = await this.dateUtilsService.getDates(this.chartStartDate, this.chartEndDate); - this.weeks = await this.dateUtilsService.getWeekRange(this.dates); - this.months = await this.dateUtilsService.getMonthRange(this.dates); - - document.documentElement.style.setProperty('--column_count', this.dates.length.toString()); - document.documentElement.style.setProperty('--column_width', `${EResourceGanttColumn.WEEKS}px`); - document.documentElement.style.setProperty('--top_margin', '290px'); - - this.todayIndex = this.dates.findIndex(e => format(new Date(), 'dd-MM-yyyy') === format(e.date, 'dd-MM-yyyy')); - - if (this.todayIndex !== -1) await this.scrollToDate(this.todayIndex); - - } - this.loadingTasks = false; - } catch (e) { - this.loadingTasks = false; - } - } - - async getColumnWidth(viewMode: EResourceGanttViewModes) { - if (viewMode === EResourceGanttViewModes.WEEKS) return EResourceGanttColumn.WEEKS; - return viewMode === EResourceGanttViewModes.QUARTERS ? EResourceGanttColumn.QUARTERS : EResourceGanttColumn.MONTHS; - } - - async changeViewMode(viewMode: EResourceGanttViewModes) { - this.viewMode = viewMode; - this.columnWidth = await this.getColumnWidth(viewMode || EResourceGanttViewModes.WEEKS); - document.documentElement.style.setProperty('--column_width', `${this.columnWidth}px`); - if (this.todayIndex !== -1) await this.scrollToDate(this.todayIndex); - } - - setRange(minDate: Date = new Date(), maxDate: Date = new Date()) { - const startDateIndex = this.dates.findIndex((date) => moment(minDate || moment().subtract(2, 'days').format('YYYY-MM-DD')).isSame(date.date, 'days')); - const endDateIndex = this.dates.findIndex((date) => moment(maxDate || moment().add(2, 'days').format('YYYY-MM-DD')).isSame(date.date, 'days')); - - return `${startDateIndex + 1} / ${endDateIndex + 2}`; - } - - getMonthRange(min: number, max: number) { - return `${min + 1} / ${max + 2}`; - } - - async scrollToDate(todayIndex: number) { - setTimeout(() => { - document.getElementById(`date_${todayIndex}`)?.scrollIntoView({ - behavior: 'auto', block: 'center', inline: 'center' - }); - }, 50); - } -} diff --git a/worklenz-frontend/src/app/administrator/components/status-form/status-form.component.html b/worklenz-frontend/src/app/administrator/components/status-form/status-form.component.html deleted file mode 100644 index d79c7701..00000000 --- a/worklenz-frontend/src/app/administrator/components/status-form/status-form.component.html +++ /dev/null @@ -1,55 +0,0 @@ - - - -
- - Name - - - - - - Category - - - - - - - - - -
- - -
- Categorized Statuses -
-
-
- {{item.name}} -
-
- -
-
-
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/components/status-form/status-form.component.scss b/worklenz-frontend/src/app/administrator/components/status-form/status-form.component.scss deleted file mode 100644 index b7d760e8..00000000 --- a/worklenz-frontend/src/app/administrator/components/status-form/status-form.component.scss +++ /dev/null @@ -1,9 +0,0 @@ -.status-name-holder { - padding: 6px 11px 6px 11px; -} -.main-title { - color: rgba(0, 0, 0, 0.85); - font-weight: 500; - font-size: 16px; - line-height: 22px; -} diff --git a/worklenz-frontend/src/app/administrator/components/status-form/status-form.component.spec.ts b/worklenz-frontend/src/app/administrator/components/status-form/status-form.component.spec.ts deleted file mode 100644 index 1c1cefa7..00000000 --- a/worklenz-frontend/src/app/administrator/components/status-form/status-form.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {StatusFormComponent} from './status-form.component'; - -describe('StatusFormComponent', () => { - let component: StatusFormComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [StatusFormComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(StatusFormComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/status-form/status-form.component.ts b/worklenz-frontend/src/app/administrator/components/status-form/status-form.component.ts deleted file mode 100644 index 9a11345e..00000000 --- a/worklenz-frontend/src/app/administrator/components/status-form/status-form.component.ts +++ /dev/null @@ -1,199 +0,0 @@ -import {Component, EventEmitter, Input, Output} from '@angular/core'; -import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; -import {TaskStatusesApiService} from '@api/task-statuses-api.service'; -import {AppService} from '@services/app.service'; -import {ProjectsDefaultColorCodes} from '@shared/constants'; -import {dispatchStatusChange} from '@shared/events'; -import {ITaskStatus} from '@interfaces/task-status'; -import {log_error} from "@shared/utils"; -import {ITaskStatusCategory} from "@interfaces/task-status-category"; -import {NzSelectModule} from "ng-zorro-antd/select"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NgForOf, NgIf} from "@angular/common"; -import {KanbanV2Service} from 'app/administrator/modules/kanban-view-v2/kanban-view-v2.service'; -import {AuthService} from "@services/auth.service"; -import {ProjectsService} from "../../projects/projects.service"; -import {TaskListV2Service} from "../../modules/task-list-v2/task-list-v2.service"; -import {ITaskListGroup} from "../../modules/task-list-v2/interfaces"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzDividerModule} from "ng-zorro-antd/divider"; - -@Component({ - selector: 'worklenz-status-form', - templateUrl: './status-form.component.html', - styleUrls: ['./status-form.component.scss'], - imports: [ - NzSelectModule, - NzSkeletonModule, - NzDrawerModule, - NzBadgeModule, - ReactiveFormsModule, - NzFormModule, - NzInputModule, - NzToolTipModule, - NzButtonModule, - NgForOf, - NgIf, - NzTypographyModule, - NzDividerModule - ], - standalone: true -}) -export class StatusFormComponent { - @Input() action: string = 'Create'; - @Input() show = false; - @Input() statusId: string | null = null; - @Input() projectId: string | null = null; - @Input() showStatusGroups = false; - - @Output() onCreateOrUpdate: EventEmitter = new EventEmitter(); - @Output() onCancel: EventEmitter = new EventEmitter(); - - form!: FormGroup; - loading = true; - loadingCategories = false; - - categories: ITaskStatusCategory[] = []; - taskStatus: ITaskStatus = {}; - categorisedStatus: { - name?: string; - description?: string; - statuses: ITaskListGroup[]; - id?: string; - color_code?: string - }[] = []; - - constructor( - private api: TaskStatusesApiService, - private fb: FormBuilder, - private app: AppService, - private readonly kanbanService: KanbanV2Service, - private readonly auth: AuthService, - private readonly projectsService: ProjectsService, - private readonly tasklistService: TaskListV2Service - ) { - this.createForm(); - } - - init() { - this.form.controls["project_id"].setValue(this.projectId); - void this.getCategories(); - if (this.statusId) { - void this.getById(this.statusId); - } else { - this.loading = false; - } - - } - - closeModal() { - this.show = false; - this.form.reset(); - this.action = 'Create'; - this.createForm(); - this.onCancel.emit(); - } - - async submit() { - if (this.taskStatus && this.taskStatus.id) { - await this.updateStatus(); - } else { - await this.addStatus(); - } - } - - async getById(id: string) { - try { - this.loading = true; - const res = await this.api.getById(id); - if (res.done) { - this.taskStatus = res.body; - this.form.patchValue(this.taskStatus); - } - this.loading = false; - } catch (e) { - log_error(e); - this.loading = false; - } - } - - async getCategories() { - try { - this.loadingCategories = true; - const res = await this.api.getCategories(); - if (res.done) { - this.categories = res.body; - this.form.controls["category_id"].setValue(this.categories[0].id); - this.categorisedStatus = this.categories.map(category => { - return { - ...category, - statuses: this.tasklistService.groups.filter(status => status.category_id === category.id) - } - }); - } - this.loadingCategories = false; - } catch (e) { - this.loadingCategories = false; - log_error(e); - } - } - - async addStatus() { - if (this.form.invalid) { - this.app.displayErrorsOf(this.form); - return; - } - - try { - const res = await this.api.create(this.form.value, this.projectId as string); - if (res.done) { - res.body.color_code = res.body.color_code + "69"; - this.kanbanService.emitOnCreateStatus(res.body); - this.onCreateOrUpdate.emit(); - this.closeModal(); - dispatchStatusChange(); - } - } catch (e) { - log_error(e); - } - } - - async updateStatus() { - if (!this.taskStatus || !this.taskStatus.id) return; - if (this.form.invalid) { - this.app.displayErrorsOf(this.form); - return; - } - - try { - const res = await this.api.update(this.taskStatus.id, this.form.value, this.projectId as string); - if (res.done) { - this.closeModal(); - dispatchStatusChange(); - } - } catch (e) { - log_error(e); - } - } - - onVisibilityChange(visible: boolean) { - if (visible) { - // Wait for drawer animation to finish - setTimeout(() => this.init(), 100); - } - } - - private createForm() { - this.form = this.fb.group({ - name: [null, [Validators.required]], - category_id: [null, [Validators.required]], - project_id: [this.projectId] - }); - } -} diff --git a/worklenz-frontend/src/app/administrator/components/task-priority-label/task-priority-label.component.html b/worklenz-frontend/src/app/administrator/components/task-priority-label/task-priority-label.component.html deleted file mode 100644 index ca2a7e23..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-priority-label/task-priority-label.component.html +++ /dev/null @@ -1,8 +0,0 @@ -
- {{name}} - - - - - -
diff --git a/worklenz-frontend/src/app/administrator/components/task-priority-label/task-priority-label.component.scss b/worklenz-frontend/src/app/administrator/components/task-priority-label/task-priority-label.component.scss deleted file mode 100644 index 7f4e416e..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-priority-label/task-priority-label.component.scss +++ /dev/null @@ -1,18 +0,0 @@ -.low-priority { - color: #52c41a; -} - -.medium-priority { - color: #faad14; - transform: rotateZ(90deg); -} - -.high-priority { - color: #f5222d; - transform: rotateZ(90deg); -} - -.center-hr { - display: flex; - align-items: center; -} diff --git a/worklenz-frontend/src/app/administrator/components/task-priority-label/task-priority-label.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-priority-label/task-priority-label.component.spec.ts deleted file mode 100644 index fbb96c74..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-priority-label/task-priority-label.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskPriorityLabelComponent} from './task-priority-label.component'; - -describe('TaskPriorityLabelComponent', () => { - let component: TaskPriorityLabelComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [TaskPriorityLabelComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskPriorityLabelComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-priority-label/task-priority-label.component.ts b/worklenz-frontend/src/app/administrator/components/task-priority-label/task-priority-label.component.ts deleted file mode 100644 index 67866d25..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-priority-label/task-priority-label.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import {Component, Input} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; - -@Component({ - selector: 'worklenz-task-priority-label', - standalone: true, - imports: [CommonModule, NzIconModule, NzTypographyModule], - templateUrl: './task-priority-label.component.html', - styleUrls: ['./task-priority-label.component.scss'] -}) -export class TaskPriorityLabelComponent { - @Input() name!: string; -} diff --git a/worklenz-frontend/src/app/administrator/components/task-template-drawer/task-template-drawer.component.html b/worklenz-frontend/src/app/administrator/components/task-template-drawer/task-template-drawer.component.html deleted file mode 100644 index e8b0fb9a..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-template-drawer/task-template-drawer.component.html +++ /dev/null @@ -1,39 +0,0 @@ - -
- - Template Name - - - - - - Selected Tasks ({{tasks.length || 0}}) -
-
    -
  • - - {{task.name}} -
  • -
-
- - -
- - -
-
-
\ No newline at end of file diff --git a/worklenz-frontend/src/app/administrator/components/task-template-drawer/task-template-drawer.component.scss b/worklenz-frontend/src/app/administrator/components/task-template-drawer/task-template-drawer.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/components/task-template-drawer/task-template-drawer.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-template-drawer/task-template-drawer.component.spec.ts deleted file mode 100644 index 185c2ebe..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-template-drawer/task-template-drawer.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskTemplateDrawerComponent} from './task-template-drawer.component'; - -describe('TaskTemplateDrawerComponent', () => { - let component: TaskTemplateDrawerComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskTemplateDrawerComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskTemplateDrawerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-template-drawer/task-template-drawer.component.ts b/worklenz-frontend/src/app/administrator/components/task-template-drawer/task-template-drawer.component.ts deleted file mode 100644 index c3c95b72..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-template-drawer/task-template-drawer.component.ts +++ /dev/null @@ -1,158 +0,0 @@ -import {Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from '@angular/core'; -import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms"; -import {TaskTemplatesService} from "@api/task-templates.service"; -import {log_error} from "@shared/utils"; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzListModule} from "ng-zorro-antd/list"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NgForOf, NgIf} from "@angular/common"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzSpinModule} from "ng-zorro-antd/spin"; -import {AppService} from "@services/app.service"; -import {TaskListHashMapService} from "../../modules/task-list-v2/task-list-hash-map.service"; - -@Component({ - selector: 'worklenz-task-template-drawer', - templateUrl: './task-template-drawer.component.html', - styleUrls: ['./task-template-drawer.component.scss'], - standalone: true, - imports: [ - NzDrawerModule, - NzFormModule, - NzListModule, - ReactiveFormsModule, - NzInputModule, - NzButtonModule, - NgIf, - NgForOf, - NzSkeletonModule, - NzSpinModule - ] -}) -export class TaskTemplateDrawerComponent implements OnChanges { - @Input() drawerVisible = false; - @Input() selectedTemplateId = ''; - - @Output() onCancelClick = new EventEmitter(); - @Output() onTaskRemove = new EventEmitter(); - @Output() onCreateOrUpdate = new EventEmitter(); - - form!: FormGroup; - tasks: IProjectTask[] = []; - - creating = false; - loading = false; - - get title() { - if (this.selectedTemplateId) return `Edit Task Template`; - return `Create Task Template`; - } - - constructor( - private readonly fb: FormBuilder, - private readonly map: TaskListHashMapService, - private readonly service: TaskTemplatesService, - private readonly app: AppService - ) { - this.form = this.fb.group({ - name: [null, Validators.required] - }); - } - - ngOnChanges(changes: SimpleChanges) { - if (changes['selectedTemplateId']?.currentValue) { - void this.getTemplateData(); - } - } - - closeDrawer(): void { - this.onCancelClick.emit(false); - } - - removeTask(index: number) { - if (this.tasks.length > 1) { - this.tasks.splice(index, 1); - } else { - this.tasks = []; - } - } - - submit() { - if (this.form.valid) { - if (this.selectedTemplateId) { - void this.updateTemplate(); - } else { - void this.saveTemplate(); - } - } else { - this.app.displayErrorsOf(this.form); - } - } - - private reset() { - this.form.reset(); - this.map.deselectAll(); - this.closeDrawer(); - this.onCreateOrUpdate.emit(); - } - - async updateTemplate() { - try { - this.creating = true; - const res = await this.service.updateTemplate(this.selectedTemplateId, { - name: this.form.value.name || '', - tasks: this.tasks - }); - if (res.done) { - this.reset(); - } - } catch (e) { - this.creating = false; - } - } - - async saveTemplate() { - try { - if (this.form.valid) { - this.creating = true; - if (this.form.value.name) { - const res = await this.service.createTemplate({ - name: this.form.value.name || '', - tasks: this.tasks - }); - if (res.done) { - this.reset(); - } - } - } - } catch (e) { - this.creating = false; - log_error(e); - } - } - - async getTemplateData() { - try { - this.loading = true; - const res = await this.service.getById(this.selectedTemplateId); - if (res.done) { - this.form.setValue({name: res.body.name}); - this.tasks = res.body.tasks || []; - this.loading = false; - } - } catch (e) { - this.loading = false; - log_error(e); - } - } - - onVisibilityChange(visible: boolean) { - if (visible) { - this.tasks = this.map.getSelectedTasks(); - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/components/task-timer/interfaces.ts b/worklenz-frontend/src/app/administrator/components/task-timer/interfaces.ts deleted file mode 100644 index 21a63f7c..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-timer/interfaces.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface ITimerChange { - task_id: string; - start_time: number; -} diff --git a/worklenz-frontend/src/app/administrator/components/task-timer/task-timer.component.html b/worklenz-frontend/src/app/administrator/components/task-timer/task-timer.component.html deleted file mode 100644 index 8c0037ed..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-timer/task-timer.component.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - {{timeString}} - - - - - - - - - - diff --git a/worklenz-frontend/src/app/administrator/components/task-timer/task-timer.component.scss b/worklenz-frontend/src/app/administrator/components/task-timer/task-timer.component.scss deleted file mode 100644 index d08a8580..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-timer/task-timer.component.scss +++ /dev/null @@ -1,16 +0,0 @@ -button { - min-width: 16px !important; - width: 16px !important; - height: 16px !important; - line-height: 14px; - padding-left: 1px; -} - -.icon-stop { - font-size: 8px; - position: relative; - top: -1px; - left: 0; - right: 0; - bottom: 0; -} diff --git a/worklenz-frontend/src/app/administrator/components/task-timer/task-timer.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-timer/task-timer.component.spec.ts deleted file mode 100644 index 2defd5f0..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-timer/task-timer.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskTimerComponent} from './task-timer.component'; - -describe('TaskTimerComponent', () => { - let component: TaskTimerComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [TaskTimerComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskTimerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-timer/task-timer.component.ts b/worklenz-frontend/src/app/administrator/components/task-timer/task-timer.component.ts deleted file mode 100644 index 3c952648..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-timer/task-timer.component.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - OnDestroy, - OnInit, - Output -} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {UtilsService} from "@services/utils.service"; -import {filter, Subject, takeUntil} from "rxjs"; -import {TaskTimerService} from "@admin/components/task-timer/task-timer.service"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; -import {AuthService} from "@services/auth.service"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {ITaskAssigneesUpdateResponse} from "@interfaces/task-assignee-update-response"; - -@Component({ - selector: 'worklenz-task-timer', - standalone: true, - imports: [CommonModule, NzIconModule, NzButtonModule, NzTypographyModule], - templateUrl: './task-timer.component.html', - styleUrls: ['./task-timer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskTimerComponent implements OnInit, OnDestroy { - private readonly DEFAULT_TIME_LEFT = this.buildTimeString(0, 0, 0); - - @Output() onStart = new EventEmitter(); - @Output() onStop = new EventEmitter(); - @Output() changeListTime = new EventEmitter(); - @Output() changeListTimeToDefault = new EventEmitter(); - - @Input() taskId!: string; - @Input() projectId!: string; - @Input() timerStartTime: number | null = null; - - started = false; - startTime = 0; - timeString = this.DEFAULT_TIME_LEFT; - timer: null | number = null; - - private readonly destroy$ = new Subject(); - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly socket: Socket, - private readonly utils: UtilsService, - private readonly service: TaskTimerService, - private readonly viewService: TaskViewService, - private readonly auth: AuthService - ) { - this.service.onStart - .pipe( - filter(value => value.task_id === this.taskId), - takeUntil(this.destroy$) - ) - .subscribe(value => { - this.startTimer(value.start_time); - this.cdr.markForCheck(); - }); - - this.service.onStop - .pipe( - filter(value => value.task_id === this.taskId), - takeUntil(this.destroy$) - ) - .subscribe(value => { - this.stopTimer(); - this.cdr.markForCheck(); - }); - } - - ngOnInit() { - this.startTime = 0; - this.timer = null; - if (this.timerStartTime) { - this.startTimer(this.timerStartTime || Date.now()); - } - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } - - quickAssignMember(session: ILocalSession) { - const task = this.viewService.model.task; - if (!task) return; - const body = { - team_member_id: session.team_member_id, - project_id: this.projectId, - task_id: task.id, - reporter_id: session?.id, - mode: 0, - parent_task: task.parent_task_id - }; - this.socket.emit(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), JSON.stringify(body)); - this.socket.once(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), (response: ITaskAssigneesUpdateResponse) => { - if (session.team_member_id) { - this.viewService.emitTimeLogAssignMember(response); - this.viewService.emitSingleMemberChange(session.team_member_id); - } - }) - this.cdr.markForCheck(); - } - - toggleTimer(event: MouseEvent) { - if (this.started) { - this.socket.once(SocketEvents.TASK_TIMER_STOP.toString(), () => { - const session = this.auth.getCurrentSession(); - if (session) { - const assignees = this.viewService.model.task?.assignees as string[]; - if (assignees) { - if (!assignees.includes(session?.team_member_id as string)) this.quickAssignMember(session); - } else { - this.service.emitListTimerStop(this.taskId) - } - } - this.onStop?.emit(); - }); - - this.stopTimer(); - } else { - this.socket.once(SocketEvents.TASK_TIMER_START.toString(), () => { - this.onStart?.emit(); - }); - - this.startTimer(this.timerStartTime || Date.now()); - } - event.stopPropagation(); - } - - buildTimeString(hours: number, minutes: number, seconds: number) { - return this.utils.buildTimeString(hours, minutes, seconds); - } - - private startTimer(startTime: number) { - if (this.viewService.model.task) this.viewService.model.task.timer_start_time = startTime; - if (this.started) return; - this.started = true; - this.startTime = startTime; - - if (!this.timerStartTime) { - this.socket.emit(SocketEvents.TASK_TIMER_START.toString(), JSON.stringify({ - task_id: this.taskId - })); - } - - this.timerTick(); - - this.changeListTime.emit(startTime); - - this.timer = setInterval(() => { - this.timerTick(); - }, 1000) as number; - } - - private stopTimer() { - if (typeof this.timer === "number") - clearInterval(this.timer); - - this.socket.emit(SocketEvents.TASK_TIMER_STOP.toString(), JSON.stringify({ - task_id: this.taskId - })); - - this.started = false; - this.timer = null; - if (this.viewService.model.task) this.viewService.model.task.timer_start_time = 0; - this.changeListTimeToDefault.emit(0); - this.startTime = 0; - this.timerStartTime = null; - this.timeString = this.DEFAULT_TIME_LEFT; - } - - private timerTick() { - const now = Date.now(); - const diff = ~~((now - this.startTime) / 1000); - const hours = ~~(diff / 3600); - const minutes = ~~((diff % 3600) / 60); - const seconds = diff % 60; - - this.timeString = this.buildTimeString(hours, minutes, seconds); - this.cdr.detectChanges(); - } -} diff --git a/worklenz-frontend/src/app/administrator/components/task-timer/task-timer.service.ts b/worklenz-frontend/src/app/administrator/components/task-timer/task-timer.service.ts deleted file mode 100644 index f540ede4..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-timer/task-timer.service.ts +++ /dev/null @@ -1,52 +0,0 @@ -import {Injectable} from '@angular/core'; -import {Subject} from "rxjs"; -import {ITimerChange} from "@admin/components/task-timer/interfaces"; - -@Injectable({ - providedIn: 'root' -}) -export class TaskTimerService { - private readonly _startSbj$ = new Subject(); - private readonly _stopSbj$ = new Subject(); - private readonly _submitOrUpdateSbj$ = new Subject(); - private readonly _listTimerStopSbj$ = new Subject(); - - public get onStart() { - return this._startSbj$.asObservable(); - } - - public get onStop() { - return this._stopSbj$.asObservable(); - } - - public get onSubmitOrUpdate() { - return this._submitOrUpdateSbj$.asObservable(); - } - - public get onListTimerStop() { - return this._listTimerStopSbj$.asObservable(); - } - - public emitStart(taskId: string, startTime = 0) { - this._startSbj$.next({ - task_id: taskId, - start_time: startTime - }); - } - - public emitStop(taskId: string) { - this._stopSbj$.next({ - task_id: taskId, - start_time: 0 - }); - } - - public emitSubmitOrUpdate() { - this._submitOrUpdateSbj$.next(); - } - - public emitListTimerStop(taskId: string) { - this._listTimerStopSbj$.next(taskId); - } - -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/interfaces.ts b/worklenz-frontend/src/app/administrator/components/task-view/interfaces.ts deleted file mode 100644 index 90f34d9c..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/interfaces.ts +++ /dev/null @@ -1,15 +0,0 @@ -import {InlineMember} from "@interfaces/api-models/inline-member"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {ITaskLabel} from "@interfaces/task-label"; -import {ITaskAssignee, ITaskStatusCategory} from "@interfaces/api-models/project-tasks-view-model"; - -export interface ITaskViewTaskIds { - id: string; - project_id: string; - parent_task_id?: string; -} - -export interface ITaskViewTaskOpenRequest { - task_id: string | null; - project_id: string | null; -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-activity-log/task-view-activity-log.component.html b/worklenz-frontend/src/app/administrator/components/task-view/task-view-activity-log/task-view-activity-log.component.html deleted file mode 100644 index bbbe98bc..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-activity-log/task-view-activity-log.component.html +++ /dev/null @@ -1,101 +0,0 @@ -
- - - -
-
- - -
-
- {{item.done_by?.name}} {{item.log_text}} {{item.attribute_type}}.   - - {{(item.created_at | fromNow) ?? ''}} - -
- - -
- - - {{item.assigned_user?.name}}  -    - {{item.log_type?.toUpperCase()}} -
- - -
- {{item.label_data?.name}} -    - {{item.log_type === 'create' ? 'ADD' : 'REMOVE'}} -
- - -
- {{item.previous_status.name ? item.previous_status.name : 'None'}} -    - {{item.next_status?.name}} -
- - -
- {{item.previous_priority?.name}} -    - {{item.next_priority?.name}} -
- - -
- {{(item.previous_phase && item.previous_phase.name) ? item.previous_phase.name : 'Unmapped'}} -    - {{(item.next_phase && item.next_phase.name) ? item.next_phase.name : 'Unmapped'}} -
- - -
-
- - -
- {{item.previous || 'None'}}  -    - {{item.current || 'None'}} -
- -
-
-
-
- -
-
- - -
-
- {{logs.name}} created the task.   - - {{(logs.created_at | fromNow) ?? ''}} - -
-
-
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-activity-log/task-view-activity-log.component.scss b/worklenz-frontend/src/app/administrator/components/task-view/task-view-activity-log/task-view-activity-log.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-activity-log/task-view-activity-log.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-activity-log/task-view-activity-log.component.spec.ts deleted file mode 100644 index b5271f67..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-activity-log/task-view-activity-log.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskViewActivityLogComponent} from './task-view-activity-log.component'; - -describe('TaskViewActivityLogComponent', () => { - let component: TaskViewActivityLogComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskViewActivityLogComponent] - }); - fixture = TestBed.createComponent(TaskViewActivityLogComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-activity-log/task-view-activity-log.component.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-activity-log/task-view-activity-log.component.ts deleted file mode 100644 index 1659ac1c..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-activity-log/task-view-activity-log.component.ts +++ /dev/null @@ -1,69 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {ActivityLogsService} from "@api/activity-logs.service"; -import {log_error} from "@shared/utils"; -import {IActivityLogAttributeTypes, IActivityLogsResponse} from "@interfaces/api-models/activity-logs-get-response"; -import {AvatarNamesMap} from "@shared/constants"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; - -@Component({ - selector: 'worklenz-task-view-activity-log', - templateUrl: './task-view-activity-log.component.html', - styleUrls: ['./task-view-activity-log.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskViewActivityLogComponent implements OnInit, OnDestroy { - @Input() taskId: string | null = null; - - loading = false; - logs: IActivityLogsResponse = {}; - readonly activityLogTypes = IActivityLogAttributeTypes; - - constructor( - private readonly api: ActivityLogsService, - private readonly cdr: ChangeDetectorRef, - private readonly socket: Socket, - ) { - this.socket.on(SocketEvents.TASK_STATUS_CHANGE.toString(), () => { - void this.getActivityLogs(); - }); - this.socket.on(SocketEvents.TASK_NAME_CHANGE.toString(), () => { - void this.getActivityLogs(); - }); - } - - ngOnInit() { - void this.getActivityLogs(); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_STATUS_CHANGE.toString(), () => { - void this.getActivityLogs(); - }); - this.socket.removeListener(SocketEvents.TASK_NAME_CHANGE.toString(), () => { - void this.getActivityLogs(); - }); - } - - async getActivityLogs() { - if (!this.taskId) return; - try { - this.loading = true; - const res = await this.api.getActivityLogs(this.taskId); - if (res.done) { - this.logs = res.body; - } - this.loading = false; - } catch (e) { - this.loading = false; - log_error(e); - } - - this.cdr.markForCheck(); - } - - getColor(name?: string) { - return AvatarNamesMap[name?.charAt(0).toUpperCase() || 'A']; - } - -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-assignees/task-view-assignees.component.html b/worklenz-frontend/src/app/administrator/components/task-view/task-view-assignees/task-view-assignees.component.html deleted file mode 100644 index 03ec4333..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-assignees/task-view-assignees.component.html +++ /dev/null @@ -1,58 +0,0 @@ - - Assignees - -
-
- - - - -
- - -
    -
  • - -
  • -
  • -
    - -
    - {{item.name}} - - {{item.email}} (Pending Invitation) - -
    -
    -
  • -
-
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-assignees/task-view-assignees.component.scss b/worklenz-frontend/src/app/administrator/components/task-view/task-view-assignees/task-view-assignees.component.scss deleted file mode 100644 index 7ea94ff9..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-assignees/task-view-assignees.component.scss +++ /dev/null @@ -1,17 +0,0 @@ -.disable { - position: relative; - &:after { - position: absolute; - content: ""; - background: #e7e7e769; - top: 0; - left: 0; - right: 0; - bottom: 0; - width: 100%; - } -} - -.z-top { - z-index: 9; -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-assignees/task-view-assignees.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-assignees/task-view-assignees.component.spec.ts deleted file mode 100644 index 3c649ad6..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-assignees/task-view-assignees.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskViewAssigneesComponent} from './task-view-assignees.component'; - -describe('TaskViewAssigneesComponent', () => { - let component: TaskViewAssigneesComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskViewAssigneesComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskViewAssigneesComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-assignees/task-view-assignees.component.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-assignees/task-view-assignees.component.ts deleted file mode 100644 index c794a468..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-assignees/task-view-assignees.component.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - NgZone, - OnDestroy, - OnInit, - ViewChild -} from '@angular/core'; -import {ITeamMemberViewModel} from "@interfaces/api-models/team-members-get-response"; -import {ITaskAssigneesUpdateResponse} from "@interfaces/task-assignee-update-response"; -import {AuthService} from "@services/auth.service"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {TaskListV2Service} from "../../../modules/task-list-v2/task-list-v2.service"; -import {ProjectsService} from "../../../projects/projects.service"; -import {TaskViewService} from "../task-view.service"; -import {UtilsService} from "@services/utils.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import { - ProjectScheduleService -} from "../../../schedule/schedule-v2/projects-schedule/service/project-schedule-service.service"; - -@Component({ - selector: 'worklenz-task-view-assignees', - templateUrl: './task-view-assignees.component.html', - styleUrls: ['./task-view-assignees.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskViewAssigneesComponent implements OnInit, OnDestroy { - @ViewChild('memberSearchInput', {static: false}) memberSearchInput!: ElementRef; - searchText: string | null = null; - - constructor( - private readonly auth: AuthService, - private readonly projectsService: ProjectsService, - private readonly socket: Socket, - private readonly ngZone: NgZone, - private readonly cdr: ChangeDetectorRef, - public readonly service: TaskViewService, - public readonly list: TaskListV2Service, - private readonly utils: UtilsService, - ) { - this.service.onTimeLogAssignMember - .pipe(takeUntilDestroyed()) - .subscribe( (response: ITaskAssigneesUpdateResponse) => { - this.handleResponse(response); - }) - } - - ngOnInit() { - this.socket.on(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), this.handleResponse); - } - - handleMembersVisibleChange(visible: boolean) { - if (!this.service.model.task) return; - - const members = [...this.list.members]; - - if (visible) { - const assignees = this.service.model.task.assignees as string[]; - for (const member of members) { - if (member.id) - member.selected = assignees.includes(member.id); - } - this.focusMemberSearchInput(); - } else { - this.searchText = null; - for (const member of members) - member.selected = false; - } - - this.list.members = members; - this.sortMembersBySelection(this.list.members); - this.cdr.markForCheck(); - } - - private focusMemberSearchInput() { - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.memberSearchInput?.nativeElement?.focus(); - }, 100); - }); - } - - handleMemberChange(item: ITeamMemberViewModel, assign: boolean) { - const task = this.service.model.task; - if (!task) return; - const session = this.auth.getCurrentSession(); - const body = { - team_member_id: item.id, - project_id: this.projectsService.id, - task_id: task.id, - reporter_id: session?.id, - mode: assign ? 0 : 1, - parent_task: task.parent_task_id - }; - this.socket.emit(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), JSON.stringify(body)); - if (item.id) this.service.emitSingleMemberChange(item.id); - } - - trackById(index: number, item: ITeamMemberViewModel) { - return item.id; - } - - private handleResponse = (response: ITaskAssigneesUpdateResponse) => { - if (!this.service.model.task) return; - try { - if (response) { - this.service.model.task.assignees = (response.assignees || []).map(m => m.team_member_id); - this.service.model.task.names = response.names || []; - this.service.emitRefresh(response.id); - this.service.emitAssigneesChange(); - if (this.list.isSubtasksIncluded) { - this.list.emitRefreshSubtasksIncluded(); - } - this.sortMembersBySelection(this.list.members); - this.cdr.markForCheck(); - } - } catch (e) { - // ignore - } - } - - private sortMembersBySelection(members: ITeamMemberViewModel[]) { - this.utils.sortBySelection(members); - this.utils.sortByPending(members); - } - -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments-thumb/task-view-attachments-thumb.component.html b/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments-thumb/task-view-attachments-thumb.component.html deleted file mode 100644 index d7bd05fd..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments-thumb/task-view-attachments-thumb.component.html +++ /dev/null @@ -1,113 +0,0 @@ -
-
- - - -

{{attachment.name}}

-

{{attachment.size}}

-

{{attachment.created_at | date: "medium"}}

-
-
- -
- -
- - - - - -
-
- - - - - - - - - - - -
-
- - - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - -

The preview for this file type is not available.

-
-
-
-
- -
-
- - {{attachment?.name}} - - diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments-thumb/task-view-attachments-thumb.component.scss b/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments-thumb/task-view-attachments-thumb.component.scss deleted file mode 100644 index d85b4f29..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments-thumb/task-view-attachments-thumb.component.scss +++ /dev/null @@ -1,45 +0,0 @@ -.file-icon { - width: 30px; - position: absolute; - z-index: 1; - background: #fff; - top: -7px; - left: -7px; - border-radius: 4px; - padding: 3px 0px; - border: 1px solid #d9d9d9; -} - -ngx-doc-viewer { - width: 100%; - height: 80vh; - height: 80vh; -} - -.preview-container { - max-width: 1024px; -} - -video { - max-width: 100%; -} - -nz-spin { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - display: flex; - align-items: center; - justify-content: center; - z-index: 0; -} - -.ant-upload-span{ - min-height: 86px; - max-width: 86px; - background-size: cover; - background-repeat: no-repeat; - background-position: center; -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments-thumb/task-view-attachments-thumb.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments-thumb/task-view-attachments-thumb.component.spec.ts deleted file mode 100644 index 688ccb71..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments-thumb/task-view-attachments-thumb.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskViewAttachmentsThumbComponent} from './task-view-attachments-thumb.component'; - -describe('TaskViewAttachmentsThumbComponent', () => { - let component: TaskViewAttachmentsThumbComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskViewAttachmentsThumbComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskViewAttachmentsThumbComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments-thumb/task-view-attachments-thumb.component.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments-thumb/task-view-attachments-thumb.component.ts deleted file mode 100644 index 5eddb5a2..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments-thumb/task-view-attachments-thumb.component.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - NgZone, - Output -} from "@angular/core"; -import {ITaskAttachmentViewModel} from "@interfaces/api-models/task-attachment-view-model"; -import {getFileIcon, log_error} from "@shared/utils"; -import {AttachmentsApiService} from "@api/attachments-api.service"; - -@Component({ - selector: 'worklenz-task-view-attachments-thumb', - templateUrl: './task-view-attachments-thumb.component.html', - styleUrls: ['./task-view-attachments-thumb.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskViewAttachmentsThumbComponent { - @Input() attachment: ITaskAttachmentViewModel | null = null; - @Output() onDelete: EventEmitter = new EventEmitter(); - - deleting = false; - isVisible = false; - currentFileUrl: string | null = null; - currentFileType: string | null = null; - previewWidth = 768; - downloading = false; - previewdFileId: string | null = null; - previewdFileName: string | null = null; - - constructor( - private readonly api: AttachmentsApiService, - private readonly ngZone: NgZone, - private readonly cdr: ChangeDetectorRef - ) { - } - - async download(id?: string, name?: string) { - if (!id || !name) return; - try { - this.downloading = true; - const res = await this.api.download(id, name); - if (res && res.done) { - this.ngZone.runOutsideAngular(() => { - const link = document.createElement('a'); - link.href = res.body; - link.download = name; - link.click(); - link.remove(); - }); - } - } catch (e) { - log_error(e); - this.downloading = false; - } - this.downloading = false; - this.cdr.markForCheck(); - } - - isImageFile() { - const type = this.attachment?.type; - return type === "jpeg" || type === "jpg" || type === "bmp" || type === "gif" || type === "webp" || type === "png" || type === "ico"; - } - - getFileIcon(type?: string) { - return getFileIcon(type); - } - - open(url?: string) { - if (!url) return; - this.isVisible = true; - this.cdr.markForCheck(); - } - - delete(id?: string) { - if (!id) return; - this.deleting = true; - this.onDelete.emit(id); - this.cdr.markForCheck(); - } - - handleCancel() { - this.isVisible = false; - this.previewdFileId = null; - this.previewdFileName = null; - this.cdr.markForCheck(); - } - - previewFile(fileUrl?: string, id?: string, fileName?: string) { - if (!fileUrl || !id || !fileName) return; - - this.previewdFileId = id; - this.previewdFileName = fileName; - - const extension = (fileUrl as string).split('.').pop()?.toLowerCase(); - - if (!extension) return; - this.isVisible = true; - if (this.isImage(extension)) { - this.currentFileType = 'image'; - } else if (this.isVideo(extension)) { - this.currentFileType = 'video'; - } else if (this.isAudio(extension)) { - this.previewWidth = 600; - this.currentFileType = 'audio'; - } else if (this.isDoc(extension)) { - this.currentFileType = 'document'; - } else { - this.previewWidth = 600; - this.currentFileType = 'unknown'; - } - - this.currentFileUrl = fileUrl; - this.cdr.markForCheck(); - } - - isImage(extension: string): boolean { - return ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'ico'].includes(extension); - } - - isVideo(extension: string): boolean { - return ['mp4', 'webm', 'ogg'].includes(extension); - } - - isAudio(extension: string): boolean { - return ['mp3', 'wav', 'ogg'].includes(extension); - } - - isDoc(extension: string): boolean { - this.previewWidth = 1024; - return ['ppt', 'pptx', 'doc', 'docx', 'xls', 'xlsx', 'pdf'].includes(extension); - } - -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments.component.html b/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments.component.html deleted file mode 100644 index df9a3afb..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments.component.html +++ /dev/null @@ -1,28 +0,0 @@ - - - -
- - -
-
- -
- - -
{{uploading ? 'Uploading' : 'Choose or drop file to upload'}}
-
-
-
-
- -
-
diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments.component.scss b/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments.component.scss deleted file mode 100644 index 287b2d39..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments.component.scss +++ /dev/null @@ -1,11 +0,0 @@ -.ant-upload-list { - border: 1px dashed; - border-color: transparent; - transition: 0.15s all; - padding-top: 8px; - padding-left: 8px; -} -.focused { - border-color: #1890ff; - background-color: #FAFAFA; -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments.component.spec.ts deleted file mode 100644 index 597dd9a9..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskViewAttachmentsComponent} from './task-view-attachments.component'; - -describe('TaskViewAttachmentsComponent', () => { - let component: TaskViewAttachmentsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskViewAttachmentsComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskViewAttachmentsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments.component.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments.component.ts deleted file mode 100644 index 7a658b54..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-attachments/task-view-attachments.component.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - HostListener, - OnDestroy, - OnInit, - Output -} from '@angular/core'; -import {ITaskAttachmentViewModel} from "@interfaces/api-models/task-attachment-view-model"; -import {AttachmentsApiService} from "@api/attachments-api.service"; -import {getBase64, log_error} from "@shared/utils"; -import {ITaskAttachment} from "@interfaces/api-models/task-attachment"; -import {TaskViewService} from "../task-view.service"; - -@Component({ - selector: 'worklenz-task-view-attachments', - templateUrl: './task-view-attachments.component.html', - styleUrls: ['./task-view-attachments.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskViewAttachmentsComponent implements OnInit, OnDestroy { - fileList: ITaskAttachmentViewModel[] = []; - - @Output() onFileRemoved: EventEmitter = new EventEmitter(); - @Output() onFileUploaded: EventEmitter = new EventEmitter(); // sends uploaded file id - @Output() uploadingChange: EventEmitter = new EventEmitter(); - - uploading = false; - loading = false; - - get taskId() { - return this.service.model.task?.id; - } - - get projectId() { - return this.service.model.task?.project_id; - } - - constructor( - private readonly api: AttachmentsApiService, - private readonly service: TaskViewService, - private readonly cdr: ChangeDetectorRef - ) { - } - - ngOnInit(): void { - void this.get(); - } - - ngOnDestroy() { - this.fileList = []; - } - - async get() { - if (!this.taskId) return; - try { - const res = await this.api.getTaskAttachment(this.taskId); - if (res.done) { - this.fileList = res.body; - - if (this.service.model.task) - this.service.model.task.attachments_count = this.fileList.length; - - this.cdr.detectChanges(); - } - } catch (e) { - log_error(e); - } - } - - async delete(id?: string) { - if (!id) return; - - // Send remove event for new tasks update the attachment list - this.onFileRemoved.emit(id); - - // Continue to delete from the server for previous tasks - try { - const res = await this.api.deleteTaskAttachment(id); - if (res.done) { - if (this.taskId) { - await this.get(); - this.service.emitAttachmentsChange(this.taskId, this.service.model.task?.attachments_count || 0); - } else { - const index = this.fileList.findIndex(f => f.id === id); - this.fileList.splice(index, 1); - this.cdr.detectChanges(); - } - } - } catch (e) { - log_error(e); - } - } - - async uploadFile(input: HTMLInputElement) { - const files = input.files || []; - - if (!files?.length) return; - if (!this.projectId) return; - - const file = files[0]; - - this.uploading = true; - this.uploadingChange.emit(true); - - try { - const data = await getBase64(file); - const body: ITaskAttachment = { - file: data as string, - file_name: file.name, - task_id: this.taskId, - project_id: this.projectId, - size: file.size - }; - const res = await this.api.createTaskAttachment(body); - - if (res.done && res.body) { - this.onFileUploaded.emit(res.body.id); - if (this.taskId) { - await this.get(); - this.service.emitAttachmentsChange(this.taskId, this.service.model.task?.attachments_count || 0); - } else { - this.fileList.push(res.body); - } - } - this.uploading = false; - this.uploadingChange.emit(false); - } catch (e) { - this.uploading = false; - this.uploadingChange.emit(false); - log_error(e); - } - - // Reset file input - const dt = new DataTransfer(); - input.files = dt.files; - - this.cdr.detectChanges(); - } - - async onDrop(event: any, input: HTMLInputElement, uploadBtn: HTMLDivElement) { - event.preventDefault(); - input.files = event.dataTransfer.files; - await this.uploadFile(input); - this.focusButton(false, uploadBtn); - } - onDragOver(event: any, uploadBtn: HTMLDivElement) { - event.stopPropagation(); - event.preventDefault(); - this.focusButton(true, uploadBtn); - } - - focusButton(value: boolean, btn: HTMLDivElement) { - if(value) { - btn.classList.add('focused'); - } else { - btn.classList.remove('focused'); - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments-input/task-view-comments-input.component.html b/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments-input/task-view-comments-input.component.html deleted file mode 100644 index 7604275e..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments-input/task-view-comments-input.component.html +++ /dev/null @@ -1,32 +0,0 @@ -
- - - - - - -
- - - - -
-
diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments-input/task-view-comments-input.component.scss b/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments-input/task-view-comments-input.component.scss deleted file mode 100644 index 83b2f55a..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments-input/task-view-comments-input.component.scss +++ /dev/null @@ -1,10 +0,0 @@ -textarea { - &:focus { - outline: none; - box-shadow: none; - } -} - -nz-mention { - cursor: text; -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments-input/task-view-comments-input.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments-input/task-view-comments-input.component.spec.ts deleted file mode 100644 index 23e68d76..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments-input/task-view-comments-input.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskViewCommentsInputComponent} from './task-view-comments-input.component'; - -describe('TaskViewCommentsInputComponent', () => { - let component: TaskViewCommentsInputComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskViewCommentsInputComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskViewCommentsInputComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments-input/task-view-comments-input.component.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments-input/task-view-comments-input.component.ts deleted file mode 100644 index e30aa777..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments-input/task-view-comments-input.component.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { - AfterViewInit, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - OnDestroy, - OnInit, - Output -} from '@angular/core'; -import {FormBuilder, FormGroup, Validators} from "@angular/forms"; -import {ITeamMember} from "@interfaces/team-member"; -import {ITaskCommentViewModel} from "@interfaces/api-models/task-comment-view-model"; -import {AppService} from "@services/app.service"; -import {TeamMembersApiService} from "@api/team-members-api.service"; -import {TaskCommentsApiService} from "@api/task-comments-api.service"; -import {UtilsService} from "@services/utils.service"; -import {log_error} from "@shared/utils"; -import {MentionOnSearchTypes} from "ng-zorro-antd/mention"; -import {ITaskCommentsCreateRequest} from "@interfaces/api-models/task-comments-create-request.ts"; -import {TaskViewService} from "../task-view.service"; -import {dispatchTaskCommentCreate} from "@shared/events"; -import {AuthService} from "@services/auth.service"; - -interface ITaskMention { - team_member_id: string, - name: string -} - -@Component({ - selector: 'worklenz-task-view-comments-input', - templateUrl: './task-view-comments-input.component.html', - styleUrls: ['./task-view-comments-input.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) - -export class TaskViewCommentsInputComponent implements OnInit, AfterViewInit, OnDestroy { - @Output() focusChange: EventEmitter = new EventEmitter(); - - form!: FormGroup; - - commentsInputFocused = false; - loadingMembers = false; - creatingComment = false; - teamMembers: ITeamMember[] = []; - search: string | null = null; - mentions: ITaskMention[] = []; - - - get taskId() { - return this.service.model.task?.id; - } - - constructor( - private readonly fb: FormBuilder, - private readonly app: AppService, - private readonly teamMembersApi: TeamMembersApiService, - private readonly commentsApi: TaskCommentsApiService, - private readonly cdr: ChangeDetectorRef, - private readonly service: TaskViewService, - public readonly utils: UtilsService, - private readonly auth: AuthService - ) { - this.form = this.fb.group({ - content: [null, [Validators.required, Validators.maxLength(2000)]] - }); - } - - get rows() { - return this.commentsInputFocused ? 4 : 1; - } - - valueWith = (data: { name: string; }): string => data.name; - - ngOnInit(): void { - void this.getTeamMembers(); - } - - ngAfterViewInit() { - setTimeout(() => { - this.cdr.detectChanges(); - }, 10); - } - - ngOnDestroy() { - this.teamMembers = []; - this.search = null; - this.mentions = []; - } - - async getTeamMembers() { - try { - this.loadingMembers = true; - const res = await this.teamMembersApi.get(1, 100, null, null, this.search); - if (res.done && res.body.data) { - // for (const item of res.body.data) { - // if (!item.pending_invitation) { - // this.teamMembers.push(item); - // } - // } - this.teamMembers = res.body.data.filter(t => !t.pending_invitation); - } - this.loadingMembers = false; - } catch (e) { - this.loadingMembers = false; - log_error(e); - } - - this.cdr.markForCheck(); - } - - async create() { - if (!this.taskId || !this.form.value.content) return; - try { - this.creatingComment = true; - const body: ITaskCommentsCreateRequest = { - task_id: this.taskId, - content: this.form.value.content, - mentions: [...new Set(this.mentions || [])] - }; - const res = await this.commentsApi.create(body); - if (res.done) { - this.form.reset(); - this.service.emitCommentsChange(this.taskId, 1); // emit 1 as count since the variable only used to check for comments has or not - dispatchTaskCommentCreate(); - } - this.creatingComment = false; - } catch (e) { - log_error(e); - this.creatingComment = false; - } - - this.cdr.markForCheck(); - } - - submit() { - if (this.form.valid) { - this.create(); - } else { - this.app.displayErrorsOf(this.form); - } - } - - cancel() { - this.setFocus(false); - this.form.reset(); - } - - onSearchChange({value}: MentionOnSearchTypes): void { - this.search = value; - // this.teamMembers = []; - void this.getTeamMembers(); - } - - onSelect(member: ITeamMember) { - if (!member || !member.id || !member.name) return; - if (!this.mentions.some(mention => mention.team_member_id === member.id)) - this.mentions.push( - { - team_member_id: member.id, - name: member.name - } - ); - } - - trackById(index: number, comment: ITaskCommentViewModel) { - return comment.id; - } - - setFocus(focused: boolean, input?: HTMLTextAreaElement) { - this.commentsInputFocused = focused; - this.focusChange?.emit(focused); - input?.focus(); - } - - isValid() { - return this.form.valid; - } -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments/task-view-comments.component.html b/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments/task-view-comments.component.html deleted file mode 100644 index 2490ad25..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments/task-view-comments.component.html +++ /dev/null @@ -1,21 +0,0 @@ -
- - - - - -

-
- - - Delete - - -
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments/task-view-comments.component.scss b/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments/task-view-comments.component.scss deleted file mode 100644 index 86a69dcb..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments/task-view-comments.component.scss +++ /dev/null @@ -1,13 +0,0 @@ -nz-skeleton { - margin-top: -16px; -} - -nz-comment-content p { - user-select: text; -} - -.mentions { - background: #f7f7f7; - font-weight: 500; - border-radius: 4px; -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments/task-view-comments.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments/task-view-comments.component.spec.ts deleted file mode 100644 index 44942514..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments/task-view-comments.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskViewCommentsComponent} from './task-view-comments.component'; - -describe('TaskViewCommentsComponent', () => { - let component: TaskViewCommentsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskViewCommentsComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskViewCommentsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments/task-view-comments.component.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments/task-view-comments.component.ts deleted file mode 100644 index a4279ca7..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-comments/task-view-comments.component.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostListener, - OnDestroy, - OnInit, - ViewChild -} from '@angular/core'; -import {TaskViewService} from "../task-view.service"; -import {ITaskCommentViewModel} from "@interfaces/api-models/task-comment-view-model"; -import {TaskCommentsApiService} from "@api/task-comments-api.service"; -import {UtilsService} from "@services/utils.service"; -import {log_error} from "@shared/utils"; -import {EventTaskCommentCreate} from "@shared/events"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-task-view-comments', - templateUrl: './task-view-comments.component.html', - styleUrls: ['./task-view-comments.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskViewCommentsComponent implements OnInit, OnDestroy { - @ViewChild("commentsView", {static: false}) commentsView!: ElementRef; - - loading = true; - comments: ITaskCommentViewModel[] = []; - - get taskId() { - return this.service.model.task?.id; - } - - constructor( - private readonly commentsApi: TaskCommentsApiService, - private readonly service: TaskViewService, - private readonly cdr: ChangeDetectorRef, - private readonly auth: AuthService, - public readonly utils: UtilsService, - ) { - } - - ngOnInit(): void { - void this.get(); - } - - ngOnDestroy() { - this.comments = []; - } - - @HostListener(`document:${EventTaskCommentCreate}`) - private async refreshComments() { - await this.get(false); - this.scrollIntoView(); - } - - canDelete(userId?: string) { - if (!userId) return false; - return userId === this.auth.getCurrentSession()?.id; - } - - async get(loading = true) { - if (!this.taskId) return; - try { - if (loading) - this.loading = true; - const res = await this.commentsApi.getByTaskId(this.taskId); - if (res.done) { - this.comments = res.body; - } - this.loading = false; - } catch (e) { - log_error(e); - this.loading = false; - } - - this.cdr.detectChanges(); - } - - trackById(index: number, comment: ITaskCommentViewModel) { - return comment.id; - } - - async deleteComment(id?: string) { - if (!this.taskId || !id) return; - try { - const res = await this.commentsApi.delete(id, this.taskId); - if (res.done) { - await this.get(false); - this.service.emitCommentsChange(this.taskId, this.comments.length); - this.cdr.detectChanges(); - } - } catch (e) { - log_error(e); - } - } - - public scrollIntoView() { - this.commentsView?.nativeElement.scrollIntoView(); - } -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-description/task-view-description.component.html b/worklenz-frontend/src/app/administrator/components/task-view/task-view-description/task-view-description.component.html deleted file mode 100644 index 40662d43..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-description/task-view-description.component.html +++ /dev/null @@ -1,19 +0,0 @@ - - -
- Add a more detailed description... - -
-
diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-description/task-view-description.component.scss b/worklenz-frontend/src/app/administrator/components/task-view/task-view-description/task-view-description.component.scss deleted file mode 100644 index 82474113..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-description/task-view-description.component.scss +++ /dev/null @@ -1,36 +0,0 @@ -:host { - display: block; - max-width: 640px; -} - -.description-editor-placeholder { - pointer-events: none; - user-select: none; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - display: flex; - align-items: center; -} - -.description-editor-preview { - position: relative; - min-height: 32px; - - &.empty { - display: flex; - align-items: center; - padding: 10px; - } -} - -.task-description-editor { - padding-left: 12px; - padding-right: 12px; - padding-bottom: 12px; - margin-left: -12px; - margin-right: -12px; - margin-bottom: -12px; -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-description/task-view-description.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-description/task-view-description.component.spec.ts deleted file mode 100644 index c718f05a..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-description/task-view-description.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskViewDescriptionComponent} from './task-view-description.component'; - -describe('TaskViewDescriptionComponent', () => { - let component: TaskViewDescriptionComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskViewDescriptionComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskViewDescriptionComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-description/task-view-description.component.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-description/task-view-description.component.ts deleted file mode 100644 index a567aa95..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-description/task-view-description.component.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - OnDestroy, - OnInit, - ViewChild -} from '@angular/core'; - -import {TaskViewService} from "../task-view.service"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {EditorComponent} from "@tinymce/tinymce-angular"; - -declare const tinymce: any; - -@Component({ - selector: 'worklenz-task-view-description', - templateUrl: './task-view-description.component.html', - styleUrls: ['./task-view-description.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskViewDescriptionComponent implements OnInit, OnDestroy { - @ViewChild("descriptionInput", {static: false}) descriptionInput!: ElementRef; - @ViewChild("descriptionEditor", {static: false}) descriptionEditor!: EditorComponent; - - readonly CONFIG = { - base_url: '/tinymce', - suffix: '.min', - plugins: "lists link code wordcount", - toolbar: 'blocks bold italic underline strikethrough | checklist numlist bullist link | alignleft aligncenter alignright alignjustify', - menubar: false, - content_css: "/assets/css/prebuilt-editor.css", - statusbar: true, - branding: false, - height: this.service.model.task?.description?.length ? "300" : "200", - // min_height: "100" - }; - - isEditing = false; - saving = false; - isSaveButtonActive = false; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly socket: Socket, - public readonly service: TaskViewService - ) { - } - - ngOnInit() { - this.socket.on(SocketEvents.TASK_DESCRIPTION_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_DESCRIPTION_CHANGE.toString(), this.handleResponse); - } - - isEmpty() { - return !this.service.model.task?.description?.length; - } - - onDescriptionInputBlur() { - this.toggleEdit(false); - this.handleDescriptionChange(); - } - - toggleEdit(editing = false) { - this.isEditing = editing; - if (editing) { - setTimeout(() => { - tinymce.get(this.descriptionEditor.id)?.focus(); - }); - } - - this.cdr.detectChanges(); - } - - handleDescriptionChange() { - const task = this.service.model.task; - if (!task?.id) return; - this.socket.emit(SocketEvents.TASK_DESCRIPTION_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - description: this.service.model.task?.description || null, - parent_task: task.parent_task_id, - })); - this.cdr.detectChanges(); - } - - private handleResponse = (response: { id: string; parent_task: string; description: string; }) => { - if (!response) return; - if (this.service.model.task && this.service.model.task.description != response.description) { - this.service.model.task.description = response.description; - } - setTimeout(() => this.cdr.detectChanges()); - } -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-due-date/task-view-due-date.component.html b/worklenz-frontend/src/app/administrator/components/task-view/task-view-due-date/task-view-due-date.component.html deleted file mode 100644 index 23eb29c8..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-due-date/task-view-due-date.component.html +++ /dev/null @@ -1,41 +0,0 @@ - - Due Date - -
-
- {{service.model.task.start_date | dateFormatter}} - - - -
- -
- {{service.model.task.end_date | dateFormatter}} - - - -
- - - -
-
-
diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-due-date/task-view-due-date.component.scss b/worklenz-frontend/src/app/administrator/components/task-view/task-view-due-date/task-view-due-date.component.scss deleted file mode 100644 index 6f35fe5f..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-due-date/task-view-due-date.component.scss +++ /dev/null @@ -1,28 +0,0 @@ -nz-date-picker { - max-width: 135px; -} - -.start-date-section { - // display: none; -} - -.date-text { - position: absolute; - top: 0; - bottom: 0; - left: 11px; - margin-top: auto; - margin-bottom: auto; - font-size: 14px; - line-height: 1.5715; - height: max-content; - cursor: text; -} - -.past-date { - color: #ff4d4f; -} - -.soon-date { - color: #52c41a; -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-due-date/task-view-due-date.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-due-date/task-view-due-date.component.spec.ts deleted file mode 100644 index 9674a6cb..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-due-date/task-view-due-date.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskViewDueDateComponent} from './task-view-due-date.component'; - -describe('TaskViewDueDateComponent', () => { - let component: TaskViewDueDateComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskViewDueDateComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskViewDueDateComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-due-date/task-view-due-date.component.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-due-date/task-view-due-date.component.ts deleted file mode 100644 index 742717b1..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-due-date/task-view-due-date.component.ts +++ /dev/null @@ -1,91 +0,0 @@ -import {ChangeDetectionStrategy, Component, ElementRef, ViewChild} from '@angular/core'; -import {TaskViewService} from "../task-view.service"; -import {UtilsService} from "@services/utils.service"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import moment from 'moment'; -import {formatGanttDate} from "@shared/utils"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-task-view-due-date', - templateUrl: './task-view-due-date.component.html', - styleUrls: ['./task-view-due-date.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskViewDueDateComponent { - @ViewChild('std_Section') std_Section!: ElementRef; - - private readonly START_DATE_SET_TEXT = "Show start date"; - private readonly START_DATE_RESET_TEXT = "Hide start date"; - - currentDateToggleBtnText = this.START_DATE_SET_TEXT; - - showStartDateSection = false; - - constructor( - private readonly socket: Socket, - public readonly service: TaskViewService, - public readonly utils: UtilsService, - private readonly auth: AuthService, - ) { - } - - handleStartDateChange(startDate?: string) { - const task = this.service.model.task; - if (!task?.id) return; - - this.socket.once(SocketEvents.TASK_START_DATE_CHANGE.toString(), (task: { id: string; }) => { - if (task?.id) - this.service.emitRefresh(task.id); - }); - - this.socket.emit( - SocketEvents.TASK_START_DATE_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - start_date: (startDate) || null, - parent_task: task.parent_task_id, - time_zone: this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone - })); - } - - handleEndDateChange(endDate?: string) { - const task = this.service.model.task; - if (!task?.id) return; - - this.socket.once(SocketEvents.TASK_END_DATE_CHANGE.toString(), (task: { id: string; }) => { - if (task?.id) - this.service.emitRefresh(task.id); - this.service.emitEndDateChange(); - }); - - this.socket.emit( - SocketEvents.TASK_END_DATE_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - end_date: (endDate) || null, - parent_task: task.parent_task_id, - time_zone: this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone - })); - } - - showStartDate() { - if (this.showStartDateSection) { - this.showStartDateSection = false; - this.currentDateToggleBtnText = this.START_DATE_SET_TEXT; - } else { - this.showStartDateSection = true; - this.currentDateToggleBtnText = this.START_DATE_RESET_TEXT; - } - } - - checkForPastDate(endDate: any) { - const formattedEndDate = moment(endDate).format('YYYY-MM-DD'); - return formattedEndDate < moment().format('YYYY-MM-DD'); - } - - checkForSoonDate(endDate: any) { - const formattedEndDate = moment(endDate).format('YYYY-MM-DD'); - const tomorrow = moment().add(1, 'day').format('YYYY-MM-DD'); - return formattedEndDate === moment().format('YYYY-MM-DD') || formattedEndDate === tomorrow; - } -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-estimation/task-view-estimation.component.html b/worklenz-frontend/src/app/administrator/components/task-view/task-view-estimation/task-view-estimation.component.html deleted file mode 100644 index 1333d5dc..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-estimation/task-view-estimation.component.html +++ /dev/null @@ -1,24 +0,0 @@ - - Time Estimation - -
- - Hours - - - - Minutes - - -
-
-
diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-estimation/task-view-estimation.component.scss b/worklenz-frontend/src/app/administrator/components/task-view/task-view-estimation/task-view-estimation.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-estimation/task-view-estimation.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-estimation/task-view-estimation.component.spec.ts deleted file mode 100644 index e320ed43..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-estimation/task-view-estimation.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskViewEstimationComponent} from './task-view-estimation.component'; - -describe('TaskViewEstimationComponent', () => { - let component: TaskViewEstimationComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskViewEstimationComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskViewEstimationComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-estimation/task-view-estimation.component.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-estimation/task-view-estimation.component.ts deleted file mode 100644 index 0ac2a60d..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-estimation/task-view-estimation.component.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {ChangeDetectionStrategy, Component} from '@angular/core'; -import {TaskViewService} from "../task-view.service"; -import {UtilsService} from "@services/utils.service"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; - -@Component({ - selector: 'worklenz-task-view-estimation', - templateUrl: './task-view-estimation.component.html', - styleUrls: ['./task-view-estimation.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskViewEstimationComponent { - constructor( - private readonly socket: Socket, - public readonly service: TaskViewService, - public readonly utils: UtilsService, - ) { - } - - handleTimeEstimationChange() { - const task = this.service.model.task; - if (!task?.id) return; - - this.socket.emit(SocketEvents.TASK_TIME_ESTIMATION_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - total_hours: task.total_hours || 0, - total_minutes: task.total_minutes || 0, - parent_task: task.parent_task_id, - })); - } -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-info/task-view-info.component.html b/worklenz-frontend/src/app/administrator/components/task-view/task-view-info/task-view-info.component.html deleted file mode 100644 index 08f83ab2..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-info/task-view-info.component.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - Task Key - - {{service.model.task.task_key}} - - - - - - - - - - - - - - - - - - - - -
- Sub-Tasks - -
-
-
- - - - - - - - - -
- -
- - - -
- - - Created {{service.model.task?.created_at | fromNow}} by {{service.model.task?.reporter}} - - - - - Updated {{service.model.task?.updated_at | fromNow}} - - -
-
-
diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-info/task-view-info.component.scss b/worklenz-frontend/src/app/administrator/components/task-view/task-view-info/task-view-info.component.scss deleted file mode 100644 index 2ec4a499..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-info/task-view-info.component.scss +++ /dev/null @@ -1,19 +0,0 @@ -nz-collapse-panel { - margin-bottom: 10px; -} - -.comments-block { - position: absolute; - bottom: 0; - left: 24px; - right: 24px; - z-index: 1; - background: #FAFAFA; - margin-left: -1.5rem; - margin-right: -1.5rem; - border-top: 1px solid rgba(0, 0, 0, 0.06); -} - -nz-collapse { - margin-bottom: 90px; -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-info/task-view-info.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-info/task-view-info.component.spec.ts deleted file mode 100644 index 2e2a2e1b..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-info/task-view-info.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskViewInfoComponent} from './task-view-info.component'; - -describe('TaskViewInfoComponent', () => { - let component: TaskViewInfoComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [TaskViewInfoComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskViewInfoComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-info/task-view-info.component.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-info/task-view-info.component.ts deleted file mode 100644 index 53f277d8..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-info/task-view-info.component.ts +++ /dev/null @@ -1,53 +0,0 @@ -import {ChangeDetectionStrategy, Component, Output, ViewChild} from '@angular/core'; -import {ActivatedRoute} from "@angular/router"; -import {UtilsService} from "@services/utils.service"; -import {TaskViewService} from "../task-view.service"; -import {TaskViewCommentsComponent} from "../task-view-comments/task-view-comments.component"; - -@Component({ - selector: 'worklenz-task-view-info', - templateUrl: './task-view-info.component.html', - styleUrls: ['./task-view-info.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskViewInfoComponent { - @ViewChild("commentsView", {static: false}) commentsView!: TaskViewCommentsComponent; - @Output() - commentsInputFocused = false; - - constructor( - private readonly route: ActivatedRoute, - public readonly utils: UtilsService, - public readonly service: TaskViewService - ) { - } - - isEditTask() { - return !!this.service.model.task?.id; - } - - isTaskAvailable() { - return !!this.service.model.task; - } - - getAttachmentsHeader() { - const count = this.service.model.task?.attachments_count || 0; - return `Attachments (${count})`; - } - - isSubTask() { - return !!this.service.model.task?.is_sub_task; - } - - onCommentsInputFocus(focused: boolean) { - this.commentsInputFocused = focused; - setTimeout(() => { - this.commentsView.scrollIntoView(); - }, 100); - } - - refreshSubTasks(event: MouseEvent) { - event.stopPropagation(); - this.service.emitSubTasksRefresh(); - } -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-labels/task-view-labels.component.html b/worklenz-frontend/src/app/administrator/components/task-view/task-view-labels/task-view-labels.component.html deleted file mode 100644 index 5353a0d4..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-labels/task-view-labels.component.html +++ /dev/null @@ -1,39 +0,0 @@ - - Labels - - - - {{item.name}} - - - - - - - - - - - - -
    -
  • - - - Hit enter to create! - -
  • -
  • - -
  • -
-
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-labels/task-view-labels.component.scss b/worklenz-frontend/src/app/administrator/components/task-view/task-view-labels/task-view-labels.component.scss deleted file mode 100644 index c165d177..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-labels/task-view-labels.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -.task-view-right-max-col { - max-width: 510px; -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-labels/task-view-labels.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-labels/task-view-labels.component.spec.ts deleted file mode 100644 index f9234e6b..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-labels/task-view-labels.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskViewLabelsComponent} from './task-view-labels.component'; - -describe('TaskViewLabelsComponent', () => { - let component: TaskViewLabelsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskViewLabelsComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskViewLabelsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-labels/task-view-labels.component.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-labels/task-view-labels.component.ts deleted file mode 100644 index dbca75db..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-labels/task-view-labels.component.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - NgZone, - OnDestroy, - OnInit, - ViewChild -} from '@angular/core'; -import {FormBuilder, FormGroup} from "@angular/forms"; -import {ITaskLabel} from "@interfaces/task-label"; -import {AuthService} from "@services/auth.service"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {TaskListV2Service} from "../../../modules/task-list-v2/task-list-v2.service"; -import {TaskViewService} from "../task-view.service"; -import {SearchByNamePipe} from "../../../../pipes/search-by-name.pipe"; -import {UtilsService} from "@services/utils.service"; - -@Component({ - selector: 'worklenz-task-view-labels', - templateUrl: './task-view-labels.component.html', - styleUrls: ['./task-view-labels.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskViewLabelsComponent implements OnInit, OnDestroy { - @ViewChild('labelSearchInput', {static: false}) labelSearchInput!: ElementRef; - - form!: FormGroup; - - labelsSearchText: string | null = null; - - get hasFilteredLabel() { - return !!this.filteredLabels.length; - } - - get filteredLabels() { - return this.searchPipe.transform(this.taskListService.labels, this.labelsSearchText); - } - - constructor( - private readonly socket: Socket, - private readonly fb: FormBuilder, - private readonly ref: ChangeDetectorRef, - private readonly auth: AuthService, - private readonly ngZone: NgZone, - public readonly service: TaskViewService, - public readonly taskListService: TaskListV2Service, - private readonly searchPipe: SearchByNamePipe, - private readonly utils: UtilsService - ) { - } - - ngOnInit() { - this.socket.on(SocketEvents.TASK_LABELS_CHANGE.toString(), this.handleLabelsChange); - this.socket.on(SocketEvents.CREATE_LABEL.toString(), this.handleLabelsChange); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_LABELS_CHANGE.toString(), this.handleLabelsChange); - this.socket.removeListener(SocketEvents.CREATE_LABEL.toString(), this.handleLabelsChange); - } - - private sortBySelected(labels: ITaskLabel[]) { - this.utils.sortBySelection(labels); - } - - handleLabelsVisibleChange(visible: boolean) { - if (!this.service.model.task) return; - const labels: ITaskLabel[] = this.taskListService.labels?.length ? [...this.taskListService.labels] : []; - - if (visible) { - const taskLabels = this.service.model.task.labels?.map((l) => l.id) || []; - for (const label of labels) - label.selected = taskLabels.includes(label.id); - this.focusLabelsSearchInput(); - } else { - for (const label of labels) label.selected = false; - } - - this.taskListService.labels = labels; - this.sortBySelected(labels); - this.ref.detectChanges(); - } - - private focusLabelsSearchInput() { - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.labelSearchInput?.nativeElement?.focus(); - }, 100); - }); - } - - trackById(index: number, item: any) { - return item.id; - } - - handleLabelChange(item: ITaskLabel) { - const task = this.service.model.task; - if (!task || !task?.id) return; - this.socket.emit(SocketEvents.TASK_LABELS_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - label_id: item.id, - parent_task: task.parent_task_id, - })); - if(this.service.model.task?.labels) this.sortBySelected(this.service.model.task?.labels); - this.ref.detectChanges(); - } - - private handleLabelsChange = (response: { - id: string; parent_task: string; is_new: boolean, new_label: ITaskLabel, - all_labels: ITaskLabel[], - labels: ITaskLabel[] - }) => { - if (this.service.model.task) { - this.service.model.task.labels = response.all_labels; - this.sortBySelected(this.service.model.task.labels); - this.ref.detectChanges(); - this.service.emitRefresh(response.id); - } - } - - createLabel() { - const task = this.service.model.task; - if (!task || !task.id || this.hasFilteredLabel || !this.labelsSearchText) return; - const session = this.auth.getCurrentSession(); - this.socket.emit(SocketEvents.CREATE_LABEL.toString(), JSON.stringify({ - task_id: task?.id, - label: this.labelsSearchText, - team_id: session?.team_id, - parent_task: task?.parent_task_id, - })); - this.labelsSearchText = null; - this.ref.detectChanges(); - } -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-name/task-view-name.component.html b/worklenz-frontend/src/app/administrator/components/task-view/task-view-name/task-view-name.component.html deleted file mode 100644 index 876618a7..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-name/task-view-name.component.html +++ /dev/null @@ -1,46 +0,0 @@ -
-
- - - -

{{service.model.task.name}}

-
- - - - {{service.model.task.name?.length || 0}} / {{MAX_LEN}} - - -
-
-
-
- - - - - - - -
-
diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-name/task-view-name.component.scss b/worklenz-frontend/src/app/administrator/components/task-view/task-view-name/task-view-name.component.scss deleted file mode 100644 index af3695f3..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-name/task-view-name.component.scss +++ /dev/null @@ -1,52 +0,0 @@ -.task-name-input, .name-text { - font-size: 16px; - font-weight: 500; - transition: none; - border: 1px solid transparent; - padding-left: 7px; - padding-right: 7px; - - &:hover, &:focus { - cursor: text; - border: 1px solid #d9d9d9; - border-radius: 4px; - } - - &:focus { - border: 1px solid #1890ff; - } -} - -.name-text { - padding-top: 4px; - padding-bottom: 4px; -} - -.length-alert { - position: absolute; - right: 0; - bottom: -15px; - - &.warn { - color: red; - } -} - -.error { - border: 1px solid red !important; -} - -.custom-status-pill { - max-width: 120px; - min-width: 100px; - text-align: center; - - & nz-select-top-control { - padding-left: 6px; - padding-right: 6px; - } - - & * { - max-width: 120px; - } -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-name/task-view-name.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-name/task-view-name.component.spec.ts deleted file mode 100644 index 0ea4b274..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-name/task-view-name.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskViewNameComponent} from './task-view-name.component'; - -describe('TaskViewNameComponent', () => { - let component: TaskViewNameComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskViewNameComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskViewNameComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-name/task-view-name.component.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-name/task-view-name.component.ts deleted file mode 100644 index 67998833..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-name/task-view-name.component.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - NgZone, - OnDestroy, - OnInit, - Renderer2, - ViewChild -} from '@angular/core'; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskViewModel} from "@interfaces/task-form-view-model"; -import {TaskViewService} from "../task-view.service"; -import {DEFAULT_TASK_NAME} from "@shared/constants"; -import {ITaskListStatusChangeResponse} from "@interfaces/task-list-status-change-response"; -import {TaskListV2Service} from "../../../modules/task-list-v2/task-list-v2.service"; -import { - RoadmapV2Service -} from "../../../modules/roadmap-v2/project-roadmap-v2-custom/services/roadmap-v2-service.service"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-task-view-name', - templateUrl: './task-view-name.component.html', - styleUrls: ['./task-view-name.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskViewNameComponent implements OnInit, OnDestroy { - @ViewChild("input", {static: false}) input!: ElementRef; - - protected readonly MAX_LEN = 250; - private readonly INPUT_FOCUSED_CLS = "input-focused"; - - showTaskNameInput = false; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly socket: Socket, - private readonly ngZone: NgZone, - private readonly renderer: Renderer2, - public readonly service: TaskViewService, - private readonly list: TaskListV2Service, - private readonly roadmapService: RoadmapV2Service, - private readonly auth: AuthService, - ) { - } - - ngOnInit(): void { - this.socket.on(SocketEvents.TASK_NAME_CHANGE.toString(), this.handleNameChangeResponse); - this.socket.on(SocketEvents.TASK_STATUS_CHANGE.toString(), this.handleStatusChangeResponse); - - this.autoFocusNameForNewTasks(); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_NAME_CHANGE.toString(), this.handleNameChangeResponse); - this.socket.removeListener(SocketEvents.TASK_STATUS_CHANGE.toString(), this.handleStatusChangeResponse); - } - - handleNameChange(task?: ITaskViewModel) { - if (!task) return; - this.socket.emit(SocketEvents.TASK_NAME_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - name: task.name, - parent_task: task.parent_task_id - })); - } - - private handleNameChangeResponse = (response: { id: string; parent_task: string; name: string; }) => { - if (!response || !this.service.model) return; - if (this.service.model.task && this.service.model.task.name != response.name) { - this.service.model.task.name = response.name; - this.cdr.detectChanges(); - } - - this.service.emitRefresh(response.id); - } - - handleStatusChange(statusId: string, task: ITaskViewModel) { - this.socket.emit(SocketEvents.TASK_STATUS_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - status_id: statusId, - parent_task: task.parent_task_id, - team_id: this.auth.getCurrentSession()?.team_id - })); - - // to update the parent task's progress - this.socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), task.id); - this.cdr.detectChanges(); - } - - private handleStatusChangeResponse = (response: ITaskListStatusChangeResponse) => { - if (this.service.model?.task && response.color_code) { - this.service.model.task.status_color = response.color_code; - this.service.emitRefresh(response.id); - this.service.emitStatusChange(); - if (this.list.getCurrentGroup().value === this.list.GROUP_BY_STATUS_VALUE && this.list.isSubtasksIncluded) { - this.list.emitRefreshSubtasksIncluded(); - } - if (this.list.getCurrentGroup().value === this.list.GROUP_BY_STATUS_VALUE && this.list.isSubtasksIncluded) { - this.list.emitRefreshSubtasksIncluded(); - } - if (this.roadmapService.getCurrentGroup().value === this.roadmapService.GROUP_BY_STATUS_VALUE) { - this.roadmapService.onGroupChange(response.id, response.status_id as string) - } - this.cdr.markForCheck(); - } - } - - private autoFocusNameForNewTasks() { - if (this.service.model.task?.name === DEFAULT_TASK_NAME) { - this.ngZone.runOutsideAngular(() => { - this.showTaskNameInput = true; - setTimeout(() => { - this.input?.nativeElement.focus(); - this.input?.nativeElement.select(); - }, 100); - }); - } - } - - addFocusCls(input: HTMLInputElement) { - this.renderer.addClass(input, this.INPUT_FOCUSED_CLS); - } - - focusInput() { - setTimeout(() => { - this.input.nativeElement.focus(); - }, 50) - } - - removeFocusCls(input: HTMLInputElement) { - this.renderer.removeClass(input, this.INPUT_FOCUSED_CLS); - } - - hasCls(input: HTMLInputElement) { - return input.classList.contains(this.INPUT_FOCUSED_CLS); - } -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-notify-to-user/task-view-notify-to-user.component.html b/worklenz-frontend/src/app/administrator/components/task-view/task-view-notify-to-user/task-view-notify-to-user.component.html deleted file mode 100644 index 5f063b09..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-notify-to-user/task-view-notify-to-user.component.html +++ /dev/null @@ -1,56 +0,0 @@ - - When done, notify - -
-
- - - - - - - - -
- - -
    -
  • - -
  • -
  • -
    - -
    - {{item.name}} - - {{item.email}} - (Pending Invitation) - -
    -
    -
  • -
-
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-notify-to-user/task-view-notify-to-user.component.scss b/worklenz-frontend/src/app/administrator/components/task-view/task-view-notify-to-user/task-view-notify-to-user.component.scss deleted file mode 100644 index 7ea94ff9..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-notify-to-user/task-view-notify-to-user.component.scss +++ /dev/null @@ -1,17 +0,0 @@ -.disable { - position: relative; - &:after { - position: absolute; - content: ""; - background: #e7e7e769; - top: 0; - left: 0; - right: 0; - bottom: 0; - width: 100%; - } -} - -.z-top { - z-index: 9; -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-notify-to-user/task-view-notify-to-user.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-notify-to-user/task-view-notify-to-user.component.spec.ts deleted file mode 100644 index e31dfc12..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-notify-to-user/task-view-notify-to-user.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskViewNotifyToUserComponent} from './task-view-notify-to-user.component'; - -describe('TaskViewNotifyToUserComponent', () => { - let component: TaskViewNotifyToUserComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskViewNotifyToUserComponent] - }); - fixture = TestBed.createComponent(TaskViewNotifyToUserComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-notify-to-user/task-view-notify-to-user.component.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-notify-to-user/task-view-notify-to-user.component.ts deleted file mode 100644 index 18d08f98..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-notify-to-user/task-view-notify-to-user.component.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - NgZone, - OnDestroy, - OnInit, - ViewChild -} from '@angular/core'; -import {ITeamMemberViewModel} from "@interfaces/api-models/team-members-get-response"; -import {AuthService} from "@services/auth.service"; -import {Socket} from "ngx-socket-io"; -import {TaskViewService} from "../task-view.service"; -import {InlineMember} from "@interfaces/api-models/inline-member"; -import {TeamMembersApiService} from "@api/team-members-api.service"; -import {TasksApiService} from "@api/tasks-api.service"; -import {SocketEvents} from "@shared/socket-events"; -import {UtilsService} from "@services/utils.service"; - -type TaskSubscriber = InlineMember; -type TeamMember = ITeamMemberViewModel; - -@Component({ - selector: 'worklenz-task-view-notify-to-user', - templateUrl: './task-view-notify-to-user.component.html', - styleUrls: ['./task-view-notify-to-user.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskViewNotifyToUserComponent implements OnInit, OnDestroy { - @ViewChild('searchInput', {static: false}) searchInput!: ElementRef; - searchText: string | null = null; - - subscribers: TaskSubscriber[] = []; - members: TeamMember[] = []; - - loadingSubscribers = false; - loadingMembers = false; - - get loading() { - return this.loadingSubscribers || this.loadingMembers; - } - - constructor( - private readonly auth: AuthService, - private readonly membersApi: TeamMembersApiService, - private readonly api: TasksApiService, - private readonly socket: Socket, - private readonly ngZone: NgZone, - private readonly cdr: ChangeDetectorRef, - public readonly service: TaskViewService, - private readonly utils: UtilsService, - ) { - } - - async ngOnInit() { - await this.getSubscribers(); - await this.getMembers(); - this.markSubscribers(); - this.cdr.markForCheck(); - - this.socket.on(SocketEvents.TASK_SUBSCRIBERS_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_SUBSCRIBERS_CHANGE.toString(), this.handleResponse); - } - - private async getMembers() { - try { - this.loadingMembers = true; - const res = await this.membersApi.getAll(); - if (res.done) { - this.members = res.body; - } - this.loadingMembers = false; - } catch (e) { - this.loadingMembers = false; - } - } - - private async getSubscribers() { - const taskId = this.service.model.task?.id; - if (!taskId) return; - - try { - this.loadingSubscribers = true; - const res = await this.api.getTaskSubscribers(taskId); - if (res.done) { - this.subscribers = res.body; - } - this.loadingSubscribers = false; - } catch (e) { - this.loadingSubscribers = false; - } - } - - private markSubscribers() { - const subscriberIds = this.subscribers.map(s => s.team_member_id); - for (const member of this.members) { - member.selected = subscriberIds.includes(member.id); - } - } - - trackById(index: number, item: ITeamMemberViewModel) { - return item.id; - } - - private focusInput() { - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.searchInput?.nativeElement?.focus(); - }, 100); // wait for dropdown animation - }); - } - - handleMembersVisibleChange(visible: boolean) { - if (!this.service.model.task) return; - if (visible) { - this.focusInput(); - this.sortMembersBySelection(this.members); - } else { - this.searchText = null; - } - } - - handleMemberChange(item: ITeamMemberViewModel, selected: boolean) { - const task = this.service.model.task; - if (!task) return; - const body = { - team_member_id: item.id, - task_id: task.id, - user_id: item.user_id, - mode: selected ? 0 : 1 - }; - this.socket.emit(SocketEvents.TASK_SUBSCRIBERS_CHANGE.toString(), body); - } - - private handleResponse = (response: TaskSubscriber[]) => { - if (!response) return; - this.subscribers = response; - const task = this.service.model.task; - if (task?.id) - this.service.emitOnTaskSubscriberChange(task.id, response.length) - this.sortMembersBySelection(this.members); - this.cdr.markForCheck(); - } - - private sortMembersBySelection(members: ITeamMemberViewModel[]) { - this.utils.sortBySelection(members); - this.utils.sortByPending(members); - } - -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-phase/task-view-phase.component.html b/worklenz-frontend/src/app/administrator/components/task-view/task-view-phase/task-view-phase.component.html deleted file mode 100644 index 9985d4bf..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-phase/task-view-phase.component.html +++ /dev/null @@ -1,22 +0,0 @@ - - Phase - - - - {{item.name}} - - - -
{{selected.nzLabel}}
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-phase/task-view-phase.component.scss b/worklenz-frontend/src/app/administrator/components/task-view/task-view-phase/task-view-phase.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-phase/task-view-phase.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-phase/task-view-phase.component.spec.ts deleted file mode 100644 index 46410b85..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-phase/task-view-phase.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskViewPhaseComponent} from './task-view-phase.component'; - -describe('TaskViewPhaseComponent', () => { - let component: TaskViewPhaseComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskViewPhaseComponent] - }); - fixture = TestBed.createComponent(TaskViewPhaseComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-phase/task-view-phase.component.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-phase/task-view-phase.component.ts deleted file mode 100644 index 74e2063e..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-phase/task-view-phase.component.ts +++ /dev/null @@ -1,55 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component} from '@angular/core'; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {TaskListV2Service} from "../../../modules/task-list-v2/task-list-v2.service"; -import { - RoadmapV2Service -} from "../../../modules/roadmap-v2/project-roadmap-v2-custom/services/roadmap-v2-service.service"; - -@Component({ - selector: 'worklenz-task-view-phase', - templateUrl: './task-view-phase.component.html', - styleUrls: ['./task-view-phase.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskViewPhaseComponent { - get task() { - return this.service.model.task; - } - - get phases() { - return this.service.model.phases ?? []; - } - - constructor( - private readonly socket: Socket, - private readonly service: TaskViewService, - private readonly list: TaskListV2Service, - private readonly roadmapService: RoadmapV2Service, - private readonly cdr: ChangeDetectorRef - ) { - } - - handleChange(phaseId: string, taskId?: string) { - if (!taskId) return; - this.socket.emit(SocketEvents.TASK_PHASE_CHANGE.toString(), { - task_id: taskId, - phase_id: phaseId, - parent_task: this.service.model.task?.parent_task_id || null - }); - - this.socket.once(SocketEvents.TASK_PHASE_CHANGE.toString(), () => { - this.service.emitPhaseChange(); - - if(this.list.getCurrentGroup().value === this.list.GROUP_BY_PHASE_VALUE && this.list.isSubtasksIncluded) { - this.list.emitRefreshSubtasksIncluded(); - } - - if (this.roadmapService.getCurrentGroup().value === this.roadmapService.GROUP_BY_PHASE_VALUE) { - this.roadmapService.onGroupChange(taskId, phaseId as string) - } - this.cdr.markForCheck(); - }); - } -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-priority/task-view-priority.component.html b/worklenz-frontend/src/app/administrator/components/task-view/task-view-priority/task-view-priority.component.html deleted file mode 100644 index 2ff58352..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-priority/task-view-priority.component.html +++ /dev/null @@ -1,23 +0,0 @@ - - Priority - - - - - - - -
- -
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-priority/task-view-priority.component.scss b/worklenz-frontend/src/app/administrator/components/task-view/task-view-priority/task-view-priority.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-priority/task-view-priority.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-priority/task-view-priority.component.spec.ts deleted file mode 100644 index b543d2b2..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-priority/task-view-priority.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskViewPriorityComponent} from './task-view-priority.component'; - -describe('TaskViewPriorityComponent', () => { - let component: TaskViewPriorityComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskViewPriorityComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskViewPriorityComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-priority/task-view-priority.component.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-priority/task-view-priority.component.ts deleted file mode 100644 index dcbfdd7c..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-priority/task-view-priority.component.ts +++ /dev/null @@ -1,62 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit} from '@angular/core'; -import {TaskViewService} from "../task-view.service"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {TaskListV2Service} from "../../../modules/task-list-v2/task-list-v2.service"; -import { - RoadmapV2Service -} from "../../../modules/roadmap-v2/project-roadmap-v2-custom/services/roadmap-v2-service.service"; - -@Component({ - selector: 'worklenz-task-view-priority', - templateUrl: './task-view-priority.component.html', - styleUrls: ['./task-view-priority.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) - -export class TaskViewPriorityComponent implements OnInit, OnDestroy { - constructor( - private readonly socket: Socket, - public readonly service: TaskViewService, - private readonly list: TaskListV2Service, - private readonly cdr: ChangeDetectorRef, - private readonly roadmapService: RoadmapV2Service - ) { - } - - ngOnInit() { - this.socket.on(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.handleResponse) - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.handleResponse) - } - - handlePriorityChange(priorityId: string) { - const task = this.service.model.task; - if (!task || !task.id) return; - this.socket.emit(SocketEvents.TASK_PRIORITY_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - priority_id: priorityId, - parent_task: task.parent_task_id, - })); - this.service.emitRefresh(task.id); - if (this.list.getCurrentGroup().value === this.list.GROUP_BY_PRIORITY_VALUE && this.list.isSubtasksIncluded) { - this.list.emitRefreshSubtasksIncluded(); - } - this.cdr.detectChanges(); - } - - private handleResponse = (response: { - priority_id: string | undefined; - name: string | undefined; id: string; parent_task: string; color_code: string; - }) => { - if (response && response.id) { - if (this.roadmapService.getCurrentGroup().value === this.roadmapService.GROUP_BY_PRIORITY_VALUE) { - this.roadmapService.onGroupChange(response.id, response.priority_id as string) - } - this.cdr.markForCheck(); - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-sub-tasks/task-view-sub-tasks.component.html b/worklenz-frontend/src/app/administrator/components/task-view/task-view-sub-tasks/task-view-sub-tasks.component.html deleted file mode 100644 index 1ab82bb6..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-sub-tasks/task-view-sub-tasks.component.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - {{data.name}} - - {{data.priority_name}} - - - - {{data.status_name}} - - - - - - - - - -
- - - - -
- - - - - - - - - - - - - - - - - - - -
-
-
- - diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-sub-tasks/task-view-sub-tasks.component.scss b/worklenz-frontend/src/app/administrator/components/task-view/task-view-sub-tasks/task-view-sub-tasks.component.scss deleted file mode 100644 index d3e1d12d..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-sub-tasks/task-view-sub-tasks.component.scss +++ /dev/null @@ -1,4 +0,0 @@ -.sub-tasks-progress { - position: relative; - top: -10px; -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-sub-tasks/task-view-sub-tasks.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-sub-tasks/task-view-sub-tasks.component.spec.ts deleted file mode 100644 index 672c64e6..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-sub-tasks/task-view-sub-tasks.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskViewSubTasksComponent} from './task-view-sub-tasks.component'; - -describe('TaskViewSubTasksComponent', () => { - let component: TaskViewSubTasksComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskViewSubTasksComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskViewSubTasksComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-sub-tasks/task-view-sub-tasks.component.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-sub-tasks/task-view-sub-tasks.component.ts deleted file mode 100644 index b62a1409..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-sub-tasks/task-view-sub-tasks.component.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - OnDestroy, - OnInit, - Output, - ViewChild -} from '@angular/core'; -import {ISubTask} from "@interfaces/sub-task"; -import {SubTasksApiService} from "@api/sub-tasks-api.service"; -import {TasksApiService} from "@api/tasks-api.service"; -import {AvatarNamesMap} from "@shared/constants"; -import {log_error} from "@shared/utils"; -import {dispatchTasksChange} from "@shared/events"; -import {TaskViewService} from "../task-view.service"; -import {SocketEvents} from "@shared/socket-events"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {Socket} from "ngx-socket-io"; -import {TaskListV2Service} from "../../../modules/task-list-v2/task-list-v2.service"; -import {TaskListHashMapService} from "../../../modules/task-list-v2/task-list-hash-map.service"; -import {Subject, takeUntil} from "rxjs"; -import { - TaskListAddTaskInputComponent -} from "../../../modules/task-list-v2/task-list-table/task-list-add-task-input/task-list-add-task-input.component"; -import {KanbanV2Service} from 'app/administrator/modules/kanban-view-v2/kanban-view-v2.service'; -import {ITaskListStatusChangeResponse} from "@interfaces/task-list-status-change-response"; -import { - RoadmapV2Service -} from "../../../modules/roadmap-v2/project-roadmap-v2-custom/services/roadmap-v2-service.service"; - -@Component({ - selector: 'worklenz-task-view-sub-tasks', - templateUrl: './task-view-sub-tasks.component.html', - styleUrls: ['./task-view-sub-tasks.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskViewSubTasksComponent implements OnInit, OnDestroy { - @ViewChild('subTaskInput', {static: false}) subTaskInput!: TaskListAddTaskInputComponent; - - @Input() taskName: string | null = null; // parent task name - @Input() projectId: string | null = null; - - // eslint-disable-next-line @angular-eslint/no-output-on-prefix - @Output() onCreateOrUpdate = new EventEmitter(); - - groupId: string | null = null; - - loading = false; - deleting = false; - - tasks: ISubTask[] = []; - - get taskId() { - return this.service.model.task?.id || null; - } - - private readonly destroy$ = new Subject(); - - constructor( - private readonly api: SubTasksApiService, - private readonly tasksApi: TasksApiService, - private readonly ref: ChangeDetectorRef, - private readonly socket: Socket, - private readonly list: TaskListV2Service, - private readonly map: TaskListHashMapService, - public readonly service: TaskViewService, - public readonly kanbanService: KanbanV2Service, - private readonly roadmapService: RoadmapV2Service - ) { - this.service.onRefreshSubTasks() - .pipe( - takeUntil(this.destroy$) - ) - .subscribe(() => { - void this.get(); - }); - } - - ngOnInit(): void { - if (this.taskId) { - this.groupId = this.map.getGroupId(this.taskId) as string; - } - - void this.get(); - this.socket.on(SocketEvents.QUICK_TASK.toString(), this.handleNewTaskReceive); - this.socket.on(SocketEvents.TASK_STATUS_CHANGE.toString(), this.handleStatusChangeResponse); - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - this.socket.removeListener(SocketEvents.QUICK_TASK.toString(), this.handleNewTaskReceive); - this.socket.removeListener(SocketEvents.TASK_STATUS_CHANGE.toString(), this.handleStatusChangeResponse); - this.tasks = []; - } - - getColor(name?: string) { - return AvatarNamesMap[name?.charAt(0).toUpperCase() || 'A']; - } - - async get() { - if (!this.taskId) return; - try { - this.loading = true; - this.ref.detectChanges(); - const res = await this.api.get(this.taskId); - if (res.done) { - this.tasks = res.body; - } - this.loading = false; - } catch (e) { - log_error(e); - this.loading = false; - } - - this.ref.detectChanges(); - } - - async deleteTask(id?: string, data?: any) { - if (!id) return; - try { - this.deleting = true; - const res = await this.tasksApi.delete(id); - if (res.done) { - - const deletedTask: IProjectTask = { - parent_task_id: data.parent_task_id, - } - - const count = this.service.model.task?.sub_tasks_count || 0; - if (this.service.model.task) - this.service.model.task.sub_tasks_count = Math.max(count - 1, 0); - - this.list.deleteTask(id); - - this.list.emitRefresh(); - - this.kanbanService.emitDeleteSubTask(deletedTask); - - this.roadmapService.deleteSubtaskFromView(id) - - this.socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), data.parent_task_id); - - dispatchTasksChange(); - - await this.get(); - } - this.deleting = false; - } catch (e) { - this.deleting = false; - log_error(e); - } - } - - editTask(data: ISubTask) { - if (!data?.id) return; - this.service.emitSubTaskSelect(data); - } - - onTaskCreateOrUpdate() { - this.get(); - this.onCreateOrUpdate.emit(); - } - - trackBy(index: number, item: ISubTask): any { - return item?.id; - } - - private handleNewTaskReceive = (response: IProjectTask) => { - if (!response) return; - - // Clone and push to trigger a change detection - const tasks = [...this.tasks]; - tasks.push(response); - this.tasks = tasks; - - const parentTask = this.service.model.task; - if (parentTask) { - parentTask.complete_ratio = Number(response.complete_ratio) || 0; - parentTask.sub_tasks_count = Number(parentTask.sub_tasks_count) || 0; - parentTask.sub_tasks_count += 1; - - if (this.service.model.task) { - this.service.model.task.sub_tasks_count = Number(this.service.model.task.sub_tasks_count) || 0; - this.service.model.task.sub_tasks_count += 1; - } - - this.service.model.task = parentTask; - - if (parentTask.id) { - const groupId = this.map.getGroupId(parentTask.id); - if (groupId) - this.list.addTask(response, groupId); - } - } - - this.subTaskInput?.reset(false); - this.ref.detectChanges(); - } - - private handleStatusChangeResponse = (response: ITaskListStatusChangeResponse) => { - if (this.service.model.task && response) { - - this.service.model.task.complete_ratio = response.complete_ratio; - this.ref.detectChanges(); - } - } - - getSubTasksProgress() { - const ratio = this.service.model.task?.complete_ratio || 0; - return ratio == Infinity ? 0 : ratio; - } -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-time-log/task-view-time-log.component.html b/worklenz-frontend/src/app/administrator/components/task-view/task-view-time-log/task-view-time-log.component.html deleted file mode 100644 index 06ed01d9..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-time-log/task-view-time-log.component.html +++ /dev/null @@ -1,164 +0,0 @@ - - -
- Total Logged: {{totalLogged}} -
- - -
-
- - - -
-
- -
- No time logs found in the task. -
- -
-
- - - - - - - - - - {{item.user_name}} logged {{item.time_spent_text}} {{item.logged_by_timer ? 'via Timer' : ''}} - about {{item.created_at | fromNow}} - - - - - - - - {{item.start_time | date: 'MMM d, y, h:mm a'}} - {{item.end_time | date: 'MMM d, y, h:mm a'}} - - - - - {{item.start_time | date: 'MMM d, y, h:mm a'}} - {{item.end_time | date: 'MMM d, y, h:mm a'}} - - - - - - -
- {{ item.description }} -
- -
-
    - - - - - - -
-
- -
-
-
- - -
- - - - - - -
-
- - Date - - - - -
-
- - Start time - - - - -
-
- - End time - - - - -
- - {{errorText}} - -
-
-
- - Work description - - - - -
-
- - - - -
-
-
- -
-
diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-time-log/task-view-time-log.component.scss b/worklenz-frontend/src/app/administrator/components/task-view/task-view-time-log/task-view-time-log.component.scss deleted file mode 100644 index 37c64183..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-time-log/task-view-time-log.component.scss +++ /dev/null @@ -1,73 +0,0 @@ -nz-list-item-meta-title, -nz-list-item-meta-description, -.log-description { - user-select: text; -} - -nz-list-item { - border-radius: 4px; - - * { - transition: none !important; - } - - [nz-list-item-actions] { - visibility: hidden; - } - - &:hover { - background: #fafafa; - - [nz-list-item-actions] { - visibility: visible; - } - } -} - -.log-form { - background: #FAFAFA; - margin-left: -1.5rem; - margin-right: -1.5rem; - padding: 1.0rem 1.5rem; - border-top: 1px solid rgba(0, 0, 0, 0.06); -} - -nz-affix { - position: absolute; - bottom: 0; - left: 24px; - right: 24px; - z-index: 1; -} - -.highlight { - border-bottom: 1px solid #1890ff !important; - border: 1px solid #1890ff; -} - -.no-data-img-holder { - width: 100px; -} - -.item-meta-title { - font-size: 15px !important; -} - -.item-meta-description { - color: rgba(0, 0, 0, 0.45); - font-size: 13px !important; -} - -.description-container { - margin-left: 42px; - margin-top: 4px; -} - -.error-text { - position: absolute; - bottom: -16px; - font-size: 13px; - margin-left: 12px; - animation-name: slideDown; - animation-duration: 0.125s; -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-time-log/task-view-time-log.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-time-log/task-view-time-log.component.spec.ts deleted file mode 100644 index 0271dad5..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-time-log/task-view-time-log.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskViewTimeLogComponent} from './task-view-time-log.component'; - -describe('TaskViewTimeLogComponent', () => { - let component: TaskViewTimeLogComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskViewTimeLogComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskViewTimeLogComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view-time-log/task-view-time-log.component.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view-time-log/task-view-time-log.component.ts deleted file mode 100644 index 74f75707..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view-time-log/task-view-time-log.component.ts +++ /dev/null @@ -1,452 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Input, - NgZone, - OnDestroy, - OnInit, - ViewChild -} from '@angular/core'; -import {FormBuilder, FormGroup} from "@angular/forms"; -import {ITaskLogViewModel} from "@interfaces/api-models/task-log-create-request"; -import {UtilsService} from "@services/utils.service"; -import {TasksLogTimeService} from "@api/tasks-log-time.service"; -import {AppService} from "@services/app.service"; -import {log_error} from "@shared/utils"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import moment from "moment/moment"; -import {NzInputNumberComponent} from "ng-zorro-antd/input-number"; -import {AuthService} from "@services/auth.service"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {ITimerStopEventResponse} from "@interfaces/timer-stop-event-response"; -import {dispatchTasksChange} from "@shared/events"; -import {TaskTimerService} from "@admin/components/task-timer/task-timer.service"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {ITaskAssigneesUpdateResponse} from "@interfaces/task-assignee-update-response"; -import {differenceInCalendarDays} from 'date-fns'; -import {NzTimePickerComponent} from "ng-zorro-antd/time-picker"; -import momentTime from "moment-timezone"; - -@Component({ - selector: 'worklenz-task-view-time-log', - templateUrl: './task-view-time-log.component.html', - styleUrls: ['./task-view-time-log.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskViewTimeLogComponent implements OnInit, OnDestroy { - @ViewChild("hoursInput", {static: false}) hoursInput!: NzInputNumberComponent; - @ViewChild("startInput", {static: false}) startInput!: NzTimePickerComponent; - - @Input() taskId: string | null = null; - @Input() parentTaskId!: string | undefined; - - @Input() timerStartTime: number | null = null; - @Input() projectId: string | null = null; - - form!: FormGroup; - - max = 1000; - min = 0; - - loading = false; - loadingLogs = false; - showForm = false; - exporting = false; - errorDatePair = false; - - errorText = "" - - editId: string | null = null; - - timeLogs: ITaskLogViewModel[] = []; - - private _totalLogged = 0; - - disabledStartHours = () => { - if (this.form.value.end) { - const top = new Date(this.form.value.end).getHours() + 1; - const body: number[] = []; - for (let i = top; i <= 24; i++) { - body.push(i); - } - return body; - } - return []; - }; - - disabledStartMinutes = (endHour: number) => { - if (this.form.value.end) { - const hour = new Date(this.form.value.end).getHours(); - const body: number[] = []; - if (endHour === hour) { - const top = new Date(this.form.value.end).getMinutes(); - for (let i = top; i <= 60; i++) { - body.push(i); - } - } - return body; - } - return []; - }; - - disabledEndHours = () => { - if (this.form.value.start) { - const top = new Date(this.form.value.start).getHours() - 1; - const body: number[] = []; - for (let i = 0; i <= top; i++) { - body.push(i); - } - return body; - } - return []; - }; - - disabledEndMinutes = (startHour: number) => { - if (this.form.value.start) { - const hour = new Date(this.form.value.start).getHours(); - const body: number[] = []; - if (startHour === hour) { - const top = new Date(this.form.value.start).getMinutes(); - for (let i = 0; i <= top; i++) { - body.push(i); - } - } - return body; - } - return []; - }; - - compareDates() { - if (this.form.value.start && this.form.value.end) { - - const momentStartTime = moment({ - year: 2000, - month: 1, - day: 1, - hour: new Date(this.form.value.start).getHours(), - minute: new Date(this.form.value.start).getMinutes(), - second: 0 - }); - - const momentEndTime = moment({ - year: 2000, - month: 1, - day: 1, - hour: new Date(this.form.value.end).getHours(), - minute: new Date(this.form.value.end).getMinutes(), - second: 0 - }); - - if (momentStartTime.isAfter(momentEndTime, 'minute')) { - this.errorText = "Start time cannot be larger than End time."; - this.errorDatePair = true; - this.cdr.markForCheck(); - return true; - } - - // if (new Date(this.form.value.start) > new Date(this.form.value.end)) { - // this.errorText = "Start time cannot be larger than End time."; - // this.errorDatePair = true; - // this.cdr.markForCheck(); - // return true; - // } - - if (!this.form.value.date) { - this.errorText = "Select date first."; - this.errorDatePair = true; - this.cdr.markForCheck(); - return true; - } - return false; - } - return true; - } - - get totalLogged() { - const duration = moment.duration(this._totalLogged, "seconds"); - return this.formatDuration(duration); - } - - constructor( - private readonly fb: FormBuilder, - private readonly api: TasksLogTimeService, - private readonly app: AppService, - private readonly cdr: ChangeDetectorRef, - public readonly service: TaskViewService, - private readonly auth: AuthService, - private readonly socket: Socket, - private readonly ngZone: NgZone, - private readonly timerService: TaskTimerService, - public readonly utils: UtilsService, - ) { - this.form = this.fb.group({ - description: [], - date: [], - start: [null], - end: [null] - }); - } - - ngOnInit(): void { - void this.get(); - this.socket.on(SocketEvents.TASK_TIMER_STOP.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_TIMER_STOP.toString(), this.handleResponse); - this.editId = null; - this.timeLogs = []; - } - - disabledDate = (current: Date): boolean => - differenceInCalendarDays(current, new Date()) > 0; - - quickAssignMember(session: ILocalSession) { - const task = this.service.model.task; - if (!task) return; - const body = { - team_member_id: session.team_member_id, - project_id: this.projectId, - task_id: task.id, - reporter_id: session?.id, - mode: 0, - parent_task: task.parent_task_id - }; - this.socket.emit(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), JSON.stringify(body)); - this.socket.once(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), (response: ITaskAssigneesUpdateResponse) => { - if (session.team_member_id) { - this.service.emitTimeLogAssignMember(response); - this.service.emitSingleMemberChange(session.team_member_id); - } - }) - this.cdr.markForCheck(); - } - - async submit() { - if (this.form.valid) { - if (!this.taskId) return; - if (this.compareDates()) return; - try { - this.loading = true; - const session = this.auth.getCurrentSession(); - const assignees = this.service.model.task?.assignees as string[]; - if (session && !assignees.includes(session?.team_member_id as string)) this.quickAssignMember(session); - - const requestBody = await this.createReqBody(); - if (!requestBody) return; - - const res = this.editId - ? await this.api.update(this.editId, this.mapToRequest(), requestBody) - : await this.api.create(this.mapToRequest(), requestBody); - if (res.done) { - this.setFormVisibility(false); - await this.get(); - this.timerService.emitSubmitOrUpdate(); - } - this.loading = false; - } catch (e) { - log_error(e); - this.loading = false; - } - } else { - this.app.displayErrorsOf(this.form); - } - - this.cdr.detectChanges(); - } - - async get() { - if (!this.taskId) return; - try { - this._totalLogged = 0; - this.loadingLogs = true; - const res = await this.api.getByTask(this.taskId, this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name as string : Intl.DateTimeFormat().resolvedOptions().timeZone); - if (res.done) { - this.buildText(res); - this.timeLogs = res.body; - } - this.loadingLogs = false; - } catch (e) { - this.loadingLogs = false; - } - - this.cdr.detectChanges(); - } - - formatDuration(duration: moment.Duration) { - const empty = "0h 0m"; - let format = ""; - - if (duration.asMilliseconds() === 0) return empty; - - const h = ~~(duration.asHours()); - const m = duration.minutes(); - const s = duration.seconds(); - - if (h === 0 && s > 0) { - format = `${m}m ${s}s`; - } else if (h > 0 && s === 0) { - format = `${h}h ${m}m`; - } else if (h > 0 && s > 0) { - format = `${h}h ${m}m ${s}s`; - } else { - format = `${h}h ${m}m`; - } - - return format; - } - - private buildText(res: IServerResponse) { - this._totalLogged = 0; - for (const element of res.body) { - const duration = moment.duration(element.time_spent, "seconds"); - element.time_spent_text = this.formatDuration(duration); - this._totalLogged += parseFloat((element.time_spent || 0).toString()); - } - } - - async delete(id?: string) { - if (!id || !this.taskId) return; - try { - const res = await this.api.delete(id, this.taskId); - if (res.done) { - void this.get(); - } - } catch (e) { - // ignored - } - } - - private mapToRequest(): ITaskLogViewModel { - return { - id: this.taskId || undefined, - project_id: this.projectId as string, - start_time: this.form.value.start || null, - end_time: this.form.value.end || null, - description: this.form.value.description, - created_at: this.form.value.date || null - }; - } - - private async createReqBody() { - const map = this.mapToRequest(); - if (!map.start_time || !map.end_time || !map.created_at) return; - - const createdAt = new Date(map.created_at); - const startTime = moment(map.start_time); - const endTime = moment(map.end_time); - - const formattedStartTime = moment({ - year: createdAt.getFullYear(), - month: createdAt.getMonth(), - day: createdAt.getDate(), - hour: startTime.hours(), - minute: startTime.minutes(), - second: 0 - }); - - const formattedEndTime = moment({ - year: createdAt.getFullYear(), - month: createdAt.getMonth(), - day: createdAt.getDate(), - hour: endTime.hours(), - minute: endTime.minutes(), - second: 0, - }); - - const diff = formattedStartTime.diff(formattedEndTime, "seconds"); - - return { - id: this.taskId || undefined, - project_id: this.projectId as string, - formatted_start: formattedStartTime, - seconds_spent: Math.floor(Math.abs(diff)), - description: map.description, - } - - } - - setFormVisibility(visible: boolean) { - this.showForm = visible; - if (visible) { - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.startInput.focus(); - }); - }); - } else { - this.form.reset(); - this.editId = null; - this.errorText = ""; - this.errorDatePair = false; - } - } - - canDelete(userId?: string) { - return this.auth.getCurrentSession()?.id === userId; - } - - isValid() { - // return this.form.value.hours > 0 || this.form.value.minutes > 0 || this.form.value.seconds > 0; - return this.form.value.start && this.form.value.end; - } - - async editRecord(record: ITaskLogViewModel) { - if (!record.id) return; - - this.editId = record.id; - - this.form.setValue({ - start: record.start_time, - end: record.end_time, - description: record.description, - date: record.created_at - }); - this.setFormVisibility(true); - } - - async exportExcel() { - if (!this.taskId || this.exporting) return; - try { - this.exporting = true; - this.api.exportExcel(this.taskId); - this.exporting = false; - } catch (e) { - this.exporting = false; - } - - this.cdr.detectChanges(); - } - - handleResponse = (response: ITimerStopEventResponse) => { - if (response?.id === this.taskId) { - void this.get(); - } - } - - updateTaskList() { - dispatchTasksChange(); - } - - emitStart(taskId: string) { - this.timerService.emitStart(taskId, Date.now()); - } - - emitStop(taskId: string) { - this.timerService.emitStop(taskId); - } - - setTodayAsDefault() { - this.form.setValue({ - date: momentTime.tz(new Date(), `${this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone}`).format("YYYY-MM-DD"), - description: null, - start: null, - end: null, - }); - this.setFormVisibility(true) - } - -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view.component.html b/worklenz-frontend/src/app/administrator/components/task-view/task-view.component.html deleted file mode 100644 index 83ce62e6..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view.component.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Back -
-
- - - - -
    -
  • Delete task
  • -
-
-
- - - - diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view.component.scss b/worklenz-frontend/src/app/administrator/components/task-view/task-view.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view.component.spec.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view.component.spec.ts deleted file mode 100644 index 708064ea..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskViewComponent} from './task-view.component'; - -describe('TaskViewComponent', () => { - let component: TaskViewComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [TaskViewComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskViewComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view.component.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view.component.ts deleted file mode 100644 index ba6a6800..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view.component.ts +++ /dev/null @@ -1,334 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - NgZone, - OnDestroy, - Output, - Renderer2 -} from '@angular/core'; -import {TasksApiService} from "@api/tasks-api.service"; -import {ITaskFormViewModel, ITaskViewModel} from "@interfaces/task-form-view-model"; -import {ActivatedRoute, NavigationStart, ParamMap, Router} from "@angular/router"; -import {TaskViewService} from "./task-view.service"; -import {log_error, waitForSeconds} from "@shared/utils"; -import {TaskListV2Service} from "../../modules/task-list-v2/task-list-v2.service"; -import {TaskListHashMapService} from "../../modules/task-list-v2/task-list-hash-map.service"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {DEFAULT_TASK_NAME} from "@shared/constants"; -import {ITeamMemberViewModel} from "@interfaces/api-models/team-members-get-response"; -import {TaskLabelsApiService} from "@api/task-labels-api.service"; -import {filter, Observable, takeWhile} from "rxjs"; -import {Socket} from 'ngx-socket-io'; -import {SocketEvents} from '@shared/socket-events'; -import {KanbanV2Service} from 'app/administrator/modules/kanban-view-v2/kanban-view-v2.service'; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {ITaskViewTaskOpenRequest} from "@admin/components/task-view/interfaces"; -import {ITaskAssigneesUpdateResponse} from "@interfaces/task-assignee-update-response"; - -@Component({ - selector: 'worklenz-task-view', - templateUrl: './task-view.component.html', - styleUrls: ['./task-view.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskViewComponent implements OnDestroy { - private _show = false; - get show(): boolean { - return this._show; - } - @Input() set show(value: boolean) { - if (value === this._show) return; - this._show = value; - } - @Output() showChange: EventEmitter = new EventEmitter(); - @Input() taskId: string | null = null; - @Output() taskIdChange: EventEmitter = new EventEmitter(); - @Input() projectId: string | null = null; - @Input() selfResetTaskId = true; - @Output() taskDeleted: EventEmitter = new EventEmitter(); - get task() { - return this.service.model.task; - } - - loading = true; - - private readonly DRAWER_CLOSE_TIME = 100; - readonly BODY_STYLE = { - padding: 0, - overflowX: 'hidden', - overflowY: 'auto' - }; - - private onUrlChange: Observable | null = null; - - constructor( - private readonly api: TasksApiService, - private readonly router: Router, - private readonly route: ActivatedRoute, - private readonly cdr: ChangeDetectorRef, - private readonly list: TaskListV2Service, - private readonly map: TaskListHashMapService, - private readonly labelsApi: TaskLabelsApiService, - private readonly renderer: Renderer2, - private readonly socket: Socket, - private readonly kanbanService: KanbanV2Service, - private readonly ngZone: NgZone, - private readonly service: TaskViewService, - ) { - this.onUrlChange = this.router.events.pipe( - filter(event => event instanceof NavigationStart) - ) as Observable; - - this.service.onSelectSubTask - .pipe(takeUntilDestroyed()) - .subscribe((task: IProjectTask) => { - if (task) { - void this.handleTaskSelectFromView(task); - } - }); - - this.service.onOpenTask - .pipe(takeUntilDestroyed()) - .subscribe(req => { - this.openTask(req); - }); - - this.service.onTimeLogAssignMember - .pipe(takeUntilDestroyed()) - .subscribe( (response: ITaskAssigneesUpdateResponse) => { - this.get(); - }) - - } - - ngOnDestroy() { - this.onUrlChange = null; - } - - private init() { - void this.get(); - void this.getLabels(); - } - - private async handleTaskSelectFromView(task: IProjectTask) { - this.handleCancel(); - if (task) { - await waitForSeconds(); - this.taskId = task.id as string; - this.projectId = task.project_id as string; - this.show = true; - this.showChange.emit(true); - this.cdr.detectChanges(); - } - } - - onVisibilityChange(visible: boolean) { - this.updateQueryParams(visible); - if (visible) { - // Wait for drawer animation to finish - setTimeout(() => this.init(), this.DRAWER_CLOSE_TIME); - this.hideDocumentOverflow(); - this.subscribeToUrlChange(visible); - } else { - this.deleteUntitledTask(); - this.service.resetModel(); - this.resetDocumentOverflow(); - } - } - - private hideDocumentOverflow() { - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.renderer.setStyle(document.documentElement, "overflow", "hidden"); - }); // run outside the current stack - }); - } - - private resetDocumentOverflow() { - this.renderer.removeStyle(document.documentElement, "overflow"); - } - - private subscribeToUrlChange(visible: boolean) { - if (this.onUrlChange) - this.onUrlChange - .pipe(takeWhile(() => !visible, false)) - .subscribe(this.onUrlChanged); - } - - private deleteUntitledTask() { - if (this.service.model.task?.name === DEFAULT_TASK_NAME) { - void this.deleteTask(); - } - } - - onUrlChanged = () => { - setTimeout(() => { - this.handleCancel(); - }, this.DRAWER_CLOSE_TIME); // wait for an animation end - } - - private loadParentTaskIfAvailable(task: ITaskViewModel | undefined) { - if (task?.is_sub_task && task.parent_task_id) { - this.service.emitTaskSelect(task.parent_task_id); - } - } - - handleCancel() { - if (this._show) { - this._show = false; - this.showChange.emit(this._show); - if (this.selfResetTaskId) { - this.taskId = null; - this.taskIdChange.emit(null); - } - this.removeTaskQueryParam(); - } - } - - protected async getLabels() { - try { - const res = await this.labelsApi.get(); - if (res.done) - this.list.labels = res.body; - } catch (e) { - // ignored - } - } - - private async get() { - try { - this.loading = true; - const res = await this.api.getFormViewModel(this.taskId, this.projectId); - if (res.done) { - this.handleResponse(res.body); - } - this.loading = false; - } catch (e) { - this.loading = false; - } - this.cdr.detectChanges(); - } - - private handleResponse(body: ITaskFormViewModel) { - this.service.setModel(body); - this.list.members = body.team_members as ITeamMemberViewModel[]; - } - - private removeParamFromUrl(paramMap: ParamMap, key: string) { - if (!paramMap.has(key)) { - return; - } - - const queryParams: any = {}; - paramMap.keys.filter(k => k != key).forEach(k => (queryParams[k] = paramMap.get(k))); - this.router.navigate([], {queryParams, replaceUrl: true, relativeTo: this.route}); - } - - private updateQueryParams(visible: boolean) { - if (!this.taskId) return; - if (visible) { - void this.router.navigate( - [], - { - relativeTo: this.route, - queryParams: {task: this.taskId}, - queryParamsHandling: 'merge' - }); - } else { - this.removeTaskQueryParam(); - } - } - - isSubTask() { - return !!this.service.model.task?.is_sub_task; - } - - async deleteTask() { - const task = this.service.model.task; - if (!task?.id) return; - try { - const res = await this.api.delete(task.id); - if (res.done) { - if (!task.is_sub_task) { - if (this.map._subTasksMap.has(task.id)) { - const subtasks = this.map._subTasksMap.get(task.id); - if (subtasks) { - for (const subtask of subtasks) { - this.map.selectTask(subtask); - if (subtask.id) { - this.list.removeSubtask(subtask?.id) - } - } - } - } - } else { - this.list.removeSubtask(task.id); - this.kanbanService.emitDeleteSubTask({ - parent_task_id: task.parent_task_id - }); - this.socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), task.parent_task_id); - } - - const count = this.service.model.task?.sub_tasks_count || 0; - if (this.service.model.task) - this.service.model.task.sub_tasks_count = Math.max(count - 1, 0); - - - if (task.sub_tasks) { - if (this.map._subTasksMap.has(task.id)) { - const subtasks = this.map._subTasksMap.get(task.id); - if (subtasks) { - for (const subtask of subtasks) { - this.map.selectTask(subtask); - } - } - } - } else { - this.map.selectTask(task as IProjectTask); - } - this.list.deleteTask(task.id); - this.list.emitRefresh(); - - this.service.emitDelete({id: task.id, parent_task_id: task.parent_task_id, project_id: this.projectId as string}); - this.service.emitRefresh(task.id); - - this.kanbanService.emitDeleteTask(task as IProjectTask); - this.taskDeleted.emit({taskId: task.id}); - - if (task.is_sub_task) { - this.loadParentTaskIfAvailable(task); - } else { - this.handleCancel(); - } - } - } catch (e) { - log_error(e); - } - } - - onBackClick() { - const task = this.service.model?.task; - if (task) { - if (task.is_sub_task) - this.loadParentTaskIfAvailable(task); - this.service.emitOnViewBackFrom(task); - } - } - - private removeTaskQueryParam() { - this.removeParamFromUrl(this.route.snapshot.queryParamMap, "task"); - } - - private openTask(req: ITaskViewTaskOpenRequest) { - this.taskId = req.task_id; - this.taskIdChange.emit(this.taskId); - this.projectId = req.project_id; - this.show = true; - this.showChange.emit(true); - this.cdr.markForCheck(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view.module.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view.module.ts deleted file mode 100644 index 249e18e7..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view.module.ts +++ /dev/null @@ -1,159 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; - -import {TaskViewComponent} from "./task-view.component"; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {FormsModule, ReactiveFormsModule} from "@angular/forms"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NzSelectModule} from "ng-zorro-antd/select"; -import {NzTabsModule} from "ng-zorro-antd/tabs"; -import {TaskViewInfoComponent} from "./task-view-info/task-view-info.component"; -import {NzTagModule} from "ng-zorro-antd/tag"; -import {NzCollapseModule} from "ng-zorro-antd/collapse"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {AvatarsComponent} from "../avatars/avatars.component"; -import {NzAvatarModule} from "ng-zorro-antd/avatar"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzCheckboxModule} from "ng-zorro-antd/checkbox"; -import {SearchByNamePipe} from "../../../pipes/search-by-name.pipe"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzDatePickerModule} from "ng-zorro-antd/date-picker"; -import {NzInputNumberModule} from "ng-zorro-antd/input-number"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import {NzDividerModule} from "ng-zorro-antd/divider"; -import {FromNowPipe} from "@pipes/from-now.pipe"; -import {TaskViewAssigneesComponent} from './task-view-assignees/task-view-assignees.component'; -import {TaskViewDueDateComponent} from './task-view-due-date/task-view-due-date.component'; -import {TaskViewEstimationComponent} from './task-view-estimation/task-view-estimation.component'; -import {TaskViewPriorityComponent} from './task-view-priority/task-view-priority.component'; -import {TaskViewLabelsComponent} from './task-view-labels/task-view-labels.component'; -import {TaskViewDescriptionComponent} from './task-view-description/task-view-description.component'; -import {TaskViewSubTasksComponent} from './task-view-sub-tasks/task-view-sub-tasks.component'; -import {TaskViewNameComponent} from './task-view-name/task-view-name.component'; -import {TaskPriorityLabelComponent} from "../task-priority-label/task-priority-label.component"; -import {TaskViewAttachmentsComponent} from './task-view-attachments/task-view-attachments.component'; -import { - TaskViewAttachmentsThumbComponent -} from './task-view-attachments/task-view-attachments-thumb/task-view-attachments-thumb.component'; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {TaskViewCommentsComponent} from './task-view-comments/task-view-comments.component'; -import {TaskViewCommentsInputComponent} from './task-view-comments-input/task-view-comments-input.component'; -import {NzCommentModule} from "ng-zorro-antd/comment"; -import {RouterLink} from "@angular/router"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzMentionModule} from "ng-zorro-antd/mention"; -import {NzAffixModule} from "ng-zorro-antd/affix"; -import {NzNoAnimationModule} from "ng-zorro-antd/core/no-animation"; -import {NzPopconfirmModule} from 'ng-zorro-antd/popconfirm'; -import {NzCardModule} from "ng-zorro-antd/card"; -import {NzTableModule} from "ng-zorro-antd/table"; -import {NzProgressModule} from "ng-zorro-antd/progress"; -import {EditorModule, TINYMCE_SCRIPT_SRC} from '@tinymce/tinymce-angular'; -import {TaskViewTimeLogComponent} from './task-view-time-log/task-view-time-log.component'; -import {NzListModule} from "ng-zorro-antd/list"; -import {NzEmptyModule} from "ng-zorro-antd/empty"; -import {TaskTimerComponent} from "../task-timer/task-timer.component"; -import {FirstCharUpperPipe} from "@pipes/first-char-upper.pipe"; -import { - TaskListAddTaskInputComponent -} from "../../modules/task-list-v2/task-list-table/task-list-add-task-input/task-list-add-task-input.component"; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; -import {DateFormatterPipe} from "../../../pipes/date-formatter.pipe"; -import {TaskViewNotifyToUserComponent} from './task-view-notify-to-user/task-view-notify-to-user.component'; -import {NgxDocViewerModule} from 'ngx-doc-viewer'; -import {NzModalModule} from 'ng-zorro-antd/modal'; -import {NzSpinModule} from "ng-zorro-antd/spin"; -import { - TaskViewActivityLogComponent -} from "@admin/components/task-view/task-view-activity-log/task-view-activity-log.component"; -import {NzTimelineModule} from "ng-zorro-antd/timeline"; -import {TaskViewPhaseComponent} from './task-view-phase/task-view-phase.component'; -import {NzTimePickerModule} from "ng-zorro-antd/time-picker"; -import {TaskCommentMentionPipe} from "@pipes/task-comment-mention.pipe"; - -@NgModule({ - declarations: [ - TaskViewComponent, - TaskViewInfoComponent, - TaskViewAssigneesComponent, - TaskViewDueDateComponent, - TaskViewEstimationComponent, - TaskViewPriorityComponent, - TaskViewLabelsComponent, - TaskViewDescriptionComponent, - TaskViewSubTasksComponent, - TaskViewNameComponent, - TaskViewAttachmentsComponent, - TaskViewAttachmentsThumbComponent, - TaskViewCommentsComponent, - TaskViewCommentsInputComponent, - TaskViewTimeLogComponent, - TaskViewNotifyToUserComponent, - TaskViewActivityLogComponent, - TaskViewPhaseComponent - ], - exports: [ - TaskViewComponent - ], - providers: [ - SearchByNamePipe, - { provide: TINYMCE_SCRIPT_SRC, useValue: 'tinymce/tinymce.min.js' } - ], - imports: [ - CommonModule, - NzDrawerModule, - NzSkeletonModule, - NzFormModule, - FormsModule, - NzInputModule, - NzSelectModule, - NzTabsModule, - NzTagModule, - NzCollapseModule, - NzDropDownModule, - AvatarsComponent, - NzAvatarModule, - NzTypographyModule, - NzCheckboxModule, - SearchByNamePipe, - NzToolTipModule, - NzDatePickerModule, - NzInputNumberModule, - NzIconModule, - NzBadgeModule, - NzDividerModule, - FromNowPipe, - ReactiveFormsModule, - TaskPriorityLabelComponent, - NzButtonModule, - NzCommentModule, - RouterLink, - NzSpaceModule, - NzMentionModule, - NzAffixModule, - NzNoAnimationModule, - NzPopconfirmModule, - NzCardModule, - NzTableModule, - NzProgressModule, - EditorModule, - NzListModule, - NzEmptyModule, - TaskTimerComponent, - FirstCharUpperPipe, - TaskListAddTaskInputComponent, - SafeStringPipe, - DateFormatterPipe, - NgxDocViewerModule, - NzModalModule, - NzSpinModule, - NzTimelineModule, - NzTimePickerModule, - TaskCommentMentionPipe - ] -}) -export class TaskViewModule { -} diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view.service.spec.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view.service.spec.ts deleted file mode 100644 index d99a9f0d..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {TestBed} from '@angular/core/testing'; - -import {TaskViewService} from './task-view.service'; - -describe('TaskViewService', () => { - let service: TaskViewService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(TaskViewService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/task-view/task-view.service.ts b/worklenz-frontend/src/app/administrator/components/task-view/task-view.service.ts deleted file mode 100644 index 175fefe7..00000000 --- a/worklenz-frontend/src/app/administrator/components/task-view/task-view.service.ts +++ /dev/null @@ -1,170 +0,0 @@ -import {Injectable} from '@angular/core'; -import {ITaskFormViewModel, ITaskViewModel} from "@interfaces/task-form-view-model"; -import {ReplaySubject, Subject} from "rxjs"; -import {ITaskViewTaskIds, ITaskViewTaskOpenRequest} from "@admin/components/task-view/interfaces"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITaskAssigneesUpdateResponse} from "@interfaces/task-assignee-update-response"; - -@Injectable({ - providedIn: 'root' -}) -export class TaskViewService { - private readonly _selectTaskSbj$ = new Subject(); - private readonly _selectSubTaskSbj$ = new Subject(); - private readonly _refreshSbj$ = new Subject(); - private readonly _deleteSbj$ = new Subject(); - private readonly _subTasksRefreshSbj$ = new ReplaySubject(); - private readonly _commentsChangeSbj$ = new Subject<{ task: string; count: number; }>(); - private readonly _attachmentsChangeSbj$ = new Subject<{ task: string; count: number; }>(); - private readonly _taskSubscriberChangeSbj$ = new Subject<{ taskId: string; subscribers: number }>(); - private readonly _phaseChangeSbj$ = new Subject(); - private readonly _statusChangeSbj$ = new Subject(); - private readonly _endDateChangeSbj$ = new Subject(); - private readonly _assigneesChangeSbj$ = new Subject(); - private readonly _viewBackFromSbj$ = new Subject(); - private readonly _openTask$ = new ReplaySubject(); - private readonly _singleMemberChangeSbj$ = new Subject(); - private readonly _timeLogMemberAssignSbj$ = new Subject(); - - private _model: ITaskFormViewModel = {}; - - get model(): ITaskFormViewModel { - return this._model; - } - - get onSelectTask() { - return this._selectTaskSbj$.asObservable(); - } - - get onSelectSubTask() { - return this._selectSubTaskSbj$.asObservable(); - } - - get onRefresh() { - return this._refreshSbj$.asObservable(); - } - - get onDelete() { - return this._deleteSbj$.asObservable(); - } - - get onTaskSubscriberChange$() { - return this._taskSubscriberChangeSbj$.asObservable(); - } - - get onCommentsChange() { - return this._commentsChangeSbj$.asObservable(); - } - - get onAttachmentsChange() { - return this._attachmentsChangeSbj$.asObservable(); - } - - get onPhaseChange() { - return this._phaseChangeSbj$.asObservable(); - } - - get onStatusChange() { - return this._statusChangeSbj$.asObservable(); - } - - get onEndDateChange() { - return this._endDateChangeSbj$.asObservable(); - } - - get onAssigneesChange() { - return this._assigneesChangeSbj$.asObservable(); - } - - get onViewBackFrom() { - return this._viewBackFromSbj$.asObservable(); - } - - get onOpenTask() { - return this._openTask$.asObservable(); - } - - get onSingleMemberChange() { - return this._singleMemberChangeSbj$.asObservable(); - } - - get onTimeLogAssignMember() { - return this._timeLogMemberAssignSbj$.asObservable(); - } - - public onRefreshSubTasks() { - return this._subTasksRefreshSbj$.asObservable(); - } - - public emitTaskSelect(taskId: string) { - this._selectTaskSbj$.next(taskId); - } - - public emitSubTaskSelect(task: IProjectTask) { - this._selectSubTaskSbj$.next(task); - } - - public emitRefresh(taskId: string) { - this._refreshSbj$.next(taskId); - } - - public emitSubTasksRefresh() { - this._subTasksRefreshSbj$.next(); - } - - public emitCommentsChange(taskId: string, count: number) { - this._commentsChangeSbj$.next({task: taskId, count}); - } - - public emitAttachmentsChange(taskId: string, count: number) { - this._attachmentsChangeSbj$.next({task: taskId, count}); - } - - public emitDelete({id, parent_task_id, project_id}: { id: string, parent_task_id?: string, project_id: string }) { - this._deleteSbj$.next({id, parent_task_id, project_id}); - } - - public emitOnTaskSubscriberChange(taskId: string, subscribers: number) { - this._taskSubscriberChangeSbj$.next({taskId, subscribers}); - } - - public emitPhaseChange() { - this._phaseChangeSbj$.next(); - } - - public emitStatusChange() { - this._statusChangeSbj$.next(); - } - - public emitEndDateChange() { - this._endDateChangeSbj$.next(); - } - - public emitAssigneesChange() { - this._statusChangeSbj$.next(); - } - - public emitOnViewBackFrom(task: ITaskViewModel) { - this._viewBackFromSbj$.next(task); - } - - public emitOpenTask(req: ITaskViewTaskOpenRequest) { - this._openTask$.next(req); - } - - public emitSingleMemberChange(teamMemberId: string) { - this._singleMemberChangeSbj$.next(teamMemberId); - } - - public emitTimeLogAssignMember(response: ITaskAssigneesUpdateResponse) { - this._timeLogMemberAssignSbj$.next(response); - } - - public setModel(model: ITaskFormViewModel) { - this._model = {...model}; - } - - public resetModel() { - this._model = {}; - } -} diff --git a/worklenz-frontend/src/app/administrator/components/tasks-group-view/tasks-group-view.component.html b/worklenz-frontend/src/app/administrator/components/tasks-group-view/tasks-group-view.component.html deleted file mode 100644 index 0de41f95..00000000 --- a/worklenz-frontend/src/app/administrator/components/tasks-group-view/tasks-group-view.component.html +++ /dev/null @@ -1,16 +0,0 @@ - -
-
-
-
-
- - - {{status.name}} - -
-
-
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/components/tasks-group-view/tasks-group-view.component.scss b/worklenz-frontend/src/app/administrator/components/tasks-group-view/tasks-group-view.component.scss deleted file mode 100644 index d6c32662..00000000 --- a/worklenz-frontend/src/app/administrator/components/tasks-group-view/tasks-group-view.component.scss +++ /dev/null @@ -1,49 +0,0 @@ -.dropdown-animation { - transition: all .1s ease-in; -} - -.expanded { - transform: rotate(-90deg); -} - -.chevron::before { - border-style: solid; - border-width: 0.25em 0.25em 0 0; - content: ''; - display: inline-block; - height: 0.45em; - left: 0.15em; - position: relative; - top: 0.15em; - transform: rotate(-45deg); - vertical-align: top; - width: 0.45em; -} - -.chevron.bottom:before { - top: 0; - transform: rotate(135deg); -} - -.tasks-table { - display: table; - width: 100%; - margin: 5px 0; -} - -.tasks-table-row { - display: table-row; -} - -.tasks-table-row:hover { - background-color: #eff7fc; -} - -.tasks-table-cell { - display: table-cell; - padding: 3px 10px; -} - -.tasks-table-body { - display: table-row-group; -} diff --git a/worklenz-frontend/src/app/administrator/components/tasks-group-view/tasks-group-view.component.spec.ts b/worklenz-frontend/src/app/administrator/components/tasks-group-view/tasks-group-view.component.spec.ts deleted file mode 100644 index bd6884ea..00000000 --- a/worklenz-frontend/src/app/administrator/components/tasks-group-view/tasks-group-view.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TasksGroupViewComponent} from './tasks-group-view.component'; - -describe('TasksGroupViewComponent', () => { - let component: TasksGroupViewComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TasksGroupViewComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TasksGroupViewComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/tasks-group-view/tasks-group-view.component.ts b/worklenz-frontend/src/app/administrator/components/tasks-group-view/tasks-group-view.component.ts deleted file mode 100644 index 8414c89e..00000000 --- a/worklenz-frontend/src/app/administrator/components/tasks-group-view/tasks-group-view.component.ts +++ /dev/null @@ -1,141 +0,0 @@ -import {Component, EventEmitter, OnInit, Output} from '@angular/core'; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ISubTask} from "@interfaces/sub-task"; -import {log_error} from "@shared/utils"; -import {TaskStatusesApiService} from "@api/task-statuses-api.service"; -import {TasksApiService} from "@api/tasks-api.service"; -import {ActivatedRoute} from "@angular/router"; -import {SubTasksApiService} from "@api/sub-tasks-api.service"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NgForOf} from "@angular/common"; - -@Component({ - selector: 'worklenz-tasks-group-view', - templateUrl: './tasks-group-view.component.html', - styleUrls: ['./tasks-group-view.component.scss'], - imports: [ - NzSkeletonModule, - NzTypographyModule, - NgForOf - ], - standalone: true -}) -export class TasksGroupViewComponent implements OnInit { - @Output() addTask: EventEmitter = new EventEmitter(); - @Output() editTask: EventEmitter = new EventEmitter(); - - taskStatuses: ITaskStatusViewModel[] = []; - loaders: { [x: string]: boolean } = {}; - - projectId: string | null = null; - tasks: { - [x: number]: { - id?: string; - label?: string; - data?: IProjectTask[]; - } - } = {}; - statusIds: string[] = []; - - loadingStatuses = false; - showTaskModal = false; - updateLoading = false; - showStatusModal = false; - loadingTasks = false; - loadingSubTasks = false; - - selectedTaskId: string | null = null; - selectedStatusId: string | null = null; - taskDragging: boolean = false; - - subTasks: ISubTask[] = []; - - constructor( - private api: TasksApiService, - private statusesApi: TaskStatusesApiService, - private subTasksApi: SubTasksApiService, - private route: ActivatedRoute - ) { - this.projectId = this.route.snapshot.paramMap.get('id'); - } - - ngOnInit(): void { - this.getStatuses().then(r => r); - } - - async getTasksByStatus(index: number, status: string, id: string) { - if (!this.projectId) return; - try { - this.loaders[status] = true; - this.loadingTasks = true; - const res = await this.api.getTasksByStatus(this.projectId, status); - if (res.done) { - this.tasks[index] = {}; - this.tasks[index].id = id; - this.tasks[index].label = status; - this.tasks[index].data = res.body; - } - this.loaders[status] = false; - this.loadingTasks = false; - } catch (e) { - log_error(e); - this.loadingTasks = false; - this.loaders[status] = false; - } - } - - async getGroupedTasks() { - for (let i = 0; i < this.taskStatuses.length; i++) { - const status = this.taskStatuses[i]; - this.statusIds.push(status.id || ''); - if (status.name && status.id) - await this.getTasksByStatus(i, status.name, status.id); - } - } - - async getStatuses() { - if (!this.projectId) return; - try { - this.loadingStatuses = true; - const res = await this.statusesApi.get(this.projectId); - if (res.done) { - this.taskStatuses = res.body; - } - this.loadingStatuses = false; - } catch (e) { - log_error(e); - this.loadingStatuses = false; - } - } - - async showSubTasks(task: IProjectTask) { - if (!task.id && task.sub_tasks_loading) return; - task.sub_tasks_loading = true; - task.show_sub_tasks = !task.show_sub_tasks; - if (task.show_sub_tasks) { - try { - const res = await this.subTasksApi.get(task.id || ''); - if (res.done) { - task.sub_tasks = res.body; - task.sub_tasks_loading = false; - } - } catch (e) { - log_error(e); - task.sub_tasks_loading = false; - } - } else { - task.sub_tasks = []; - task.sub_tasks_loading = false; - } - } - - viewTasks(status: ITaskStatusViewModel) { - try { - - } catch (e) { - - } - } -} diff --git a/worklenz-frontend/src/app/administrator/components/tasks-progress-bar/tasks-progress-bar.component.html b/worklenz-frontend/src/app/administrator/components/tasks-progress-bar/tasks-progress-bar.component.html deleted file mode 100644 index 9963c221..00000000 --- a/worklenz-frontend/src/app/administrator/components/tasks-progress-bar/tasks-progress-bar.component.html +++ /dev/null @@ -1,25 +0,0 @@ - -
-
-
-
- {{done | withPercentageMark}} -
-
- {{doing | withPercentageMark}} -
-
- {{todo | withPercentageMark}} -
-
-
-
- - -
-
Done: {{done | withPercentageMark}}
-
Doing: {{doing | withPercentageMark}}
-
Todo: {{todo | withPercentageMark}}
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/components/tasks-progress-bar/tasks-progress-bar.component.scss b/worklenz-frontend/src/app/administrator/components/tasks-progress-bar/tasks-progress-bar.component.scss deleted file mode 100644 index aa3f9f49..00000000 --- a/worklenz-frontend/src/app/administrator/components/tasks-progress-bar/tasks-progress-bar.component.scss +++ /dev/null @@ -1,51 +0,0 @@ -$bar-height: 16px; -$progress-color: #75C997; - -.progress-container-td { - width: 180px; - border-radius: 4px; - overflow: hidden; - height: $bar-height; - - .progress-container { - width: 100%; - background-color: #EDEDED; - position: relative; - - small { - position: absolute; - font-size: 10px; - line-height: $bar-height; - top: 0; - bottom: 0; - left: 0; - right: 0; - margin: auto auto auto auto; - min-width: 20px; - z-index: 8; - word-wrap: normal; - font-weight: 600; - color: rgba(0, 0, 0, 0.85); - } - - .todo, - .doing, - .done { - position: relative; - text-align: center; - height: $bar-height; - } - - .done { - background-color: lighten($progress-color, 10%); - } - - .doing { - background-color: lighten($progress-color, 20%); - } - - .todo { - background-color: lighten($progress-color, 30%); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/components/tasks-progress-bar/tasks-progress-bar.component.spec.ts b/worklenz-frontend/src/app/administrator/components/tasks-progress-bar/tasks-progress-bar.component.spec.ts deleted file mode 100644 index 0ea2e2ab..00000000 --- a/worklenz-frontend/src/app/administrator/components/tasks-progress-bar/tasks-progress-bar.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TasksProgressBarComponent} from './tasks-progress-bar.component'; - -describe('TasksProgressBarComponent', () => { - let component: TasksProgressBarComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [TasksProgressBarComponent] - }); - fixture = TestBed.createComponent(TasksProgressBarComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/tasks-progress-bar/tasks-progress-bar.component.ts b/worklenz-frontend/src/app/administrator/components/tasks-progress-bar/tasks-progress-bar.component.ts deleted file mode 100644 index 56ee1081..00000000 --- a/worklenz-frontend/src/app/administrator/components/tasks-progress-bar/tasks-progress-bar.component.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {WithPercentageMarkPipe} from "@admin/components/tasks-progress-bar/with-percentage-mark.pipe"; - -@Component({ - selector: 'worklenz-tasks-progress-bar', - standalone: true, - imports: [CommonModule, NzToolTipModule, NzTypographyModule, WithPercentageMarkPipe], - templateUrl: './tasks-progress-bar.component.html', - styleUrls: ['./tasks-progress-bar.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TasksProgressBarComponent { - todo = 0; - doing = 0; - done = 0; - - @Input() set todoProgress(value: number | undefined) { - this.todo = value ?? 0; - } - - @Input() set doingProgress(value: number | undefined) { - this.doing = value ?? 0; - } - - @Input() set doneProgress(value: number | undefined) { - this.done = value ?? 0; - } - - canDisplay() { - return this.todo || this.doing || this.done; - } -} diff --git a/worklenz-frontend/src/app/administrator/components/tasks-progress-bar/with-percentage-mark.pipe.ts b/worklenz-frontend/src/app/administrator/components/tasks-progress-bar/with-percentage-mark.pipe.ts deleted file mode 100644 index c53ec412..00000000 --- a/worklenz-frontend/src/app/administrator/components/tasks-progress-bar/with-percentage-mark.pipe.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; - -@Pipe({ - name: 'withPercentageMark', - standalone: true -}) -export class WithPercentageMarkPipe implements PipeTransform { - transform(value: string | number): string { - return `${value}%`; - } -} diff --git a/worklenz-frontend/src/app/administrator/components/team-members-autocomplete/team-members-autocomplete.component.html b/worklenz-frontend/src/app/administrator/components/team-members-autocomplete/team-members-autocomplete.component.html deleted file mode 100644 index cc45347d..00000000 --- a/worklenz-frontend/src/app/administrator/components/team-members-autocomplete/team-members-autocomplete.component.html +++ /dev/null @@ -1,58 +0,0 @@ -
- - {{label}} - - - - -
- -
- {{item.name}} - {{item.email}} -
-
-
-
- - - Loading Data... - -
-
-
-
- - - -
- Invitees will be added to the team and project either they accept the invitation or not. -
-
diff --git a/worklenz-frontend/src/app/administrator/components/team-members-autocomplete/team-members-autocomplete.component.scss b/worklenz-frontend/src/app/administrator/components/team-members-autocomplete/team-members-autocomplete.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/components/team-members-autocomplete/team-members-autocomplete.component.spec.ts b/worklenz-frontend/src/app/administrator/components/team-members-autocomplete/team-members-autocomplete.component.spec.ts deleted file mode 100644 index 628f70aa..00000000 --- a/worklenz-frontend/src/app/administrator/components/team-members-autocomplete/team-members-autocomplete.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TeamMembersAutocompleteComponent} from './team-members-autocomplete.component'; - -describe('TeamMembersAutocompleteComponent', () => { - let component: TeamMembersAutocompleteComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TeamMembersAutocompleteComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TeamMembersAutocompleteComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/team-members-autocomplete/team-members-autocomplete.component.ts b/worklenz-frontend/src/app/administrator/components/team-members-autocomplete/team-members-autocomplete.component.ts deleted file mode 100644 index 514076f8..00000000 --- a/worklenz-frontend/src/app/administrator/components/team-members-autocomplete/team-members-autocomplete.component.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { - AfterViewInit, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - HostListener, - Input, - NgZone, - OnInit, - Output, - ViewChild -} from '@angular/core'; -import {FormBuilder, FormGroup, ReactiveFormsModule} from "@angular/forms"; -import {ITeamMembersViewModel} from "@interfaces/api-models/team-members-view-model"; -import {TeamMembersApiService} from "@api/team-members-api.service"; -import {isValidateEmail, log_error} from "@shared/utils"; -import {NzSelectComponent, NzSelectModule} from "ng-zorro-antd/select"; -import {NgForOf, NgIf} from "@angular/common"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzAvatarModule} from "ng-zorro-antd/avatar"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {ActivatedRoute} from "@angular/router"; -import {ITeamMember} from "@interfaces/team-member"; -import {ProjectMembersApiService} from "@api/project-members-api.service"; -import {FirstCharUpperPipe} from "@pipes/first-char-upper.pipe"; -import {DRAWER_ANIMATION_INTERVAL} from "@shared/constants"; -import {ProjectsService} from "../../projects/projects.service"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-team-members-autocomplete', - templateUrl: './team-members-autocomplete.component.html', - styleUrls: ['./team-members-autocomplete.component.scss'], - imports: [ - NzSelectModule, - NgIf, - NzTypographyModule, - ReactiveFormsModule, - NzFormModule, - NgForOf, - NzIconModule, - NzAvatarModule, - NzToolTipModule, - NzButtonModule, - FirstCharUpperPipe - ], - standalone: true, - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TeamMembersAutocompleteComponent implements OnInit, AfterViewInit { - @ViewChild("searchInput") searchInput!: NzSelectComponent; - - @Output() refresh: EventEmitter = new EventEmitter(); - - @Output() membersChange: EventEmitter = new EventEmitter(); - @Input() members: string | string[] = []; - - @Input() placeholder = 'Select Members'; - @Input() label: string | null = 'Members'; - - @Input() multiple = false; - @Input() disabled = false; - @Input() autofocus = false; - @Input() disableTeamInvites = false; - - form!: FormGroup; - - loading = false; - searching = false; - inviting = false; - - model: ITeamMembersViewModel = {}; - - searchingName: string | null = null; - - projectId: string | null = null; - - get buttonText() { - return this.isValueIsAnEmail() ? 'Invite as a member' : 'Invite a new member by email'; - } - - constructor( - private readonly api: TeamMembersApiService, - private readonly membersApi: ProjectMembersApiService, - private readonly fb: FormBuilder, - private readonly route: ActivatedRoute, - private readonly ngZone: NgZone, - private readonly cdr: ChangeDetectorRef, - private readonly projectsService: ProjectsService, - private readonly auth: AuthService - ) { - // The id currently only consumed under a project with id param. - this.projectId = this.route.snapshot.paramMap.get("id"); - - this.form = this.fb.group({ - members: [] - }); - - this.form.controls["members"]?.valueChanges.subscribe((value: string[]) => { - this.emitChanges(value); - }); - } - - async ngOnInit() { - this.form.controls["members"].setValue(this.members || []); - await this.init(); - this.cdr.detectChanges(); - } - - ngAfterViewInit() { - if (this.autofocus) { - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.searchInput.setOpenState(true); - }, DRAWER_ANIMATION_INTERVAL); - }); - this.cdr.detectChanges(); - } - } - - async init() { - this.loading = true; - await this.get(); - this.loading = false; - this.cdr.detectChanges(); - } - - async get() { - try { - const res = await this.api.get(1, 5, 'name', null, this.searchingName); - if (res.done) { - this.model = res.body; - } - this.cdr.detectChanges(); - } catch (e) { - log_error(e); - this.cdr.detectChanges(); - } - } - - async search(value: string) { - this.searchingName = value; - this.searching = true; - await this.get(); - this.searching = false; - this.cdr.detectChanges(); - } - - public reset() { - this.form.reset(); - this.searchingName = null; - void this.init(); - this.cdr.detectChanges(); - } - - private emitChanges(newMemberIds: string | string[]) { - this.members = newMemberIds; - this.membersChange.emit(newMemberIds); - this.cdr.detectChanges(); - } - - trackById(index: number, item: ITeamMember) { - return item.id; - } - - isValueIsAnEmail() { - if (!this.searchingName) return false; - return isValidateEmail(this.searchingName); - } - - private resetSearchInput() { - this.reset(); - this.searchInput.clearInput(); - this.searchInput.focus(); - this.cdr.detectChanges(); - } - - @HostListener("document:keydown", ["$event"]) - handleEnterKeyPress(event: KeyboardEvent) { - if (event.code === "Enter" && this.isValueIsAnEmail()) - void this.sendInvitation(); - } - - async sendInvitation() { - if (!this.projectId) return; - if (typeof this.searchingName !== "string" || !this.searchingName.length) return; - - try { - const email = this.searchingName.trim().toLowerCase(); - const request = { - project_id: this.projectId, - email - }; - this.inviting = true; - const res = await this.membersApi.createByEmail(request); - this.inviting = false; - if (res.done) { - this.resetSearchInput(); - this.refresh?.emit(); - } - this.cdr.detectChanges(); - } catch (e) { - this.inviting = false; - this.cdr.detectChanges(); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/components/team-members-form/team-members-form.component.html b/worklenz-frontend/src/app/administrator/components/team-members-form/team-members-form.component.html deleted file mode 100644 index f1c6ef6c..00000000 --- a/worklenz-frontend/src/app/administrator/components/team-members-form/team-members-form.component.html +++ /dev/null @@ -1,99 +0,0 @@ - - - -
- - - - Email(s) - - - - - - - - - Invitees will be added to the team either they accept the invitation or not. - - - - - - - Access - - - - - - - - - - - - - - -

- - Added {{model.created_at | fromNow}} - -

-

- - Updated {{model.updated_at | fromNow}} - -

-
-
-
-
- - -
- - -
- {{title}} - {{model.email}} -
-
-
diff --git a/worklenz-frontend/src/app/administrator/components/team-members-form/team-members-form.component.scss b/worklenz-frontend/src/app/administrator/components/team-members-form/team-members-form.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/components/team-members-form/team-members-form.component.spec.ts b/worklenz-frontend/src/app/administrator/components/team-members-form/team-members-form.component.spec.ts deleted file mode 100644 index 9bcc8de7..00000000 --- a/worklenz-frontend/src/app/administrator/components/team-members-form/team-members-form.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TeamMembersFormComponent} from './team-members-form.component'; - -describe('TeamMembersFormComponent', () => { - let component: TeamMembersFormComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TeamMembersFormComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TeamMembersFormComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/team-members-form/team-members-form.component.ts b/worklenz-frontend/src/app/administrator/components/team-members-form/team-members-form.component.ts deleted file mode 100644 index 542f363e..00000000 --- a/worklenz-frontend/src/app/administrator/components/team-members-form/team-members-form.component.ts +++ /dev/null @@ -1,248 +0,0 @@ -import {Component, EventEmitter, Input, Output} from '@angular/core'; -import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms"; -import {TeamMembersApiService} from "@api/team-members-api.service"; -import {ITeamMemberCreateRequest} from "@interfaces/api-models/team-member-create-request"; -import {AppService} from "@services/app.service"; -import {AuthService} from "@services/auth.service"; -import {ITeamMemberViewModel} from "@interfaces/api-models/team-members-get-response"; -import {log_error} from "@shared/utils"; -import {AvatarNamesMap} from "@shared/constants"; -import {NzSelectModule} from "ng-zorro-antd/select"; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzSpinModule} from "ng-zorro-antd/spin"; -import {JobTitlesAutocompleteComponent} from "../job-titles-autocomplete/job-titles-autocomplete.component"; -import {NzAvatarModule} from "ng-zorro-antd/avatar"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {DatePipe, NgIf} from "@angular/common"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {FromNowPipe} from "../../../pipes/from-now.pipe"; -import {SettingsService} from "../../settings/settings.service"; - -@Component({ - selector: 'worklenz-team-members-form', - templateUrl: './team-members-form.component.html', - styleUrls: ['./team-members-form.component.scss'], - imports: [ - NzSelectModule, - NzDrawerModule, - NzSpinModule, - JobTitlesAutocompleteComponent, - NzAvatarModule, - NzTypographyModule, - NzButtonModule, - NzToolTipModule, - NgIf, - NzFormModule, - ReactiveFormsModule, - FromNowPipe, - DatePipe - ], - standalone: true -}) -export class TeamMembersFormComponent { - - form!: FormGroup; - - model: ITeamMemberViewModel = {}; - - @Input() memberId: string | null = null; - - @Input() show = false; - @Output() showChange: EventEmitter = new EventEmitter(); - - @Output() onCreateOrUpdate: EventEmitter = new EventEmitter(); - @Output() onCancel: EventEmitter = new EventEmitter(); - - jobTitle: string | null = null; - loading = true; - jobTitlesLoading = false; - resending = false; - resentSuccess = false; - - constructor( - private api: TeamMembersApiService, - private fb: FormBuilder, - private app: AppService, - private auth: AuthService - ) { - this.form = this.fb.group({ - email: [null, Validators.required], - access: ['member', Validators.required] - }); - } - - get title() { - return this.isEditMember() ? (this.model.name || 'Edit Member') : 'Add Member'; - } - - get okButtonText() { - return this.isEditMember() ? 'Update' : 'Add to team'; - } - - get email() { - return this.form.value.email; - } - - isOwnAccount() { - return this.auth.getCurrentSession()?.email === this.model.email; - } - - getColor(name?: string) { - return AvatarNamesMap[name?.charAt(0).toUpperCase() || 'A']; - } - - async getTeamMember() { - if (!this.memberId) { - this.loading = false; - return; - } - - try { - this.loading = true; - const res = await this.api.getById(this.memberId); - if (res.done) { - this.model = res.body; - - this.form.patchValue(res.body); - this.form.controls["access"].setValue(res.body.is_admin ? "admin" : "member"); - - if (this.model.email) - this.form.controls["email"].disable(); - if (this.isOwnAccount()) { - this.form.controls["access"].disable(); - } - this.jobTitle = res.body.job_title as string; - } - this.loading = false; - } catch (e) { - log_error(e); - this.loading = false; - } - } - - init() { - this.form.reset(); - this.form.controls["email"].enable(); - this.form.controls["access"].enable(); - this.form.controls["access"].setValue('member'); - this.model = {}; - this.getTeamMember(); - } - - handleCancel() { - this.reset(); - this.onCancel.emit(); - } - - isEditMember() { - return !!this.memberId; - } - - async handleOk() { - if (this.isEditMember()) { - await this.updateMember(); - } else { - await this.createMember(); - } - } - - isLoading() { - return this.loading; - } - - visibilityChange(visible: boolean) { - if (visible) { - this.init(); - } - } - - canDisplayTitles() { - return this.isEditMember() ? !this.loading : true; - } - - public async resendInvitation() { - if (!this.memberId || this.resending) return; - try { - this.resending = true; - const res = await this.api.resendInvitation(this.memberId); - if (res.done) - this.resentSuccess = true; - this.resending = false; - } catch (e) { - log_error(e); - this.resending = false; - } - } - - isResendAvailable() { - return this.model.pending_invitation && this.memberId && !this.resentSuccess; - } - - private isAdmin() { - return this.form.value.access === "admin"; - } - - private reset() { - this.form.reset(); - this.show = false; - this.jobTitle = null; - this.loading = true; - this.showChange?.emit(this.show); - } - - private async updateMember() { - if (!this.memberId) return; - this.form.value.is_admin = !!this.form.value.is_admin; - - if (this.form.valid) { - try { - this.loading = true; - const body: ITeamMemberCreateRequest = { - job_title: this.jobTitle, - emails: this.form.controls['email'].value, - is_admin: this.isAdmin() - }; - - const res = await this.api.update(this.memberId, body); - if (res.done) { - this.reset(); - this.onCreateOrUpdate?.emit(0); - } - this.loading = false; - } catch (e) { - log_error(e); - this.loading = false; - } - } else { - this.app.displayErrorsOf(this.form); - } - - } - - private async createMember() { - this.form.value.is_admin = !!this.form.value.is_admin; - if (this.form.valid) { - try { - this.loading = true; - const body: ITeamMemberCreateRequest = { - job_title: this.jobTitle, - emails: this.form.controls['email'].value, - is_admin: this.isAdmin() - }; - const res = await this.api.create(body); - if (res.done) { - this.reset(); - this.onCreateOrUpdate?.emit(1); - } - this.loading = false; - } catch (e) { - log_error(e); - this.loading = false; - } - } else { - this.app.displayErrorsOf(this.form); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/components/toggle-menu-button/toggle-menu-button.component.html b/worklenz-frontend/src/app/administrator/components/toggle-menu-button/toggle-menu-button.component.html deleted file mode 100644 index cdd80120..00000000 --- a/worklenz-frontend/src/app/administrator/components/toggle-menu-button/toggle-menu-button.component.html +++ /dev/null @@ -1,9 +0,0 @@ - diff --git a/worklenz-frontend/src/app/administrator/components/toggle-menu-button/toggle-menu-button.component.scss b/worklenz-frontend/src/app/administrator/components/toggle-menu-button/toggle-menu-button.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/components/toggle-menu-button/toggle-menu-button.component.spec.ts b/worklenz-frontend/src/app/administrator/components/toggle-menu-button/toggle-menu-button.component.spec.ts deleted file mode 100644 index e594f3d1..00000000 --- a/worklenz-frontend/src/app/administrator/components/toggle-menu-button/toggle-menu-button.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ToggleMenuButtonComponent} from './toggle-menu-button.component'; - -describe('ToggleMenuButtonComponent', () => { - let component: ToggleMenuButtonComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ToggleMenuButtonComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ToggleMenuButtonComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/components/toggle-menu-button/toggle-menu-button.component.ts b/worklenz-frontend/src/app/administrator/components/toggle-menu-button/toggle-menu-button.component.ts deleted file mode 100644 index 141ba6bd..00000000 --- a/worklenz-frontend/src/app/administrator/components/toggle-menu-button/toggle-menu-button.component.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {Component, Input} from '@angular/core'; -import {MenuService} from "@services/menu.service"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzIconModule} from "ng-zorro-antd/icon"; - -@Component({ - selector: 'worklenz-toggle-menu-button', - templateUrl: './toggle-menu-button.component.html', - styleUrls: ['./toggle-menu-button.component.scss'], - imports: [ - NzToolTipModule, - NzButtonModule, - NzIconModule - ], - standalone: true -}) -export class ToggleMenuButtonComponent { - @Input() key!: string; - - constructor( - public menu: MenuService - ) { - } -} diff --git a/worklenz-frontend/src/app/administrator/layout/alerts/alerts.component.html b/worklenz-frontend/src/app/administrator/layout/alerts/alerts.component.html deleted file mode 100644 index 44929641..00000000 --- a/worklenz-frontend/src/app/administrator/layout/alerts/alerts.component.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - Action required! New version of Worklenz available! Reload to apply changes. - diff --git a/worklenz-frontend/src/app/administrator/layout/alerts/alerts.component.scss b/worklenz-frontend/src/app/administrator/layout/alerts/alerts.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/layout/alerts/alerts.component.spec.ts b/worklenz-frontend/src/app/administrator/layout/alerts/alerts.component.spec.ts deleted file mode 100644 index 1c788da0..00000000 --- a/worklenz-frontend/src/app/administrator/layout/alerts/alerts.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {AlertsComponent} from './alerts.component'; - -describe('AlertsComponent', () => { - let component: AlertsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [AlertsComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(AlertsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/layout/alerts/alerts.component.ts b/worklenz-frontend/src/app/administrator/layout/alerts/alerts.component.ts deleted file mode 100644 index bdefb4af..00000000 --- a/worklenz-frontend/src/app/administrator/layout/alerts/alerts.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; -import {WorklenzAlert} from "@interfaces/api-models/local-session"; - -@Component({ - selector: 'worklenz-alerts', - templateUrl: './alerts.component.html', - styleUrls: ['./alerts.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class AlertsComponent { - @Input() updateAvailable = false; - @Input() alerts: WorklenzAlert[] = []; - - reload() { - window.location.reload(); - } - - trackByIndex(index: number, item: any) { - return item.id; - } -} diff --git a/worklenz-frontend/src/app/administrator/layout/header/header.component.html b/worklenz-frontend/src/app/administrator/layout/header/header.component.html deleted file mode 100644 index 41aad38b..00000000 --- a/worklenz-frontend/src/app/administrator/layout/header/header.component.html +++ /dev/null @@ -1,194 +0,0 @@ -
-
- Worklenz - Worklenz -
-
- -
-
    -
  • - {{ item.label }} -
  • -
- -
- -
    -
  • - {{ item.label }} -
  • -
- - - - -
    -
  • - {{ item.label }} -
  • -
-
- -
- -
- -
-
    - - - - - - - - - - - -
    - - -
    -
      -
    • Admin Center
    • -
    • Settings -
    • -
    • Log Out
    • -
    -
    -
    - -
-
-
-
- - -
    -
  • -
    -
    -
    - - -
    -
    - - {{ getTeamStatus(item) !== TEAM_STATUSES.OwnerAndNameNotChanged ? 'Owned by ' + item.owns_by : 'Click to setup your own account' }} - - {{ item.name }} - Setup my account - {{ item.name }} -
    -
    -
    - -
    -
    -
  • -
-
- - diff --git a/worklenz-frontend/src/app/administrator/layout/header/header.component.scss b/worklenz-frontend/src/app/administrator/layout/header/header.component.scss deleted file mode 100644 index e19dcc4b..00000000 --- a/worklenz-frontend/src/app/administrator/layout/header/header.component.scss +++ /dev/null @@ -1,207 +0,0 @@ - -.logo-holder { - width: 135px; - max-width: 135px; - margin-right: 7px; -} - -.logo { - width: 120px; - height: 31px; - background: rgba(255, 255, 255, 0.2); - margin: 16px 24px 16px 0; - float: left; - - img { - height: 27px; - } -} - -.pt-def { - padding-top: 16px; -} - -.pb-def { - padding-bottom: 16px; -} - -.pl-def { - padding-left: 16px; -} - -.pr-def { - padding-right: 16px; -} - -[nz-menu-item] { - user-select: none; -} - -span[nztype="bell"] { - font-size: 20px; -} - -.team-list-item:not(:last-child) { - border-bottom: 1px solid #f0f0f0 !important; -} - -.team-name-edit-icon { - width: 32px; - height: 32px; - background: rgba(150, 150, 150, .1); - display: flex; - align-items: center; - justify-content: center; - border-radius: 16px; -} - -.profile-details-dropdown { - position: absolute; - right: 0; - top: 70px; - background-color: white; - box-shadow: var(--ds-shadow-overlay, 0 4px 8px -2px rgba(9, 30, 66, 0.25), 0 0 1px rgba(9, 30, 66, 0.31)); - z-index: 9; - border-radius: 4px; - width: 230px; - - .account-heading h2 { - line-height: 20px; - margin-bottom: 0px; - font-size: 14px; - font-weight: 500; - } - - .actions h2 { - line-height: 20px; - margin-bottom: 10px; - margin-top: 10px; - font-size: 14px; - font-weight: 500; - padding-left: 16px; - } - - .account-name { - margin-left: 12px; - width: 155px; - } - - .account-name h4 { - line-height: 20px; - margin-bottom: 0px; - font-size: 14px; - font-weight: 500; - } - - .account-name p { - line-height: 20px; - margin-bottom: 0px; - font-size: 12px; - } - - .actions { - border-top: 1px solid #f0f0f0; - } - - .actions ul { - margin-bottom: 6px; - } - - .actions ul li { - line-height: 36px; - cursor: pointer; - margin-bottom: 2px; - } - - .actions ul li:hover { - background-color: #edebf0 !important; - } - - .account-details { - margin-right: 16px; - } -} - -[nz-menu] { - line-height: 64px; -} - -nz-content { - margin-top: 64px; - background: #ffffff; -} - -.upgrade-btn { - background-color: #fbc84c69; - padding: 4px 11px; - border-radius: 4px; -} -.invite-btn { - color: #1890ff; - border-color: #1890ff; -} -.lg-none { - display: none; -} -.mb-none { - display: block; -} -.logo-large { - display: block; -} -.logo-small { - display: none; -} - -.item-selected { - color: #188fff!important; - font-weight: 500; -} - -.top-nav-ul-main { - display: block; -} - -.mob-nav-main { - display: none; -} - -@media(max-width: 1200px) { - .mob-nav-main { - display: flex; - } - .top-nav-ul-main { - display: none; - } - .upgrade-btn { - font-size: 12px; - } -} - -@media(max-width: 1100px) { - .lg-none { - display: block; - } - .mb-none { - display: none; - } - .teams-switch { - padding-left: 7px !important; - } - .logo-holder { - width: 50px; - max-width: 50px; - } - .top-nav-ul-main.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item, .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu { - padding: 0 15px !important; - } - .top-nav-ul-secondary.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item, .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu { - padding: 0 15px !important; - } - .logo-large { - display: none; - } - .logo-small { - display: block; - } -} diff --git a/worklenz-frontend/src/app/administrator/layout/header/header.component.spec.ts b/worklenz-frontend/src/app/administrator/layout/header/header.component.spec.ts deleted file mode 100644 index bb5633e7..00000000 --- a/worklenz-frontend/src/app/administrator/layout/header/header.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {HeaderComponent} from './header.component'; - -describe('HeaderComponent', () => { - let component: HeaderComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [HeaderComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(HeaderComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/layout/header/header.component.ts b/worklenz-frontend/src/app/administrator/layout/header/header.component.ts deleted file mode 100644 index 9ef84f95..00000000 --- a/worklenz-frontend/src/app/administrator/layout/header/header.component.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - HostListener, - Input, - Output -} from '@angular/core'; -import {NavItem} from "@interfaces/nav-item"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {UtilsService} from "@services/utils.service"; -import {AuthService} from "@services/auth.service"; -import {ProfileSettingsApiService} from "@api/profile-settings-api.service"; -import {NotificationSettingsService} from "@services/notification-settings.service"; -import {log_error} from "@shared/utils"; -import {ITeamViewModel} from "@interfaces/api-models/team-view-model"; -import {TeamsApiService} from "@api/teams-api.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {AdminCenterService} from "../../admin-center/admin-center-service.service"; -import {TeamMembersApiService} from "@api/team-members-api.service"; -import {SettingsService} from "../../settings/settings.service"; -import {Router} from "@angular/router"; - -enum TeamStatus { - OwnerAndNameChanged, - OwnerAndNameNotChanged, - NotTheOwner -} - -@Component({ - selector: 'worklenz-header', - templateUrl: './header.component.html', - styleUrls: ['./header.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class HeaderComponent { - @Input() teams: ITeamViewModel[] = []; - @Input() profile: any = {}; - @Input() navigation: NavItem[] = []; - @Input() count = 0; - - @Input() showNotifications = false; - @Output() showNotificationsChange = new EventEmitter(); - - // readonly BETA_INFO = `A "Beta" app generally means that people can use and test it, but they should expect bugs & issues.`; - readonly TEAM_STATUSES = TeamStatus; - - showProfileDropdown = false; - showTeamMemberModal = false; - loading = false; - switchingTeam = false; - - selectedMemberId: string | null = null; - - get avatarColor() { - return this.utils.getColor(this.profile?.name); - } - - get userRole() { - return this.auth.role; - } - - constructor( - private readonly settingsApi: ProfileSettingsApiService, - private readonly api: TeamsApiService, - private readonly notificationSettings: NotificationSettingsService, - private readonly auth: AuthService, - private readonly cdr: ChangeDetectorRef, - public readonly utils: UtilsService, - private readonly adminCenterService: AdminCenterService, - private teamMembersApi: TeamMembersApiService, - private settingsService: SettingsService, - private router: Router, - ) { - this.notificationSettings.onCountsUpdate$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.cdr.markForCheck(); - }); - - this.adminCenterService.onTeamNameChange - .pipe(takeUntilDestroyed()) - .subscribe((value) => { - this.onHandleTeamNameChange(value); - }) - } - - signOut() { - this.auth.signOutWithConfirm(); - } - - showUnreadNotificationsCount() { - return !!this.notificationSettings.settings.show_unread_items_count; - } - - hasNotifications() { - return this.notificationSettings.count > 0; - } - - isActiveTeam(teamId?: string) { - return teamId === this.profile?.team_id; - } - - openNotificationsDrawer() { - this.showNotifications = true; - this.showNotificationsChange.emit(true); - } - - async selectTeam(id: string | undefined) { - if (!id) return; - this.loading = true; - this.switchingTeam = true; - try { - const res = await this.api.activate(id); - if (res.done) { - await this.auth.authorize(); - this.reload(); - } else { - this.switchingTeam = false; - } - this.loading = false; - } catch (e) { - this.loading = false; - this.switchingTeam = false; - log_error(e); - } - - this.cdr.detectChanges(); - } - - @HostListener('document:click', ['$event']) - hideProfileDropdown(event: any) { - const classList = event.target.classList; - if (classList.contains('prevent-default')) - return; - this.showProfileDropdown = false; - this.cdr.detectChanges(); - } - - getTeamStatus(item: ITeamViewModel) { - if (item.owner && !this.profile?.my_setup_completed) return TeamStatus.OwnerAndNameNotChanged; - if (item.owner && this.profile?.my_setup_completed) return TeamStatus.OwnerAndNameChanged; - return TeamStatus.NotTheOwner; - } - - onHandleTeamNameChange(response: { teamId: string, teamName: string }) { - if (this.profile?.team_id === response.teamId) { - this.profile.team_name = response.teamName; - } - this.cdr.detectChanges(); - } - - reload() { - window.location.reload(); - } - - reset() { - this.selectedMemberId = null; - } - - isOwnerOrAdmin() { - return (this.profile?.owner || this.profile?.is_admin); - } - - handleOnCreateOrUpdate(event: number) { - if (event == 1) // create - this.settingsService.emitNewMemberCreated(); - } - openAddMemberForm() { - this.showTeamMemberModal = true; - } - - navigateHome() { - this.router.navigate(['/worklenz']); - } - -} diff --git a/worklenz-frontend/src/app/administrator/layout/layout.component.html b/worklenz-frontend/src/app/administrator/layout/layout.component.html deleted file mode 100644 index 550f01a3..00000000 --- a/worklenz-frontend/src/app/administrator/layout/layout.component.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - -
- -
-
-
-
-
- - diff --git a/worklenz-frontend/src/app/administrator/layout/layout.component.scss b/worklenz-frontend/src/app/administrator/layout/layout.component.scss deleted file mode 100644 index 954408a7..00000000 --- a/worklenz-frontend/src/app/administrator/layout/layout.component.scss +++ /dev/null @@ -1,37 +0,0 @@ -$dark-white: #FAFBFC; - -.layout { - min-height: 100vh; -} - -nz-content { - margin-top: 64px; - background: #ffffff; -} - -.inner-content { - background: #ffffff; - min-height: 380px; - padding: 0 24px 24px; - - &:has(.reporting-tab-module) { - padding: 0 24px 0 0px; - } -} - -nz-header { - position: fixed; - width: 100vw; - background: #FFFFFF; - z-index: 11; - - &::after { - content: ""; - position: absolute; - left: 0px; - right: 0px; - top: 100%; - height: 4px; - background: linear-gradient(rgba(9, 30, 66, 0.13) 0px, rgba(9, 30, 66, 0.13) 1px, rgba(9, 30, 66, 0.08) 1px, rgba(9, 30, 66, 0) 4px); - } -} diff --git a/worklenz-frontend/src/app/administrator/layout/layout.component.spec.ts b/worklenz-frontend/src/app/administrator/layout/layout.component.spec.ts deleted file mode 100644 index 104d670d..00000000 --- a/worklenz-frontend/src/app/administrator/layout/layout.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {LayoutComponent} from './layout.component'; - -describe('LayoutComponent', () => { - let component: LayoutComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [LayoutComponent] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(LayoutComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/layout/layout.component.ts b/worklenz-frontend/src/app/administrator/layout/layout.component.ts deleted file mode 100644 index 266df944..00000000 --- a/worklenz-frontend/src/app/administrator/layout/layout.component.ts +++ /dev/null @@ -1,217 +0,0 @@ -import {AfterViewInit, Component, HostListener, OnDestroy, OnInit} from '@angular/core'; -import {NavItem} from "@interfaces/nav-item"; -import {AuthService} from "@services/auth.service"; -import {TeamsApiService} from "@api/teams-api.service"; -import {ITeamViewModel} from "@interfaces/api-models/team-view-model"; -import {NavItemType} from "@interfaces/nav-item-type"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {NzMessageService} from "ng-zorro-antd/message"; -import {EventMenuChanged, EventProfilePictureChange} from "@shared/events"; -import {MenuService} from "@services/menu.service"; -import {NzModalService} from 'ng-zorro-antd/modal'; -import {UtilsService} from "@services/utils.service"; -import {log_error} from "@shared/utils"; -import {ProfileSettingsApiService} from "@api/profile-settings-api.service"; -import {NotificationSettingsService} from "@services/notification-settings.service"; -import {SocketService} from "@services/socket.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {AdminCenterService} from "../admin-center/admin-center-service.service"; -import {merge} from "rxjs"; - -@Component({ - selector: 'worklenz-layout', - templateUrl: './layout.component.html', - styleUrls: ['./layout.component.scss'] -}) -export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit { - navigation: NavItem[] = []; - teams: ITeamViewModel[] = []; - - count = 0; - loading = false; - showNotifications = false; - switchingTeam = false; - reconnecting = false; - updateAvailable = false; - - private messageId: string | null = null; - - get profile() { - return this.auth.getCurrentSession(); - } - - constructor( - private readonly auth: AuthService, - private readonly api: TeamsApiService, - private readonly socket: Socket, - private readonly message: NzMessageService, - private readonly menu: MenuService, - private readonly modal: NzModalService, - private readonly socketService: SocketService, - private readonly settingsApi: ProfileSettingsApiService, - private readonly notificationSettings: NotificationSettingsService, - public readonly utils: UtilsService, - private readonly adminCenterService: AdminCenterService - ) { - this.socket.connect(); - - merge(this.adminCenterService.onCreateTeam, this.adminCenterService.onTeamNameChange) - .pipe(takeUntilDestroyed()) - .subscribe(value => { - this.getTeams(); - }); - } - - ngOnInit() { - void this.getTeams(); - void this.getNotificationSettings(); - this.listeningForSocketEvents(); - } - - async ngAfterViewInit() { - await this.auth.authorize(); - this.buildNavigation(); - } - - ngOnDestroy() { - this.socket.disconnect(); - } - - reload() { - window.location.reload(); - } - - private listeningForSocketEvents() { - this.socket.on("connect", () => { - this.displayReconnectedMessage(); - this.socket.emit(SocketEvents.LOGIN.toString(), this.profile?.id); - this.socket.once(SocketEvents.LOGIN.toString(), () => { - this.socketService.emitSocketLoginSuccess(); - }); - this.socketService.emitSocketConnect(); - }); - - this.socket.on("disconnect", () => { - this.displayDisconnectedMessage(); - this.socket.emit(SocketEvents.LOGOUT.toString(), this.profile?.id); - this.socketService.emitSocketDisconnect(); - }); - - this.socket.on(SocketEvents.INVITATIONS_UPDATE.toString(), (message: string) => { - void this.getTeams(); - }); - - this.socket.on(SocketEvents.TEAM_MEMBER_REMOVED.toString(), (data: { teamId: string; message: string; }) => { - if (!data) return; - void this.getTeams(); - if (this.profile?.team_id === data.teamId) { - this.modal.confirm({ - nzTitle: 'You no longer have permissions to stay on this team!', - nzContent: data.message, - nzClosable: false, - nzCancelDisabled: true, - nzOnOk: () => this.reload() - }); - } - }); - } - - private async getTeams() { - try { - this.loading = true; - const res = await this.api.get(); - if (res.done) { - this.teams = res.body; - } - this.loading = false; - } catch (e) { - this.loading = false; - log_error(e); - } - } - - @HostListener(`document:${EventMenuChanged}`) - private buildNavigation() { - const navigation: NavItem[] = []; - navigation.push({label: "Home", icon: "appstore", url: 'home', type: NavItemType.MenuItem}); - navigation.push({label: "Projects", icon: "project", url: 'projects', type: NavItemType.MenuItem}); - - if (this.profile?.owner || this.profile?.is_admin) { - navigation.push({label: "Schedule", icon: "team", url: 'schedule', type: NavItemType.MenuItem}); - navigation.push({label: "Reporting", icon: "team", url: 'reporting', type: NavItemType.MenuItem}); - - if (this.menu.isPinned(this.menu.CLIENTS_MENU)) - navigation.push({label: "Clients", icon: "team", url: 'settings/clients', type: NavItemType.MenuItem}); - if (this.menu.isPinned(this.menu.JOB_TITLES_MENU)) - navigation.push({ - label: "Job Titles", - icon: "team", - url: 'settings/job-titles', - type: NavItemType.MenuItem - }); - if (this.menu.isPinned(this.menu.TEAMS_MENU)) - navigation.push({label: "Teams", icon: "team", url: 'settings/teams', type: NavItemType.MenuItem}); - if (this.menu.isPinned(this.menu.LABELS_MENU)) - navigation.push({label: "Labels", icon: "tags", url: 'settings/labels', type: NavItemType.MenuItem}); - if (this.menu.isPinned(this.menu.TASK_STATUSES_MENU)) - navigation.push({ - label: "Task Statuses", - icon: "team", - url: 'settings/statuses', - type: NavItemType.MenuItem - }); - } - - this.navigation = [...navigation]; - } - - private async getNotificationSettings() { - try { - const res = await this.settingsApi.getNotificationSettings(); - if (res.done) { - this.notificationSettings.settings = res.body; - } - } catch (e) { - // ignored - } - } - - private displayReconnectedMessage() { - if (this.messageId) { - this.message.remove(this.messageId); - this.message.success("Connected to the server.", {nzDuration: 2000}); - this.messageId = null; - void this.checkForUpdates(); - this.reconnecting = false; - } - } - - @HostListener(`document:${EventProfilePictureChange}`) - public async checkForUpdates() { - await this.auth.authorize(); - const key = "worklenz-build-version"; - const v = this.profile?.build_v || null; - const storedVersion = localStorage.getItem(key); - - if (storedVersion == null && v) { - localStorage.setItem(key, v); - this.updateAvailable = false; - return false; - } - - this.updateAvailable = !!(v && v !== storedVersion); - if (this.updateAvailable) { - localStorage.setItem(key, v as string); - } - - return this.updateAvailable; - } - - private displayDisconnectedMessage() { - if (this.reconnecting) return; - this.reconnecting = true; - this.message.error("You are disconnected from the server!", {nzDuration: 1500}); - this.messageId = this.message.loading("Trying to reconnect...", {nzDuration: 0}).messageId; - } -} diff --git a/worklenz-frontend/src/app/administrator/layout/licensing-alerts/licensing-alerts.component.html b/worklenz-frontend/src/app/administrator/layout/licensing-alerts/licensing-alerts.component.html deleted file mode 100644 index c0eb809b..00000000 --- a/worklenz-frontend/src/app/administrator/layout/licensing-alerts/licensing-alerts.component.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - Action required! {{string1}} {{string2}} - diff --git a/worklenz-frontend/src/app/administrator/layout/licensing-alerts/licensing-alerts.component.scss b/worklenz-frontend/src/app/administrator/layout/licensing-alerts/licensing-alerts.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/layout/licensing-alerts/licensing-alerts.component.spec.ts b/worklenz-frontend/src/app/administrator/layout/licensing-alerts/licensing-alerts.component.spec.ts deleted file mode 100644 index 667f1f4f..00000000 --- a/worklenz-frontend/src/app/administrator/layout/licensing-alerts/licensing-alerts.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { LicensingAlertsComponent } from './licensing-alerts.component'; - -describe('LicensingAlertsComponent', () => { - let component: LicensingAlertsComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [LicensingAlertsComponent] - }); - fixture = TestBed.createComponent(LicensingAlertsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/layout/licensing-alerts/licensing-alerts.component.ts b/worklenz-frontend/src/app/administrator/layout/licensing-alerts/licensing-alerts.component.ts deleted file mode 100644 index daaea55a..00000000 --- a/worklenz-frontend/src/app/administrator/layout/licensing-alerts/licensing-alerts.component.ts +++ /dev/null @@ -1,68 +0,0 @@ -import {Component, Input} from '@angular/core'; -import moment from "moment"; - -@Component({ - selector: 'worklenz-licensing-alerts', - templateUrl: './licensing-alerts.component.html', - styleUrls: ['./licensing-alerts.component.scss'] -}) -export class LicensingAlertsComponent { - @Input() profile: any; - - readonly licensingClose = "worklenz.licensing_close"; - - string1 = ''; - string2 = ''; - - getVisible(): boolean { - const date = this.getLicensingLastClose(); - if (date) { - if ((moment(date).isSame(moment(), 'day'))) return false; - } - - const validTillDate = moment(this.profile.valid_till_date); - if (validTillDate.isAfter(moment(), 'days')) validTillDate.add(1, 'day'); - - // Calculate the difference in days between the two dates - const daysDifference = validTillDate.diff(moment(), 'days'); - if (!this.profile.valid_till_date || daysDifference >= 7) return false; - - this.string2 = ` ${Math.abs(daysDifference)} day${Math.abs(daysDifference) === 1 ? "" : 's'}`; - - if (this.profile.subscription_status === 'trialing') { - if (daysDifference < 0) { - this.string1 = `Your Worklenz trial expired`; - this.string2 = this.string2 + ' ago'; - } else if (daysDifference !== 0 && daysDifference < 7) { - this.string1 = `Your Worklenz trial expires in`; - } else if (daysDifference === 0 && daysDifference < 7) { - this.string1 = `Your Worklenz trial expires `; - this.string2 = 'today'; - } - return true; - } - - if (this.profile.subscription_status === 'active') { - if (daysDifference < 0) { - this.string1 = `Your Worklenz subscription expired`; - this.string2 = this.string2 + ' ago'; - } else if (daysDifference !== 0 && daysDifference < 7) { - this.string1 = `Your Worklenz subscription expires in`; - } else if (daysDifference === 0 && daysDifference < 7) { - this.string1 = `Your Worklenz subscription expires `; - this.string2 = 'today'; - } - return true; - } - - return false - } - - setLicensingLastClose() { - localStorage.setItem(this.licensingClose, moment().format('YYYY-MM-DD')); - } - - getLicensingLastClose() { - return localStorage.getItem(this.licensingClose); - } -} diff --git a/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notification-template/notification-template.component.html b/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notification-template/notification-template.component.html deleted file mode 100644 index df702a05..00000000 --- a/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notification-template/notification-template.component.html +++ /dev/null @@ -1,27 +0,0 @@ - -
- - -
- {{data.team}} -
- -
-
-
- {{data.project}} -
-
- - - - - -
-
- - - - diff --git a/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notification-template/notification-template.component.scss b/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notification-template/notification-template.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notification-template/notification-template.component.spec.ts b/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notification-template/notification-template.component.spec.ts deleted file mode 100644 index 693fd6dd..00000000 --- a/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notification-template/notification-template.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {NotificationTemplateComponent} from './notification-template.component'; - -describe('NotificationTemplateComponent', () => { - let component: NotificationTemplateComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [NotificationTemplateComponent] - }); - fixture = TestBed.createComponent(NotificationTemplateComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notification-template/notification-template.component.ts b/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notification-template/notification-template.component.ts deleted file mode 100644 index 055667a1..00000000 --- a/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notification-template/notification-template.component.ts +++ /dev/null @@ -1,75 +0,0 @@ -import {ChangeDetectionStrategy, Component, TemplateRef, ViewChild} from '@angular/core'; -import {NzNotificationService} from "ng-zorro-antd/notification"; -import {Router} from "@angular/router"; -import {IWorklenzNotification} from "@interfaces/worklenz-notification"; -import {NotificationSettingsService} from "@services/notification-settings.service"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {AuthService} from "@services/auth.service"; -import {TeamsApiService} from "@api/teams-api.service"; - -@Component({ - selector: 'worklenz-notification-template', - templateUrl: './notification-template.component.html', - styleUrls: ['./notification-template.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class NotificationTemplateComponent { - @ViewChild("template", {static: false}) templateRef!: TemplateRef; - @ViewChild("closeIcon", {static: false}) closeIconRef!: TemplateRef; - - private session: ILocalSession | null = null; - - constructor( - private readonly service: NzNotificationService, - private readonly router: Router, - private readonly auth: AuthService, - private readonly teamApi: TeamsApiService, - private readonly settings: NotificationSettingsService - ) { - this.session = this.auth.getCurrentSession(); - } - - public show(data: IWorklenzNotification) { - - data.color = data.color || "#191919"; - - const style = { - cursor: "pointer", - borderRadius: "15px", - border: `2px solid ${data.color}4d` - }; - - const notificationRef = this.service.template(this.templateRef, { - nzDuration: 5000, - nzData: data, - nzStyle: style, - nzCloseIcon: this.closeIconRef - }); - - notificationRef.onClick.subscribe(async () => { - this.service.remove(notificationRef.messageId); - - if (data.url) { - if (this.session?.team_id !== data.team_id) { - await this.teamApi.activate(data.team_id); - await this.auth.authorize(); - } - - await this.router.navigate([data.url], { - queryParams: data.params || null - }); - } - - if (data.project && data.task_id) - this.settings.emitNotificationClick({ - project: data.project, - task: data.task_id - }); - }); - } - - close(event: MouseEvent, notification: any) { - event.stopPropagation(); - notification.close(); - } -} diff --git a/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notifications-drawer.component.html b/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notifications-drawer.component.html deleted file mode 100644 index d669ea7e..00000000 --- a/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notifications-drawer.component.html +++ /dev/null @@ -1,96 +0,0 @@ - - -
- - - - -
- - - - - - - - -
-
- - -
-
-
- You have been invited to work with {{item.team_name}}. -
- - - - -
-
-
- - -
-
-
-
- {{item.team}} -
-
-
- {{item.project}} -
-
- -
- - {{item.created_at | fromNow}} -
-
-
-
- - diff --git a/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notifications-drawer.component.scss b/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notifications-drawer.component.scss deleted file mode 100644 index 55ceff0d..00000000 --- a/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notifications-drawer.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -.scroll-viewport { - height: calc(100vh - 140px); -} diff --git a/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notifications-drawer.component.spec.ts b/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notifications-drawer.component.spec.ts deleted file mode 100644 index 4e05c482..00000000 --- a/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notifications-drawer.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {NotificationsDrawerComponent} from './notifications-drawer.component'; - -describe('NotificationsDrawerComponent', () => { - let component: NotificationsDrawerComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [NotificationsDrawerComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(NotificationsDrawerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notifications-drawer.component.ts b/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notifications-drawer.component.ts deleted file mode 100644 index e69fc651..00000000 --- a/worklenz-frontend/src/app/administrator/layout/notifications-drawer/notifications-drawer.component.ts +++ /dev/null @@ -1,433 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - HostListener, - Input, - NgZone, - OnDestroy, - OnInit, - Output, - Renderer2, - ViewChild -} from '@angular/core'; -import {IAcceptTeamInvite} from "@interfaces/team"; -import {TeamsApiService} from "@api/teams-api.service"; -import {Router} from "@angular/router"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {NotificationsApiService} from "@api/notifications-api.service"; -import {ITeamInvitationViewModel} from "@interfaces/api-models/team-invitation-view-model"; -import {AuthService} from "@services/auth.service"; -import {log_error, toQueryString} from "@shared/utils"; -import {NotificationSettingsService} from "@services/notification-settings.service"; -import {NotificationTemplateComponent} from "./notification-template/notification-template.component"; -import {IWorklenzNotification} from "@interfaces/worklenz-notification"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {IInvitationResponse} from "@interfaces/invitation-response"; -import {NotificationsDataModel} from "./types"; -import {HTML_TAG_REGEXP} from "@shared/constants"; - -@Component({ - selector: 'worklenz-notifications-drawer', - templateUrl: './notifications-drawer.component.html', - styleUrls: ['./notifications-drawer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class NotificationsDrawerComponent implements OnInit, OnDestroy { - @ViewChild("template", {static: false}) notificationTemplate!: NotificationTemplateComponent; - - @Input() show = false; - @Output() showChange: EventEmitter = new EventEmitter(); - - @Input() count = 0; - @Output() countChange: EventEmitter = new EventEmitter(); - - readonly OPTION_UNREAD = "Unread"; - readonly OPTION_READ = "Read"; - readonly options = [this.OPTION_UNREAD, this.OPTION_READ]; - - private invitations: ITeamInvitationViewModel[] = []; - private notifications: IWorklenzNotification[] = []; - private showBrowserPush = false; - private session: ILocalSession | null = null; - private _dataset: NotificationsDataModel = []; - - loading = false; - loadingInvitations = false; - activatingTeam = false; - acceptingInvitation = false; - accepting = false; - joining = false; - readAllInProgress = false; - - invitationsCount = 0; - notificationsCount = 0; - unreadNotificationsCount = 0; - - selectedFilter = this.OPTION_UNREAD; - - dataset: NotificationsDataModel = []; - loadersMap: { [x: string]: boolean } = {}; - - get title() { - return `${this.selectedFilter} Notifications (${this.dataset.length})`; - } - - constructor( - private readonly api: TeamsApiService, - private readonly notificationsApi: NotificationsApiService, - private readonly auth: AuthService, - private readonly router: Router, - private readonly socket: Socket, - private readonly settingsService: NotificationSettingsService, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly renderer: Renderer2, - private readonly teamApi: TeamsApiService - ) { - this.askPushPerm(); - this.session = this.auth.getCurrentSession(); - } - - ngOnInit() { - void this.init(); - this.socket.on(SocketEvents.INVITATIONS_UPDATE.toString(), this.onInvitationsUpdate); - this.socket.on(SocketEvents.NOTIFICATIONS_UPDATE.toString(), this.onNotificationsUpdate); - this.socket.on(SocketEvents.TEAM_MEMBER_REMOVED.toString(), this.onInvitationDelete); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.INVITATIONS_UPDATE.toString(), this.onInvitationsUpdate); - this.socket.removeListener(SocketEvents.NOTIFICATIONS_UPDATE.toString(), this.onNotificationsUpdate); - this.socket.removeListener(SocketEvents.TEAM_MEMBER_REMOVED.toString(), this.onInvitationDelete); - } - - private async init() { - this.dataset = []; - this._dataset = []; - await this.getInvites(); - await this.getNotifies(); - await this.getUnreadCount(); - this.sortDataset(); - this.dataset = [...this._dataset]; - this.cdr.markForCheck(); - } - - @HostListener("document:visibilitychange") - private onFocusChange() { - this.ngZone.runOutsideAngular(() => { - this.showBrowserPush = document.visibilityState === "hidden"; - }); - } - - private sortDataset() { - const invitation = "invitation"; - this._dataset.sort((a, b) => { - if (a.type === invitation && b.type !== invitation) return -1; - if (a.type !== invitation && b.type === invitation) return 1; - return 0; - }); - } - - async selectTeam(id: string | undefined) { - if (!id) return; - this.activatingTeam = true; - try { - const res = await this.api.activate(id); - if (res.done) { - await this.router.navigate(['/worklenz']); - } - this.activatingTeam = false; - } catch (e) { - this.activatingTeam = false; - log_error(e); - } - } - - closeDrawer() { - this.show = false; - this.showChange.emit(false); - } - - isUnreadNotifications() { - return this.selectedFilter === this.OPTION_UNREAD; - } - - inProgress() { - return this.accepting || this.joining || this.acceptingInvitation || this.activatingTeam; - } - - trackByFn(index: number, item: any) { - return item.id; - } - - isEmpty() { - return (this.invitationsCount + this.notificationsCount) === 0; - } - - private askPushPerm() { - if('Notification' in window && 'serviceWorker' in navigator && 'PushManager' in window) { - // The user hasn't been asked for permission yet - if (Notification.permission === "default") { - this.ngZone.runOutsideAngular(() => { - // Let's check if the browser supports notifications - if (!('Notification' in window)) { - console.log("This browser does not support notifications."); - return; - } - void Notification.requestPermission(); - }); - } - } else { - console.log("This browser does not support notification permission."); - return; - } - } - - async accept(event: MouseEvent, item?: ITeamInvitationViewModel) { - if (!item) return; - const res = await this.acceptInvite(item.team_member_id, false); - if (res) { - this.markNotificationAsRead(event, item.id) - await this.init(); - } - this.cdr.markForCheck(); - } - - async acceptAndJoin(item?: ITeamInvitationViewModel) { - if (!item) return; - this.joining = true; - item.joining = true; - - this.cdr.detectChanges(); - - const res = await this.acceptInvite(item.team_member_id, true); - if (res) { - void this.init(); - await this.selectTeam(res.id); - await this.auth.authorize(); - this.closeDrawer(); - window.location.reload(); - } - item.joining = false; - this.joining = false; - - this.cdr.markForCheck(); - } - - async readAll() { - try { - this.readAllInProgress = true; - - this.cdr.detectChanges(); - - const res = await this.notificationsApi.readAll(); - if (res) { - await this.init(); - } - this.readAllInProgress = false; - } catch (e) { - this.readAllInProgress = false; - } - - this.cdr.markForCheck(); - } - - private async getInvites() { - try { - this.loadingInvitations = true; - const res = await this.api.getInvites(); - if (res.done) { - this.invitations = res.body; - this.invitationsCount = this.invitations.length; - for (const item of this.invitations) { - this._dataset.push({type: "invitation", data: item}); - } - } - this.loadingInvitations = false; - } catch (e) { - this.loadingInvitations = false; - log_error(e); - } - } - - private async getNotifies() { - try { - this.loading = true; - const res = await this.notificationsApi.get(this.selectedFilter); - if (res.done) { - this.notifications = res.body; - this.notificationsCount = this.notifications.length; - this.settingsService.count = this.notificationsCount; - - for (const item of this.notifications) { - this._dataset.push({type: "notification", data: item}); - } - } - this.loading = false; - } catch (e) { - log_error(e); - this.loading = false; - } - } - - private async getUnreadCount() { - try { - const res = await this.notificationsApi.getUnreadCount(); - if (res.done) { - this.unreadNotificationsCount = res.body; - this.emitCountChange(); - this.cdr.markForCheck(); - } - } catch (e) { - log_error(e); - } - this.cdr.markForCheck(); - } - - private async acceptInvite(teamMemberId?: string, showAlert?: boolean) { - if (!teamMemberId) return; - try { - this.acceptingInvitation = true; - const body: IAcceptTeamInvite = { - team_member_id: teamMemberId, - show_alert: showAlert - }; - const res = await this.api.accept(body); - this.acceptingInvitation = false; - if (res.done && res.body.id) { - return res.body; - } - } catch (e) { - this.acceptingInvitation = false; - log_error(e); - } - return null; - } - - private emitCountChange() { - this.countChange.emit(this.unreadNotificationsCount); - this.settingsService.emitCountsUpdate(); - } - - handleVisibilityChange(visible: boolean) { - this.ngZone.runOutsideAngular(() => { - if (visible) { - this.renderer.setStyle(document.documentElement, "overflow", "hidden"); - } else { - this.renderer.removeStyle(document.documentElement, "overflow"); - } - }); - } - - async markNotificationAsRead(event: MouseEvent, id?: string) { - event.stopPropagation(); - if (!id) return; - this.loadersMap[id] = true; - const res = await this.notificationsApi.update(id); - if (res) { - this.notificationsCount--; - this.dataset.splice(this.dataset.findIndex(n => n.data.id === id), 1); - this._dataset.splice(this._dataset.findIndex(n => n.data.id === id), 1); - } - this.loadersMap[id] = false; - this.getUnreadCount(); - this.cdr.markForCheck(); - } - - async goToUrl(event: MouseEvent, notification: IWorklenzNotification) { - event.stopPropagation(); - if (notification.url) { - this.closeDrawer(); - - if (this.session?.team_id !== notification.team_id) { - await this.teamApi.activate(notification.team_id); - await this.auth.authorize(); - } - - if (notification.project && notification.task_id) - this.settingsService.emitNotificationClick({ - project: notification.project, - task: notification.task_id - }); - - void this.router.navigate([notification.url], { - queryParams: notification.params || null - }); - - this.cdr.markForCheck(); - - } - } - - private isPushEnabled() { - return !!this.settingsService.settings.popup_notifications_enabled; - } - - private createPush(message: string, title: string, teamId: string | null, url?: string) { - if (Notification.permission === "granted" && this.showBrowserPush) { - this.ngZone.runOutsideAngular(() => { - const img = 'https://worklenz.com/assets/icons/icon-128x128.png'; - new Notification(title, { - body: message.replace(HTML_TAG_REGEXP, ''), - icon: img, - badge: img - }).onclick = async (event) => { - if (url) { - window.focus(); - - if (teamId && this.session?.team_id !== teamId) { - await this.teamApi.activate(teamId); - await this.auth.authorize(); - } - - window.location.href = url; - } - }; - }); - } - } - - private onInvitationsUpdate = async (response: IInvitationResponse) => { - if (this.isPushEnabled()) { - this.createPush(response.message, response.team, response.team_id); - this.notificationTemplate.show({ - id: "", - team: response.team, - team_id: response.team_id, - message: response.message, - }); - } - void this.init(); - } - - private onInvitationDelete = async (response: any) => { - void this.init(); - this.getUnreadCount(); - } - - private onNotificationsUpdate = (notification: IWorklenzNotification) => { - if (this.isPushEnabled()) { - const title = notification.team ? `${notification.team} | Worklenz` : "Worklenz"; - - let url = notification.url; - if (url && notification.params && Object.keys(notification.params).length) { - const q = toQueryString(notification.params); - url += q; - } - - this.createPush(notification.message, title, notification.team_id, url); - this.notificationTemplate.show(notification); - } - - void this.init(); - } - - onOptionChange(index: number) { - this.selectedFilter = this.options[index]; - this.loading = true; - void this.init(); - } -} diff --git a/worklenz-frontend/src/app/administrator/layout/notifications-drawer/tag-background.pipe.ts b/worklenz-frontend/src/app/administrator/layout/notifications-drawer/tag-background.pipe.ts deleted file mode 100644 index f79ff894..00000000 --- a/worklenz-frontend/src/app/administrator/layout/notifications-drawer/tag-background.pipe.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; - -@Pipe({ - name: 'tagBackground' -}) -export class TagBackgroundPipe implements PipeTransform { - transform(value?: string) { - if (!value) { - value = "#333333"; - } - return `background-color: ${value}ff;color: ${value};border: 1px solid ${value};`; - } -} diff --git a/worklenz-frontend/src/app/administrator/layout/notifications-drawer/types.ts b/worklenz-frontend/src/app/administrator/layout/notifications-drawer/types.ts deleted file mode 100644 index e8d71f65..00000000 --- a/worklenz-frontend/src/app/administrator/layout/notifications-drawer/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {ITeamInvitationViewModel} from "@interfaces/api-models/team-invitation-view-model"; -import {IWorklenzNotification} from "@interfaces/worklenz-notification"; - -export declare type NotificationsDataModel = Array<{ - type: 'invitation' | 'notification'; - data: ITeamInvitationViewModel | IWorklenzNotification; -}>; diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-card/task-card.component.html b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-card/task-card.component.html deleted file mode 100644 index b2ede3a7..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-card/task-card.component.html +++ /dev/null @@ -1,43 +0,0 @@ -
-
-
- -
-
- -
-
- -
- - - - - - - -
- - - - -
diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-card/task-card.component.scss b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-card/task-card.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-card/task-card.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-card/task-card.component.spec.ts deleted file mode 100644 index daf9507d..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-card/task-card.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskCardComponent} from './task-card.component'; - -describe('TaskCardComponent', () => { - let component: TaskCardComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskCardComponent] - }); - fixture = TestBed.createComponent(TaskCardComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-card/task-card.component.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-card/task-card.component.ts deleted file mode 100644 index 06807fd8..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-card/task-card.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* eslint-disable @angular-eslint/no-input-rename */ -import {Component, Input} from '@angular/core'; -import {IProjectTask} from '@interfaces/api-models/project-tasks-view-model'; - -@Component({ - selector: 'worklenz-kanban-task-card', - templateUrl: './task-card.component.html', - styleUrls: ['./task-card.component.scss'] -}) -export class TaskCardComponent { - @Input({required: true}) task!: IProjectTask; - - @Input({required: true}) projectId!: string | null; - -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-creation-assignees/task-creation-assignees.component.html b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-creation-assignees/task-creation-assignees.component.html deleted file mode 100644 index 1c62932a..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-creation-assignees/task-creation-assignees.component.html +++ /dev/null @@ -1,71 +0,0 @@ -
-
- - -
-
- - - - - -
- -
- -
    - -
  • -
    - -
    - {{item.name}} - - {{item.email}} (Pending Invitation) - -
    -
    -
  • -
- -
- - -
-
- - diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-creation-assignees/task-creation-assignees.component.scss b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-creation-assignees/task-creation-assignees.component.scss deleted file mode 100644 index 7ea94ff9..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-creation-assignees/task-creation-assignees.component.scss +++ /dev/null @@ -1,17 +0,0 @@ -.disable { - position: relative; - &:after { - position: absolute; - content: ""; - background: #e7e7e769; - top: 0; - left: 0; - right: 0; - bottom: 0; - width: 100%; - } -} - -.z-top { - z-index: 9; -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-creation-assignees/task-creation-assignees.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-creation-assignees/task-creation-assignees.component.spec.ts deleted file mode 100644 index f74e6062..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-creation-assignees/task-creation-assignees.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskCreationAssigneesComponent} from './task-creation-assignees.component'; - -describe('TaskCreationAssigneesComponent', () => { - let component: TaskCreationAssigneesComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskCreationAssigneesComponent] - }); - fixture = TestBed.createComponent(TaskCreationAssigneesComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-creation-assignees/task-creation-assignees.component.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-creation-assignees/task-creation-assignees.component.ts deleted file mode 100644 index bbadfbf7..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-creation-assignees/task-creation-assignees.component.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - NgZone, - OnDestroy, - OnInit, - ViewChild -} from '@angular/core'; -import {ITeamMemberViewModel} from '@interfaces/api-models/team-members-get-response'; -import {ITaskAssignee} from '@interfaces/task'; -import {ITaskAssigneesUpdateResponse} from '@interfaces/task-assignee-update-response'; -import {AuthService} from '@services/auth.service'; -import {SocketEvents} from '@shared/socket-events'; -import {TaskListV2Service} from 'app/administrator/modules/task-list-v2/task-list-v2.service'; -import {ProjectsService} from 'app/administrator/projects/projects.service'; -import {Socket} from 'ngx-socket-io'; -import {KanbanV2Service} from '../../../kanban-view-v2.service'; -import {UtilsService} from '@services/utils.service'; - -@Component({ - selector: 'worklenz-kanban-task-creation-assignees', - templateUrl: './task-creation-assignees.component.html', - styleUrls: ['./task-creation-assignees.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskCreationAssigneesComponent implements OnInit, OnDestroy { - @ViewChild('memberSearchInput', {static: false}) memberSearchInput!: ElementRef; - searchText: string | null = null; - isOwnerOrAdmin = false; - - constructor( - private readonly auth: AuthService, - private readonly projectsService: ProjectsService, - private readonly socket: Socket, - private readonly ngZone: NgZone, - private readonly cdr: ChangeDetectorRef, - public readonly service: KanbanV2Service, - public readonly taskListService: TaskListV2Service, - private readonly utils: UtilsService - ) { - } - - ngOnInit() { - this.isOwnerOrAdmin = this.auth.isOwnerOrAdmin(); - this.socket.on(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), this.handleResponse); - } - - private sortMembersBySelection(members: ITeamMemberViewModel[]) { - this.utils.sortBySelection(members); - this.utils.sortByPending(members); - } - - handleMembersVisibleChange(visible: boolean) { - if (!this.service.model.task) return; - - const members = [...this.taskListService.members]; - if (visible) { - const assignees = (this.service.model.task.assignees as ITaskAssignee[])?.map(a => a.team_member_id) || []; - for (const member of members) { - if (member.id) - member.selected = assignees.includes(member.id); - } - this.focusMemberSearchInput(); - } else { - this.searchText = null; - for (const member of members) - member.selected = false; - } - this.taskListService.members = members; - - // if (visible) { - // const assignees = (this.service.model.task.assignees as ITaskAssignee[])?.map(a => a.team_member_id) || []; - // for (const member of this.taskListService.members) - // member.selected = assignees.includes(member.id); - // this.focusMemberSearchInput(); - // } else { - // this.searchText = null; - // for (const member of this.taskListService.members) - // member.selected = false; - // } - // - // this.taskListService.members = members; - - this.sortMembersBySelection(this.taskListService.members); - this.cdr.markForCheck(); - } - - private focusMemberSearchInput() { - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.memberSearchInput?.nativeElement?.focus(); - }, 100); - }); - } - - handleMemberChange(item: ITeamMemberViewModel, checked: boolean) { - const task = this.service.model.task; - if (!task) return; - const session = this.auth.getCurrentSession(); - const body = { - team_member_id: item.id, - project_id: this.projectsService.id, - task_id: task.id, - reporter_id: session?.id, - mode: checked ? 0 : 1, - parent_task: task.parent_task_id - }; - this.socket.emit(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), JSON.stringify(body)); - } - - trackById(index: number, item: ITeamMemberViewModel) { - return item.id; - } - - private handleResponse = (response: ITaskAssigneesUpdateResponse) => { - if (!this.service.model.task) return; - try { - if (response) { - this.service.emitOnAssignMembers(response); - this.service.model.task.assignees = response.assignees || []; - this.service.model.task.names = response.names || []; - this.service.emitRefresh(response.id); - this.sortMembersBySelection(this.taskListService.members); - this.cdr.markForCheck(); - } - } catch (e) { - // ignore - } - } - - onInviteClick() { - document.body.click(); - this.taskListService.emitInviteMembers(); - } - - closeDropdown() { - this.ngZone.runOutsideAngular(() => { - document.body.click(); - }); - } - - selectLastValue(event: any) { - if (!event.target.value) { - return; - } else { - const filteredMembers = this.taskListService.members.filter(member => member.name && member.name.toLowerCase().includes(event.target.value.toLowerCase())); - if (filteredMembers.length == 1) { - this.handleMemberChange(filteredMembers[0], !filteredMembers[0].selected); - filteredMembers[0].selected = !filteredMembers[0].selected; - } - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-end-date/task-end-date.component.html b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-end-date/task-end-date.component.html deleted file mode 100644 index f0e5e724..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-end-date/task-end-date.component.html +++ /dev/null @@ -1,16 +0,0 @@ -
- {{task.end_date | dateFormatter}} - - - - -
diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-end-date/task-end-date.component.scss b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-end-date/task-end-date.component.scss deleted file mode 100644 index eedfaf8b..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-end-date/task-end-date.component.scss +++ /dev/null @@ -1,36 +0,0 @@ -nz-date-picker { - padding: 1px 4px; - max-width: 105px; - height: 24px; - margin-left: -4px; - border-color: transparent !important; - - &:hover { - border-color: #d9d9d9 !important; - } -} - -.date-text { - font-size: 12px; - line-height: 24px; -} - -.past-date { - color: #ff4d4f; -} - -.soon-date { - color: #52c41a; -} - -.show-hover { - opacity: 0; - - &:hover { - opacity: 1; - } -} - -.show-def { - opacity: 1; -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-end-date/task-end-date.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-end-date/task-end-date.component.spec.ts deleted file mode 100644 index 06e37db2..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-end-date/task-end-date.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskEndDateComponent} from './task-end-date.component'; - -describe('TaskEndDateComponent', () => { - let component: TaskEndDateComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskEndDateComponent] - }); - fixture = TestBed.createComponent(TaskEndDateComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-end-date/task-end-date.component.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-end-date/task-end-date.component.ts deleted file mode 100644 index 52452254..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-end-date/task-end-date.component.ts +++ /dev/null @@ -1,65 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {IProjectTask} from '@interfaces/api-models/project-tasks-view-model'; -import {SocketEvents} from '@shared/socket-events'; -import moment from 'moment'; -import {Socket} from 'ngx-socket-io'; -import {formatGanttDate} from "@shared/utils"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-kanban-task-end-date', - templateUrl: './task-end-date.component.html', - styleUrls: ['./task-end-date.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskEndDateComponent implements OnInit, OnDestroy { - @Input() task: IProjectTask = {}; - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly auth: AuthService, - ) { - } - - ngOnInit() { - this.socket.on(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.handleResponse); - } - - private handleResponse = (response: { - id: string; - parent_task: string | null; - end_date: string; - }) => { - if (response.id === this.task.id && this.task.end_date !== response.end_date) { - this.task.end_date = response.end_date; - this.cdr.markForCheck(); - } - }; - - handleEndDateChange(date: string, task: IProjectTask) { - this.socket.emit( - SocketEvents.TASK_END_DATE_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - end_date: formatGanttDate(date) || null, - parent_task: task.parent_task_id, - time_zone: this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone - })); - } - - checkForPastDate(endDate: any) { - const formattedEndDate = moment(endDate).format('YYYY-MM-DD'); - return formattedEndDate < moment().format('YYYY-MM-DD'); - } - - checkForSoonDate(endDate: any) { - const formattedEndDate = moment(endDate).format('YYYY-MM-DD'); - const tomorrow = moment().add(1, 'day').format('YYYY-MM-DD'); - return formattedEndDate === moment().format('YYYY-MM-DD') || formattedEndDate === tomorrow; - } - -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-labels/task-labels.component.html b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-labels/task-labels.component.html deleted file mode 100644 index c698ec3e..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-labels/task-labels.component.html +++ /dev/null @@ -1,15 +0,0 @@ -
- - {{task.all_labels?.length}} - - - -
- - - -

{{item.name}}

-
-
diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-labels/task-labels.component.scss b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-labels/task-labels.component.scss deleted file mode 100644 index aba2a49e..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-labels/task-labels.component.scss +++ /dev/null @@ -1,4 +0,0 @@ -nz-tag { - border: none; - color: rgba(0, 0, 0, 0.65); -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-labels/task-labels.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-labels/task-labels.component.spec.ts deleted file mode 100644 index 6c15a9b6..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-labels/task-labels.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskLabelsComponent} from './task-labels.component'; - -describe('TaskLabelsComponent', () => { - let component: TaskLabelsComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskLabelsComponent] - }); - fixture = TestBed.createComponent(TaskLabelsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-labels/task-labels.component.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-labels/task-labels.component.ts deleted file mode 100644 index 412483f0..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-labels/task-labels.component.ts +++ /dev/null @@ -1,94 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {IProjectTask} from '@interfaces/api-models/project-tasks-view-model'; -import {ITaskLabel} from '@interfaces/task-label'; -import {ALPHA_CHANNEL} from '@shared/constants'; -import {SocketEvents} from '@shared/socket-events'; -import {ILabelsChangeResponse} from 'app/administrator/modules/task-list-v2/interfaces'; -import {TaskListHashMapService} from 'app/administrator/modules/task-list-v2/task-list-hash-map.service'; -import {TaskListV2Service} from 'app/administrator/modules/task-list-v2/task-list-v2.service'; -import {Socket} from 'ngx-socket-io'; -import {Subject, takeUntil} from 'rxjs'; - -@Component({ - selector: 'worklenz-kanban-task-labels', - templateUrl: './task-labels.component.html', - styleUrls: ['./task-labels.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskLabelsComponent implements OnInit, OnDestroy { - @Input() task: IProjectTask = {}; - - readonly alpha = ALPHA_CHANNEL; - - labels: ITaskLabel[] = []; - labelsCount?: number | null = null; - - private readonly destroy$ = new Subject(); - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly socket: Socket, - public readonly service: TaskListV2Service, - private readonly map: TaskListHashMapService - ) { - this.service.onLabelsChange$ - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - this.updateLabels(); - this.cdr.markForCheck(); - }); - - } - - ngOnInit() { - this.updateLabels(); - this.socket.on(SocketEvents.TASK_LABELS_CHANGE.toString(), this.handleLabelsChange); - this.socket.on(SocketEvents.CREATE_LABEL.toString(), this.handleLabelsChange); - } - - ngOnDestroy() { - this.labels = []; - this.destroy$.next(); - this.destroy$.complete(); - this.socket.removeListener(SocketEvents.TASK_LABELS_CHANGE.toString(), this.handleLabelsChange); - this.socket.removeListener(SocketEvents.CREATE_LABEL.toString(), this.handleLabelsChange); - } - - private updateLabels() { - this.labels = this.service.labels; - } - - trackById(index: number, item: ITaskLabel) { - return item.id; - } - - private handleLabelsChange = (response: ILabelsChangeResponse) => { - if (response && response.id === this.task.id) { - this.task.labels = response.labels; - this.task.all_labels = response.all_labels; - - if (response.new_label) { - if (response.is_new) { - const labels = [...this.service.labels]; - labels.push(response.new_label); - this.service.labels = [...labels]; - } else { - const label = this.labels.find(l => l.id === response.new_label.id); - if (label) - label.selected = true; - } - } - this.cdr.markForCheck(); - } - } - - handleLabelChange(label: ITaskLabel) { - this.socket.emit(SocketEvents.TASK_LABELS_CHANGE.toString(), JSON.stringify({ - task_id: this.task.id, - label_id: label.id, - parent_task: this.task.parent_task_id - })); - - } - -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-members/task-members.component.html b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-members/task-members.component.html deleted file mode 100644 index f6bc3bdb..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-members/task-members.component.html +++ /dev/null @@ -1,64 +0,0 @@ -
-
- - -
-
- - -
- -
-
    - -
  • -
    - -
    - {{item.name}} - - {{item.email}} (Pending Invitation) - -
    -
    -
  • -
- -
- - -
-
diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-members/task-members.component.scss b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-members/task-members.component.scss deleted file mode 100644 index dd2ad40e..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-members/task-members.component.scss +++ /dev/null @@ -1,33 +0,0 @@ -.show-hover { - opacity: 0; - - &:hover { - opacity: 1; - } -} - -.show-def { - opacity: 1; -} - -.editable { - border-radius: 4px; -} - -.disable { - position: relative; - &:after { - position: absolute; - content: ""; - background: #e7e7e769; - top: 0; - left: 0; - right: 0; - bottom: 0; - width: 100%; - } -} - -.z-top { - z-index: 9; -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-members/task-members.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-members/task-members.component.spec.ts deleted file mode 100644 index 82b8c47d..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-members/task-members.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskMembersComponent} from './task-members.component'; - -describe('TaskMembersComponent', () => { - let component: TaskMembersComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskMembersComponent] - }); - fixture = TestBed.createComponent(TaskMembersComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-members/task-members.component.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-members/task-members.component.ts deleted file mode 100644 index 95bc4d34..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-members/task-members.component.ts +++ /dev/null @@ -1,176 +0,0 @@ -/* eslint-disable @angular-eslint/no-input-rename */ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostBinding, - Input, - NgZone, - OnDestroy, - OnInit, - ViewChild -} from '@angular/core'; -import {ILocalSession} from '@interfaces/api-models/local-session'; -import {IProjectTask} from '@interfaces/api-models/project-tasks-view-model'; -import {ITeamMemberViewModel} from '@interfaces/api-models/team-members-get-response'; -import {ITaskAssigneesUpdateResponse} from '@interfaces/task-assignee-update-response'; -import {AuthService} from '@services/auth.service'; -import {UtilsService} from '@services/utils.service'; -import {SocketEvents} from '@shared/socket-events'; -import {TaskListV2Service} from 'app/administrator/modules/task-list-v2/task-list-v2.service'; -import {Socket} from 'ngx-socket-io'; -import {filter, Subject, takeUntil} from 'rxjs'; - -@Component({ - selector: 'worklenz-kanban-task-members', - templateUrl: './task-members.component.html', - styleUrls: ['./task-members.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskMembersComponent implements OnInit, OnDestroy { - @ViewChild('memberSearchInput', {static: false}) memberSearchInput!: ElementRef; - @Input() task: IProjectTask = {}; - @Input({required: true}) projectId!: string | null; - @HostBinding("class") cls = "flex-row task-members"; - - searchText: string | null = null; - members: ITeamMemberViewModel[] = []; - - private session: ILocalSession | null = null; - - show = false; - isOwnerOrAdmin = false; - - get avatarClass() { - return this.task.assignees?.length - ? 'add-button avatar-dashed ms-1 bg-white' - : 'avatar-dashed bg-white'; - } - - private readonly destroy$ = new Subject(); - - constructor( - private readonly service: TaskListV2Service, - private readonly cdr: ChangeDetectorRef, - private readonly socket: Socket, - private readonly auth: AuthService, - private readonly utils: UtilsService, - private readonly ngZone: NgZone - ) { - this.service.onMembersChange$ - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - this.cdr.markForCheck(); - this.updateMembers(); - }); - - this.service.onAssignMe$ - .pipe( - takeUntil(this.destroy$), - filter(value => value.id === this.task.id) - ) - .subscribe((value) => { - this.handleResponse(value); - }); - } - - ngOnInit() { - this.session = this.auth.getCurrentSession(); - this.isOwnerOrAdmin = this.auth.isOwnerOrAdmin(); - this.updateMembers(); - this.socket.on(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.members = []; - this.destroy$.next(); - this.destroy$.complete(); - this.socket.removeListener(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), this.handleResponse); - } - - private handleResponse = (response: ITaskAssigneesUpdateResponse) => { - try { - if (response && response.id === this.task.id) { - this.task.assignees = response.assignees || []; - this.task.names = response.names || []; - this.cdr.markForCheck(); - } - } catch (e) { - // ignore - } - } - - private updateMembers() { - this.members = this.service.members; - } - - private sortMembersBySelection(members: ITeamMemberViewModel[]) { - this.utils.sortBySelection(members); - this.utils.sortByPending(members); - } - - trackById(index: number, item: ITeamMemberViewModel) { - return item.id; - } - - handleVisibleChange(visible: boolean) { - this.show = visible; - if (visible) { - const assignees = this.task.assignees?.map(a => a.team_member_id) || []; - for (const member of this.members) - member.selected = assignees.includes(member.id); - this.focusMemberSearchInput(); - } else { - this.searchText = null; - for (const member of this.members) - member.selected = false; - } - - this.sortMembersBySelection(this.members); - } - - private focusMemberSearchInput() { - setTimeout(() => { - this.memberSearchInput?.nativeElement?.focus(); - }, 100); - } - - handleMemberChange(member: ITeamMemberViewModel, checked: boolean) { - if (!this.session) return; - const body = { - team_member_id: member.id, - project_id: this.projectId, - task_id: this.task.id, - reporter_id: this.session.id, - mode: checked ? 0 : 1, - parent_task: this.task.parent_task_id - }; - this.socket.emit(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), JSON.stringify(body)); - this.sortMembersBySelection(this.members); - } - - closeDropdown() { - this.ngZone.runOutsideAngular(() => { - document.body.click(); - }); - } - - onInviteClick() { - document.body.click(); - this.service.emitInviteMembers(); - } - - selectLastValue(event: any) { - if (!event.target.value) { - return; - } else { - const filteredMembers = this.members.filter(member => member.name && member.name.toLowerCase().includes(event.target.value.toLowerCase())); - if (filteredMembers.length == 1) { - this.handleMemberChange(filteredMembers[0], !filteredMembers[0].selected); - filteredMembers[0].selected = !filteredMembers[0].selected; - } - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-name/task-name.component.html b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-name/task-name.component.html deleted file mode 100644 index e40a37c1..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-name/task-name.component.html +++ /dev/null @@ -1,19 +0,0 @@ -
- - - - - {{task.name}} - -
- - -
Loading...
-

Sub-task of {{taskName}}

-
diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-name/task-name.component.scss b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-name/task-name.component.scss deleted file mode 100644 index 237749dc..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-name/task-name.component.scss +++ /dev/null @@ -1,8 +0,0 @@ -nz-tag { - border: none; - color: rgba(0, 0, 0, 0.65); -} - -.task-name-kanban { - font-weight: 500; -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-name/task-name.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-name/task-name.component.spec.ts deleted file mode 100644 index 9c9d48b8..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-name/task-name.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskNameComponent} from './task-name.component'; - -describe('TaskNameComponent', () => { - let component: TaskNameComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskNameComponent] - }); - fixture = TestBed.createComponent(TaskNameComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-name/task-name.component.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-name/task-name.component.ts deleted file mode 100644 index 50d914ce..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-name/task-name.component.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* eslint-disable @angular-eslint/no-input-rename */ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit} from '@angular/core'; -import {IProjectTask} from '@interfaces/api-models/project-tasks-view-model'; -import {SocketEvents} from '@shared/socket-events'; -import {Socket} from 'ngx-socket-io'; -import {KanbanV2Service} from '../../../kanban-view-v2.service'; -import {Subject, takeUntil} from 'rxjs'; -import {log_error} from '@shared/utils'; -import {TaskListHashMapService} from 'app/administrator/modules/task-list-v2/task-list-hash-map.service'; -import {TaskListV2Service} from 'app/administrator/modules/task-list-v2/task-list-v2.service'; - -@Component({ - selector: 'worklenz-kanban-task-name', - templateUrl: './task-name.component.html', - styleUrls: ['./task-name.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskNameComponent implements OnInit { - - @Input({required: true}) task!: IProjectTask; - - loadingName = false; - taskName: string | null = null; - - private readonly destroy$ = new Subject(); - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private kanbanService: KanbanV2Service, - private readonly map: TaskListHashMapService, - private readonly list: TaskListV2Service - ) { - this.kanbanService.onCreateSubTask - .pipe(takeUntil(this.destroy$)) - .subscribe(value => { - // this.cdr.markForCheck(); - }); - } - - ngOnInit() { - this.socket.on(SocketEvents.TASK_NAME_CHANGE.toString(), this.handleResponse); - } - - private handleResponse = (response: { - id: string; - parent_task: string | null; - name: string; - }) => { - if (response.id === this.task.id && this.task.name !== response.name) { - this.task.name = response.name; - this.cdr.markForCheck(); - } - }; - - async getParentTaskName(id?: string) { - if (!id) return; - try { - this.loadingName = true; - - const groupId = this.map.getGroupId(this.task.parent_task_id as string); - if (!groupId || !this.task.parent_task_id) return; - - const group = this.list.groups.find(g => g.id === groupId); - if (!group) return; - - const parentTask = group.tasks.find(t => t.id === this.task.parent_task_id); - this.taskName = parentTask?.name as string; - - this.loadingName = false; - } catch (e) { - log_error(e); - this.loadingName = false; - } - - this.cdr.markForCheck(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-priority/task-priority.component.html b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-priority/task-priority.component.html deleted file mode 100644 index 4c54f58d..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-priority/task-priority.component.html +++ /dev/null @@ -1,13 +0,0 @@ -
-
- - - -
-
diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-priority/task-priority.component.scss b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-priority/task-priority.component.scss deleted file mode 100644 index f7e01c70..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-priority/task-priority.component.scss +++ /dev/null @@ -1,13 +0,0 @@ -.low-priority { - color: #52c41a; -} - -.medium-priority { - color: #faad14; - transform: rotateZ(90deg); -} - -.high-priority { - color: #f5222d; - transform: rotateZ(90deg); -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-priority/task-priority.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-priority/task-priority.component.spec.ts deleted file mode 100644 index 816a831a..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-priority/task-priority.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskPriorityComponent} from './task-priority.component'; - -describe('TaskPriorityComponent', () => { - let component: TaskPriorityComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskPriorityComponent] - }); - fixture = TestBed.createComponent(TaskPriorityComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-priority/task-priority.component.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-priority/task-priority.component.ts deleted file mode 100644 index f5385d4a..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-priority/task-priority.component.ts +++ /dev/null @@ -1,60 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {IProjectTask} from '@interfaces/api-models/project-tasks-view-model'; -import {ITaskPrioritiesGetResponse} from '@interfaces/api-models/task-priorities-get-response'; -import {ITaskStatusViewModel} from '@interfaces/api-models/task-status-get-response'; -import {SocketEvents} from '@shared/socket-events'; -import {TaskListV2Service} from 'app/administrator/modules/task-list-v2/task-list-v2.service'; -import {Socket} from 'ngx-socket-io'; -import {Subject} from 'rxjs'; - -@Component({ - selector: 'worklenz-kanban-task-priority', - templateUrl: './task-priority.component.html', - styleUrls: ['./task-priority.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskPriorityComponent implements OnInit, OnDestroy { - @Input() task: IProjectTask = {}; - - priorities: ITaskPrioritiesGetResponse[] = []; - - private readonly destroy$ = new Subject(); - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - public readonly service: TaskListV2Service, - ) { - } - - ngOnInit() { - this.updatePriorities(); - this.socket.on(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.priorities = []; - this.destroy$.next(); - this.destroy$.complete(); - this.socket.removeListener(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.handleResponse); - } - - trackById(index: number, item: ITaskStatusViewModel) { - return item.id; - } - - private handleResponse = (response: { - priority_id: string | undefined; - name: string | undefined; id: string; parent_task: string; color_code: string; - }) => { - if (response && response.id === this.task.id) { - this.task.priority_color = response.color_code; - this.task.priority = response.priority_id; - this.cdr.markForCheck(); - } - } - - private updatePriorities() { - this.priorities = this.service.priorities; - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-progress/task-progress.component.html b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-progress/task-progress.component.html deleted file mode 100644 index fb246449..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-progress/task-progress.component.html +++ /dev/null @@ -1,9 +0,0 @@ -
- -
diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-progress/task-progress.component.scss b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-progress/task-progress.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-progress/task-progress.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-progress/task-progress.component.spec.ts deleted file mode 100644 index a2898f3e..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-progress/task-progress.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskProgressComponent} from './task-progress.component'; - -describe('TaskProgressComponent', () => { - let component: TaskProgressComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskProgressComponent] - }); - fixture = TestBed.createComponent(TaskProgressComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-progress/task-progress.component.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-progress/task-progress.component.ts deleted file mode 100644 index b80ff8ba..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-progress/task-progress.component.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* eslint-disable @angular-eslint/no-input-rename */ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {IProjectTask} from '@interfaces/api-models/project-tasks-view-model'; -import {SocketEvents} from '@shared/socket-events'; -import {TaskListV2Service} from 'app/administrator/modules/task-list-v2/task-list-v2.service'; -import {Socket} from 'ngx-socket-io'; -import {Subject} from 'rxjs'; -import {KanbanV2Service} from '../../../kanban-view-v2.service'; -import {TaskListHashMapService} from 'app/administrator/modules/task-list-v2/task-list-hash-map.service'; - -@Component({ - selector: 'worklenz-kanban-task-progress', - templateUrl: './task-progress.component.html', - styleUrls: ['./task-progress.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskProgressComponent implements OnInit, OnDestroy { - @Input({required: true}) task!: IProjectTask; - private readonly destroy$ = new Subject(); - loadingProgress = false; - progress: string | null = null; - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly list: TaskListV2Service, - private readonly kanbanService: KanbanV2Service, - private readonly map: TaskListHashMapService, - ) { - } - - get percent() { - return this.task.complete_ratio || 0; - } - - ngOnInit() { - this.socket.on(SocketEvents.GET_TASK_PROGRESS.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.GET_TASK_PROGRESS.toString(), this.handleResponse); - this.destroy$.next(); - this.destroy$.complete(); - } - - private handleResponse = (response: { - id: string; - parent_task: string; - complete_ratio: number; - completed_count: number; - total_tasks_count: number; - }) => { - if (response && (response.parent_task === this.task.id || response.id === this.task.id)) { - this.task.complete_ratio = +response.complete_ratio; - this.task.total_tasks_count = response.total_tasks_count; - this.task.completed_count = response.completed_count; - } - this.cdr.markForCheck(); - } - - get tooltipTitle() { - return (this.task.completed_count || 0) + '/' + (this.task.total_tasks_count || 0); - } - -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-subtask-count/task-subtask-count.component.html b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-subtask-count/task-subtask-count.component.html deleted file mode 100644 index 0151afd4..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-subtask-count/task-subtask-count.component.html +++ /dev/null @@ -1,17 +0,0 @@ -
- {{task.sub_tasks_count}} - - -
- - -
Loading...
-

Sub-tasks

-

{{s.name}}

-
diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-subtask-count/task-subtask-count.component.scss b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-subtask-count/task-subtask-count.component.scss deleted file mode 100644 index aba2a49e..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-subtask-count/task-subtask-count.component.scss +++ /dev/null @@ -1,4 +0,0 @@ -nz-tag { - border: none; - color: rgba(0, 0, 0, 0.65); -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-subtask-count/task-subtask-count.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-subtask-count/task-subtask-count.component.spec.ts deleted file mode 100644 index 2204ffa0..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-subtask-count/task-subtask-count.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskSubtaskCountComponent} from './task-subtask-count.component'; - -describe('TaskSubtaskCountComponent', () => { - let component: TaskSubtaskCountComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskSubtaskCountComponent] - }); - fixture = TestBed.createComponent(TaskSubtaskCountComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-subtask-count/task-subtask-count.component.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-subtask-count/task-subtask-count.component.ts deleted file mode 100644 index 15ea3487..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/components/task-subtask-count/task-subtask-count.component.ts +++ /dev/null @@ -1,79 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy} from '@angular/core'; -import {SubTasksApiService} from '@api/sub-tasks-api.service'; -import {IProjectTask} from '@interfaces/api-models/project-tasks-view-model'; -import {ISubTask} from '@interfaces/sub-task'; -import {log_error} from '@shared/utils'; -import {Socket} from 'ngx-socket-io'; -import {KanbanV2Service} from '../../../kanban-view-v2.service'; -import {Subject, takeUntil} from 'rxjs'; -import {TaskListV2Service} from 'app/administrator/modules/task-list-v2/task-list-v2.service'; - -@Component({ - selector: 'worklenz-kanban-task-subtask-count', - templateUrl: './task-subtask-count.component.html', - styleUrls: ['./task-subtask-count.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskSubtaskCountComponent implements OnDestroy { - @Input() task: IProjectTask = {}; - count: number | null = null; - subTasks: ISubTask[] = []; - loadingSubTasks = false; - private readonly destroy$ = new Subject(); - - constructor( - private readonly subTasksApi: SubTasksApiService, - private readonly cdr: ChangeDetectorRef, - private readonly socket: Socket, - private readonly kanbanService: KanbanV2Service, - public readonly service: TaskListV2Service, - ) { - - this.kanbanService.onDeleteSubTask - .pipe(takeUntil(this.destroy$)) - .subscribe(value => { - this.handelSubtaskDelete(value) - }); - - this.kanbanService.onCreateSubTask - .pipe(takeUntil(this.destroy$)) - .subscribe(value => { - cdr.markForCheck(); - }); - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } - - handelSubtaskDelete(value: IProjectTask) { - if (this.task.id === value.parent_task_id && this.task.sub_tasks_count) { - this.task.sub_tasks_count = Math.max(+this.task.sub_tasks_count - 1, 0); - // this.task.sub_tasks_count = this.task.sub_tasks_count - 1; - } - this.cdr.markForCheck(); - } - - async getSubTasks(id?: string) { - if (!id) return; - try { - this.subTasks = []; - this.loadingSubTasks = true; - const res = await this.subTasksApi.getNames(id); - if (res.done) { - this.subTasks = res.body; - } - this.loadingSubTasks = false; - } catch (e) { - log_error(e); - this.loadingSubTasks = false; - } - - this.cdr.markForCheck(); - } - - trackByFn(index: number, data: any) { - return data.id; - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/kanban-board.component.html b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/kanban-board.component.html deleted file mode 100644 index a28d2f5d..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/kanban-board.component.html +++ /dev/null @@ -1,306 +0,0 @@ -
- -
-
-
-
-
- - - -
-
-
- - - - {{ column.name }} ({{column.tasks.length}}) - - - - - - -
    -
  • - - Rename -
  • -
  • -
      -
    • - - - - {{item?.name || null}} - - -
    • -
    -
  • -
  • - Delete -
  • -
-
- - - Change category - - - - - - -
- - {{ column.name }} - ({{column.tasks.length}}) - - -
-
- -
- -
- - - -
- - - - -
    -
  • - Assign to me -
  • -
  • - Archive -
  • -
  • - Delete -
  • -
-
- -
-
- -
-
- -
- There are no status groups to show - in this project. -
- - Click on - - button to create status groups as you desire. - -
-
- -
- -
-
-
-
-
-
-
-
- - - - - - - - - - - - - - -
{{selectedForDelete?.name}}
-

- - - - - - - - -
-
-
diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/kanban-board.component.scss b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/kanban-board.component.scss deleted file mode 100644 index b49914ac..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/kanban-board.component.scss +++ /dev/null @@ -1,401 +0,0 @@ -.root { - display: flex; - flex-direction: column; - height: 100%; -} - -.worklenz-kanban { - display: flex; - flex-direction: column; - flex-grow: 1; - min-width: 0; - min-height: 0; -} - - -.board-wrapper { - display: flex; - flex-grow: 1; - background-color: #fafafa; - padding-top: 6px; - padding-bottom: 6px; -} - -.board-columns { - display: flex; - flex-grow: 1; -} - -.board-column { - display: flex; - flex-direction: column; - flex-grow: 1; - flex-basis: 0; - max-width: 325px; - width: 325px; - margin-right: 8px; - padding: 8px; - border-radius: 4px; - // background: #f4f5f7; - max-height: calc(100vh - 220px); - overflow: auto; - border: 1px solid transparent; - - .column-footer { - position: sticky; - bottom: -2px; - box-shadow: 0px 0px 0px 4px #fafafa; - - button { - opacity: 1; - pointer-events: none; - } - } - - &:hover { - border: 1px solid #f0f0f0; - - .column-footer { - button { - opacity: 1; - pointer-events: auto; - } - } - } -} - -.column-title { - * { - color: rgba(0, 0, 0, 0.85); - font-weight: 600; - } - - font-size: 14px; - padding-top: 0; -} - -.tasks-container { - flex-grow: 1; - overflow-y: auto; - // background-color: #f3f3f3; - padding: 2px; - border-radius: 4px; -} - -.task { - padding: 12px; - background: white; - border-radius: 4px; - margin-bottom: 12px; - cursor: pointer; - position: relative; - overflow: hidden; - // border: 1px solid transparent; - transition: box-shadow 0.3s; - box-shadow: rgb(237, 234, 233) 0px 0px 0px 1px, rgba(109, 110, 111, 0.08) 0px 1px 4px 0px; - - &:hover { - // border: 1px solid #c7c7c7; - box-shadow: rgb(168, 168, 168) 0px 0px 0px 1px, rgba(109, 110, 111, 0.08) 0px 1px 4px 0px; - } -} - -.cdk-drag-preview { - box-sizing: border-box; - //border-radius: 2px; - box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), - 0 8px 10px 1px rgba(0, 0, 0, 0.14), - 0 3px 14px 2px rgba(0, 0, 0, 0.12); -} - -.cdk-drag-placeholder { - opacity: 0; -} - -.kanban-status { - font-size: 12px; - font-weight: 500; -} - -.left-border { - left: 5px; - position: absolute; - width: 3px; - top: 10px; - border-radius: 4px; - bottom: 10px; - margin-top: auto; - margin-bottom: auto; - opacity: 0.8; -} - -nz-avatar-group { - float: right; -} - -.ant-input { - padding: 2px 4px; - color: rgba(0, 0, 0, 0.85); - font-weight: 500; - font-size: 14px; -} - -.create-new-task { - padding: 4px 15px; - cursor: pointer; - background: #1890ff; - transition: 0.25s all; - color: white; - font-weight: 500; - border-radius: 4px; - width: max-content; - text-shadow: 0 -1px 0 rgb(0 0 0 / 12%); - box-shadow: 0 2px 0 rgb(0 0 0 / 5%); -} - -.create-new-task:hover { - border-color: #40a9ff; - background: #40a9ff; -} - -.task-name { - padding-bottom: 0px; - margin-bottom: 8px !important; - line-height: 24px; - // font-weight: 500; -} - -.column-footer { - background: #fafafa; - padding-top: 7px; - z-index: 10; -} - -.new-column-sec { - text-align: right; - padding: 0 0 4px 0; - margin-bottom: 4px; - border-bottom: 1px solid #f0f0f0; - - button { - width: 34px; - height: 34px; - display: flex; - align-items: center; - justify-content: center; - font-size: 24px; - border: transparent; - background: #f4f5f7; - color: #42526e; - } -} - -.text-plus-btn { - width: 34px; - height: 34px; - display: flex; - align-items: center; - justify-content: center; - font-size: 24px; - border: transparent; - background: #f4f5f7; - color: #42526e; -} - -.new-board-btn { - padding: 15px 35px 15px 35px; - height: auto; - border-radius: 4px; - background: #f4f5f7; - color: #000000d9; - border: none; - font-weight: 500; -} - -.new-board-btn:hover { - background: #40a9ff; - color: white; -} - -*::-webkit-scrollbar-track, -*::-webkit-scrollbar { - background-color: transparent !important; -} - -*::-webkit-scrollbar-thumb { - border-color: #f4f5f7 !important; -} - -.column-title { - cursor: grab; - - &:hover:active, - &:active { - cursor: grabbing; - } -} - -.kanban-status-label { - margin: -5px; - padding: 5px; - border-radius: 5px; - font-weight: 600; - - &:hover { - background: #f3f3f3; - } -} - -.kanban-category-badge { - cursor: pointer; - font-size: 11px; - width: 16px; - height: 16px; -} - -nz-tag { - border: none; - color: rgba(0, 0, 0, 0.65); -} - -.low-priority { - color: #52c41a; -} - -.medium-priority { - color: #faad14; - transform: rotateZ(90deg); -} - -.high-priority { - color: #f5222d; - transform: rotateZ(90deg); -} - -.subtask-icon-main { - font-size: 12px; - padding: 2px; - - & .anticon { - margin-left: 4px; - } -} - -.subtasks-count-tag { - // border: 1px solid rgba(0, 0, 0, 0.85); - background: white; -} - -.add-task-btn { - height: 38px; - - & span { - color: rgba(0, 0, 0, 0.65); - transition: all 0.3s; - } - - &:hover span { - color: #1890ff; - } - - &:active span { - color: #1890ff; - } - - &:focus span { - color: #1890ff; - } - - &:focus-visible span { - color: #1890ff; - } -} - -.no-data-img-holder { - width: 100px; - margin-top: 72px; -} - -.task-create-card { - min-height: 110px; - box-shadow: rgb(168, 168, 168) 0px 0px 0px 1px, rgba(109, 110, 111, 0.08) 0px 1px 4px 0px; -} - -.add-task-btn-card { - font-size: 12px; - padding: 2px 7px; - margin-left: auto; - height: auto; -} - -.hide { - pointer-events: none !important; - opacity: 0.6 !important; - cursor: not-allowed; -} - -nz-date-picker { - padding: 1px 4px; - width: 100%; - max-width: 105px; - height: 24px; - margin-left: -4px; - border-color: transparent !important; - - &:hover { - border-color: #d9d9d9 !important; - } -} - -.date-text { - font-size: 12px; - line-height: 24px; - position: absolute; -} - -.past-date { - color: #ff4d4f; -} - -.soon-date { - color: #52c41a; -} - -.spinner { - position: absolute; - z-index: 9; - top: 0; - bottom: 0; - left: 0; - right: 0; - background: rgb(255 255 255 / 65%); - display: flex; - align-items: center; - justify-content: center; -} - -.a-t:disabled { - color: rgba(0, 0, 0, 0.25) !important; - border-color: #d9d9d9 !important; - background: #f5f5f5 !important; -} - -.a-t:hover { - background: #188fff; - color: white; -} - -.bottom-task-add { - background: white; - border: none; - transition: background, color 0.2s; - - &:hover { - background: #f3f3f3; - color: rgba(0, 0, 0, 0.85); - - & span { - color: rgba(0, 0, 0, 0.85); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/kanban-board.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/kanban-board.component.spec.ts deleted file mode 100644 index 2be388eb..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/kanban-board.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {KanbanBoardComponent} from './kanban-board.component'; - -describe('KanbanBoardComponent', () => { - let component: KanbanBoardComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [KanbanBoardComponent] - }); - fixture = TestBed.createComponent(KanbanBoardComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/kanban-board.component.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/kanban-board.component.ts deleted file mode 100644 index 6c3bf5d7..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/kanban-board.component.ts +++ /dev/null @@ -1,1068 +0,0 @@ -import {CdkDragDrop, moveItemInArray, transferArrayItem} from '@angular/cdk/drag-drop'; -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostListener, - NgZone, - OnDestroy, - OnInit, - ViewChild -} from '@angular/core'; -import {ActivatedRoute} from '@angular/router'; -import {TaskStatusesApiService} from '@api/task-statuses-api.service'; -import {TasksApiService} from '@api/tasks-api.service'; -import {IProjectTask} from '@interfaces/api-models/project-tasks-view-model'; -import {ITaskCreateRequest} from '@interfaces/api-models/task-create-request'; -import {ITaskStatusViewModel} from '@interfaces/api-models/task-status-get-response'; -import {ISubTask} from '@interfaces/sub-task'; -import {ITask} from '@interfaces/task'; -import {ITaskStatusCategory} from '@interfaces/task-status-category'; -import {AuthService} from '@services/auth.service'; -import {EventStatusChanged, EventTaskCreatedOrUpdate} from '@shared/events'; -import {SocketEvents} from '@shared/socket-events'; -import {formatGanttDate, log_error} from '@shared/utils'; -import {NzContextMenuService, NzDropdownMenuComponent} from 'ng-zorro-antd/dropdown'; -import {Socket} from 'ngx-socket-io'; -import {merge} from 'rxjs'; -import {TaskListHashMapService} from '../../task-list-v2/task-list-hash-map.service'; -import {TaskListV2Service} from '../../task-list-v2/task-list-v2.service'; -import {Board} from './models/board.model'; -import {ILocalSession} from '@interfaces/api-models/local-session'; -import {DEFAULT_TASK_NAME, DRAWER_ANIMATION_INTERVAL} from "@shared/constants"; -import {KanbanV2Service} from '../kanban-view-v2.service'; -import { - IGroupByOption, - ITaskListConfigV2, - ITaskListGroup, - ITaskListGroupChangeResponse -} from '../../task-list-v2/interfaces'; -import {IServerResponse} from '@interfaces/api-models/server-response'; -import {IKanbanTaskStatus} from '@interfaces/task-status'; -import {IBulkAssignRequest} from '@interfaces/api-models/bulk-assign-request'; -import {TaskViewService} from '@admin/components/task-view/task-view.service'; -import moment from 'moment'; -import {ITaskAssigneesUpdateResponse} from '@interfaces/task-assignee-update-response'; -import {TeamMembersApiService} from '@api/team-members-api.service'; -import {TaskLabelsApiService} from '@api/task-labels-api.service'; -import {TaskPrioritiesService} from '@api/task-priorities.service'; -import {TaskTemplatesService} from '@api/task-templates.service'; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {ITaskListStatusChangeResponse} from "@interfaces/task-list-status-change-response"; -import {ProjectsService} from "../../../projects/projects.service"; - -@Component({ - selector: 'worklenz-kanban-board', - templateUrl: './kanban-board.component.html', - styleUrls: ['./kanban-board.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class KanbanBoardComponent implements OnInit, OnDestroy { - - private session: ILocalSession | null = null; - - @ViewChild('inputValue') inputValue!: ElementRef; - @ViewChild('tasksContainer', {static: false}) tasksContainer!: ElementRef; - @ViewChild('taskCreateCard', {static: false}) taskCreateCard!: ElementRef; - - public board: Board = new Board([]); - taskStatuses: ITaskStatusViewModel[] = []; - loaders: { [x: string]: boolean } = {}; - - projectId: string | null = null; - - tasks: { - [x: number]: { - id?: string; - label?: string; - data?: IProjectTask[]; - } - } = {}; - statusIds: string[] = []; - categories: ITaskStatusCategory[] = []; - - loadingStatuses = false; - showTaskModal = false; - updateLoading = false; - showStatusModal = false; - loadingTasks = false; - loadingSubTasks = false; - taskDragging = false; - isEditColProgress = false; - loadingCategories = false; - assigningTasks = false; - archivingTasks = false; - deletingTasks = false; - showStatusesReplaceModal = false; - deletingStatus = false; - taskUpdated = false; - creatingTask = false; - updatingTask = false; - isCreateButtonClicked = false - loadingGroups = false; - loadingMembers = false; - loadingLabels = false; - loadingPriorities = false; - - selectedTaskId: string | null = null; - selectedStatusId: string | null = null; - statusDeleteWarning: string | null = null; - newTaskName?: string | null = null; - createdTaskId?: string | null = null; - createTaskEndDate: string | null = null; - previousColumnName: string | null = null; - - editingColumn: ITaskListGroup | null = null; - selectedForDelete: ITaskListGroup | null = null; - createdTask: IProjectTask | null = null - - subTasks: ISubTask[] = []; - createdMainTask: IProjectTask | null = null - taskStatuses_: string[] = []; - contextSelectedTask: IProjectTask | null = null; - replacingStatus: string | null = null; - selectedTask: ITask | null = null; - - statusesColumns: { [columnId: string]: boolean } = {}; - createButtons: { [columnId: string]: boolean } = {}; - - tasksContainerMain: HTMLDivElement | null = null; - selectedGroupFilter: IGroupByOption | null = null; - - get defaultStatus() { - return this.taskStatuses[0] || null; - } - - get profile() { - return this.auth.getCurrentSession(); - } - - protected get groups() { - return this.service.groups; - } - - constructor( - private readonly api: TasksApiService, - private readonly auth: AuthService, - private readonly statusesApi: TaskStatusesApiService, - private readonly route: ActivatedRoute, - private readonly contextMenuService: NzContextMenuService, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly socket: Socket, - private readonly map: TaskListHashMapService, - private readonly view: TaskViewService, - private readonly tmApi: TeamMembersApiService, - private readonly labelsApi: TaskLabelsApiService, - private readonly prioritiesApi: TaskPrioritiesService, - private readonly taskTemplates: TaskTemplatesService, - public readonly kanbanService: KanbanV2Service, - public readonly service: TaskListV2Service, - private readonly projectsService: ProjectsService - ) { - this.projectId = this.route.snapshot.paramMap.get('id'); - - this.taskTemplates.onTemplateImport.pipe(takeUntilDestroyed()) - .subscribe(() => { - void this.getGroups(); - }); - - merge(this.view.onSelectTask, this.view.onSelectSubTask) - .pipe(takeUntilDestroyed()) - .subscribe(value => { - if (typeof value === "string") { - const task = this.map.tasks.get(value); - if (task) - this.handleTaskSelectFromView(task); - } else { - this.handleTaskSelectFromView(value); - } - }); - - this.kanbanService.onCreateStatus - .pipe(takeUntilDestroyed()) - .subscribe(value => { - this.onStatusCreate(value); - }); - - this.service.onTaskAddOrDelete$ - .pipe(takeUntilDestroyed()) - .subscribe((value) => { - if (value) - this.handleNewTaskReceive(value); - }); - - this.kanbanService.onCreateSubTask - .pipe(takeUntilDestroyed()) - .subscribe(value => { - this.onSubtaskCreate(value); - }); - - this.kanbanService.onAssignMembers - .pipe(takeUntilDestroyed()) - .subscribe(value => { - this.handleNewTaskAssignees(value) - }); - - this.kanbanService.onDeleteTask - .pipe(takeUntilDestroyed()) - .subscribe(value => { - this.service.deleteTask(value.id as string); - this.cdr.markForCheck(); - }); - } - - ngOnInit(): void { - void this.init(); - this.socket.on(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), this.handleSortOrderResponse); - this.socket.on(SocketEvents.TASK_STATUS_CHANGE.toString(), this.handleStatusChangeResponse); - this.session = this.auth.getCurrentSession(); - this.selectedGroupFilter = this.service.getCurrentGroup(); - this.service.setCurrentGroup(this.service.GROUP_BY_OPTIONS[0]); - } - - private async init() { - void this.getGroups(); - void this.getCategories(); - void this.getStatuses(); - void this.getTeamMembers(); - void this.getLabels(); - void this.getPriorities(); - } - - ngOnDestroy(): void { - this.board.columns = []; - this.tasks = {}; - this.statusIds = []; - this.loaders = {}; - this.socket.removeListener(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), this.handleSortOrderResponse); - if (this.selectedGroupFilter) this.service.setCurrentGroup(this.selectedGroupFilter); - } - - isProjectManager() { - if (this.projectsService.projectOwnerTeamMemberId) return this.auth.getCurrentSession()?.team_member_id === this.projectsService.projectOwnerTeamMemberId; - return false; - } - - private handleNewTaskReceive(value: ITaskListGroupChangeResponse) { - if (value.isSubTask) { - this.cdr.detectChanges(); - } - this.cdr.markForCheck(); - } - - private onSubtaskCreate(task: IProjectTask) { - if (task) { - const group = this.service.groups.find(g => g.id === task.status); - if (group) { - group.tasks.push(task) - } - } - this.cdr.markForCheck(); - } - - private getConf(parentTaskId?: string): ITaskListConfigV2 { - - const config: ITaskListConfigV2 = { - id: this.projectId as string, - statuses: null, - group: this.service.GROUP_BY_STATUS_VALUE, - field: null, - order: null, - search: null, - members: null, - projects: null, - isSubtasksInclude: true - }; - - if (parentTaskId) - config.parent_task = parentTaskId; - - return config; - } - - private async getGroups() { - if (!this.projectId) return; - try { - this.map.deselectAll(); - this.loadingGroups = true; - const config = this.getConf(); - const res = await this.api.getTaskListV2(config) as IServerResponse; - if (res.done) { - this.service.groups = res.body; - this.groupIds = res.body.map(g => g.id); - this.mapTasks(this.service.groups); - } - } catch (e) { - this.loadingGroups = false; - } - - this.cdr.detectChanges(); - } - - private mapTasks(groups: ITaskListGroup[]) { - for (const group of groups) { - this.map.registerGroup(group); - for (const task of group.tasks) { - if (task.start_date) task.start_date = new Date(task.start_date) as any; - if (task.end_date) task.end_date = new Date(task.end_date) as any; - } - } - this.loadingGroups = false; - } - - protected trackById(index: number, item: IProjectTask | ITaskListGroup) { - return item.id; - } - - private onStatusCreate(status: IKanbanTaskStatus) { - const newStatus: ITaskListGroup = { - id: status.id as string, - name: status.name as string, - color_code: status.color_code as string, - category_id: status.category_id as string, - tasks: [], - } - this.service.groups.push(newStatus); - if (status.id) this.groupIds.push(status.id); - // this.getGroups(); - this.cdr.markForCheck(); - } - - @HostListener('document:click', ['$event.target']) - onClick(target: HTMLElement) { - if (this.taskCreateCard && !this.taskCreateCard.nativeElement.contains(target) && (!this.newTaskName || this.newTaskName.trim() === "")) { - this.deleteTask(); - this.resetAll(); - } - } - - @HostListener('window:beforeunload', ['$event']) - handleBeforeUnload(event: BeforeUnloadEvent) { - if (!this.newTaskName) { - this.deleteTask(); - } - } - - private async getTeamMembers() { - try { - this.loadingMembers = true; - const res = await this.tmApi.getAll(this.projectId); - if (res.done) - this.service.members = res.body; - this.loadingMembers = false; - } catch (e) { - this.loadingMembers = false; - } - } - - protected async getLabels() { - try { - this.loadingLabels = true; - const res = await this.labelsApi.get(this.projectId); - if (res.done) - this.service.labels = res.body; - this.loadingLabels = false; - } catch (e) { - this.loadingLabels = false; - } - } - - private async getPriorities() { - try { - this.loadingPriorities = true; - const res = await this.prioritiesApi.get(); - if (res.done) - this.service.priorities = res.body; - this.loadingPriorities = false; - } catch (e) { - this.loadingPriorities = false; - } - } - - private handleStatusChangeResponse = (response: ITaskListStatusChangeResponse) => { - if (response && response.id) { - - const groupId = this.map.getGroupId(response.id); - if (!groupId || !response.id) return; - - const group = this.service.groups.find(g => g.id === groupId); - if (!group) return; - - const task = group.tasks.find(t => t.id === response.id); - if (!task) return; - - task.status = response.status_id; - - if (this.isGroupByStatus()) { - - this.service.updateTaskGroup(task, false); - - if (task.parent_task_id) { - - const groupId = this.service.getGroupIdByGroupedColumn(task); - const group_ = this.groups.find(g => g.id === groupId); - - if (!group_) return; - group_.tasks.push(task); - - } - - } - - this.cdr.markForCheck(); - } - } - - private isGroupByStatus() { - return this.service.getCurrentGroup().value === this.service.GROUP_BY_STATUS_VALUE; - } - - isOwnerOrAdmin() { - return this.profile?.owner || this.profile?.is_admin; - } - - onShowChange(show: boolean) { - if (!show) { - this.selectedTask = null; - } - } - - handleSortOrderResponse = (updatedTasks: IProjectTask[]) => { - for (const element of updatedTasks) { - const taskId = element.id; - if (taskId) { - const task = this.map.tasks.get(taskId); - if (task) { - task.sort_order = element.sort_order; - this.map.tasks.set(taskId, task); - } - } - } - }; - - async editModal(task: IProjectTask, event?: Event) { - if (task && task.id && !this.taskDragging) { - this.selectedTask = task; - this.selectedTaskId = task.id; - this.showTaskModal = true; - } - event?.preventDefault(); - } - - private handleTaskSelectFromView(task: IProjectTask) { - this.showTaskModal = false; - this.cdr.detectChanges(); - - setTimeout(() => { - if (task) { - this.editModal(task); - this.cdr.markForCheck(); - } - }, DRAWER_ANIMATION_INTERVAL); - } - - drop(event: CdkDragDrop) { - - if (this.projectId) { - - const fromIndex = event.previousIndex; - const toIndex = event.currentIndex; - const fromGroup = event.previousContainer.data; - const toGroup = event.container.data; - const task = event.item.data; - const toPos = toGroup.tasks[toIndex]?.sort_order; - - const body = { - project_id: this.projectId, - from_index: fromGroup.tasks[fromIndex].sort_order, - to_index: toPos || toGroup.tasks[toGroup.tasks.length - 1]?.sort_order || -1, - to_last_index: !toPos, - from_group: fromGroup.id, - to_group: toGroup.id, - group_by: this.service.GROUP_BY_STATUS_VALUE, - task, - team_id: this.auth.getCurrentSession()?.team_id - }; - - this.socket.emit(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), body); - - this.socket.once(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), () => { - if (task.is_sub_task) { - this.socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), task.parent_task_id); - } else { - this.socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), task.id); - } - }); - - if (fromGroup.id === toGroup.id) { - moveItemInArray(event.container.data.tasks, fromIndex, toIndex); - } else { - transferArrayItem( - event.previousContainer.data.tasks, - event.container.data.tasks, - event.previousIndex, - event.currentIndex - ); - this.map.remove(task); - this.map.add(toGroup.id, task); - this.service.emitGroupChange(toGroup.id, task.id as string, toGroup.color_code); - } - this.cdr.markForCheck(); - } - } - - async dropGrid(event: any) { - moveItemInArray(this.service.groups, event.previousIndex, event.currentIndex); - - const columnOrder: string[] = []; - for (const element of this.service.groups) - columnOrder.push(element.id); - - const res = await this.statusesApi.updateStatus(event.item.data || '', {status_order: columnOrder}, this.projectId as string); - if (!res.done) { - moveItemInArray(this.service.groups, event.currentIndex, event.previousIndex); - } - } - - protected groupIds: string[] = []; - - async getStatuses() { - if (!this.projectId) return; - try { - this.loadingStatuses = !this.taskUpdated; - - const res = await this.statusesApi.get(this.projectId); - if (res.done) { - this.taskStatuses = res.body; - // await this.getGroupedTasks(); - } - this.loadingStatuses = false; - } catch (e) { - log_error(e); - this.loadingStatuses = false; - } - - this.cdr.markForCheck(); - } - - async getCategories() { - try { - this.loadingCategories = true; - const res = await this.statusesApi.getCategories(); - if (res.done) { - this.categories = res.body; - } - this.loadingCategories = false; - } catch (e) { - log_error(e); - this.loadingCategories = false; - } - - this.cdr.markForCheck(); - } - - @HostListener(`document:${EventTaskCreatedOrUpdate}`) - async onTaskCreateOrUpdate() { - await this.refresh(); - } - - @HostListener(`document:${EventStatusChanged}`) - async onStatusCreateOrUpdate() { - await this.refresh(); - } - - isLoading() { - return this.loadingGroups; - } - - onDragStart() { - this.taskDragging = true; - } - - onDragEnded() { - this.taskDragging = false; - } - - private async refresh() { - if (this.updateLoading) return; - this.kanbanService.resetModel(); - this.updateLoading = true; - this.tasks = {}; - this.statusIds = []; - this.loaders = {}; - await this.getStatuses(); - this.updateLoading = false; - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - document.querySelectorAll(`.bottom-task-add`).forEach(function (elem) { - elem.classList.remove('hide'); - }); - }); - }); - } - - editColumn(column: ITaskListGroup) { - this.editingColumn = column; - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - const input = document.querySelector(`#kanban-col-${column.id}`) as HTMLInputElement; - if (input) { - input.focus(); - input.select(); - this.previousColumnName = column.name; - } - }); - }); - } - - async changeStatusCategory(column: ITaskListGroup, categoryId?: string) { - if (!categoryId) return; - column.old_category_id = column.category_id; - column.category_id = categoryId; - - await this.onBlurEditColumn(column); - - this.cdr.markForCheck(); - } - - async onBlurEditColumn(column: ITaskListGroup) { - if (!this.projectId) return; - if (this.isEditColProgress) return; - if (!column.name || !column.name.trim().length) { - if (this.previousColumnName) column.name = this.previousColumnName - return; - } - try { - this.isEditColProgress = true; - const body = { - name: column.name, - project_id: this.projectId, - category_id: column.category_id - }; - const res = await this.statusesApi.update(column.id, body, this.projectId as string); - if (res.done) { - column.color_code = res.body.color_code as string; - - for (const item of column.tasks) { - this.socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), item.id); - if (item.parent_task_id) { - this.socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), item.parent_task_id); - } - } - - } else { - column.category_id = column.old_category_id; - } - this.editingColumn = null; - this.isEditColProgress = false; - } catch (e) { - column.category_id = column.old_category_id; - log_error(e); - this.isEditColProgress = false; - } - - this.cdr.markForCheck(); - } - - async onNameChange(column: ITaskListGroup) { - if (!this.projectId) return; - if (this.isEditColProgress) return; - if (!column.name || !column.name.trim().length) { - if (this.previousColumnName) column.name = this.previousColumnName - return; - } - - try { - this.isEditColProgress = true; - const body = { - name: column.name, - project_id: this.projectId, - category_id: column.category_id - }; - const res = await this.statusesApi.updateName(column.id, body, this.projectId as string); - if (res.done) { - column.color_code = res.body.color_code as string; - } - this.editingColumn = null; - this.isEditColProgress = false; - } catch (e) { - log_error(e); - this.isEditColProgress = false; - } - - this.cdr.markForCheck(); - } - - async assignToMe() { - if (!this.projectId) return; - try { - this.assigningTasks = true; - const body: IBulkAssignRequest = { - tasks: this.map.getSelectedTaskIds(), - project_id: this.projectId - }; - const res = await this.api.bulkAssignMe(body); - if (res.done) { - this.service.emitOnAssignMe(res.body); - this.map.deselectAll(); - } - this.assigningTasks = false; - } catch (e) { - this.assigningTasks = false; - log_error(e) - } - this.cdr.markForCheck(); - } - - async deleteSelected() { - if (!this.projectId || !this.contextSelectedTask) return; - try { - this.deletingTasks = true; - - // Select each subtask - if (!this.contextSelectedTask.is_sub_task) { - if (this.map._subTasksMap.has(this.contextSelectedTask.id as string)) { - const subtasks = this.map._subTasksMap.get(this.contextSelectedTask.id as string); - if (subtasks) { - for (const subtask of subtasks) { - this.map.selectTask(subtask); - } - } - } - } - - const res = await this.api.bulkDelete({tasks: this.map.getSelectedTaskIds()}, this.projectId as string); - if (res.done) { - for (const task of res.body.deleted_tasks) { - this.service.deleteTask(task); - this.service.removeSubtask(task); - } - - if (this.contextSelectedTask.is_sub_task) { - this.kanbanService.emitDeleteSubTask({ - parent_task_id: this.contextSelectedTask.parent_task_id - }); - this.socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), this.contextSelectedTask.parent_task_id); - } - - } - - this.deletingTasks = false; - } catch (e) { - log_error(e) - this.deletingTasks = false; - } - this.contextSelectedTask = null; - this.cdr.markForCheck(); - } - - async archiveSelected() { - if (this.archivingTasks || !this.contextSelectedTask) return; - try { - // Select each subtask - if (!this.contextSelectedTask.is_sub_task) - if (this.map._subTasksMap.has(this.contextSelectedTask.id as string)) { - const subtasks = this.map._subTasksMap.get(this.contextSelectedTask.id as string); - if (subtasks) { - for (const subtask of subtasks) { - this.map.selectTask(subtask); - } - } - } - this.archivingTasks = true; - const res = await this.api.bulkArchive({ - tasks: this.map.getSelectedTaskIds(), - project_id: this.projectId as string - }, false); - - if (res.done) { - for (const task of res.body) { - this.service.deleteTask(task); - } - } - - this.archivingTasks = false; - } catch (e) { - this.archivingTasks = false; - } - this.contextSelectedTask = null; - this.cdr.markForCheck(); - } - - async deleteStatus(column?: ITaskListGroup): Promise { - if (!column || !column.id || !this.projectId) return false; - try { - this.selectedForDelete = column; - this.deletingStatus = true; - const res = await this.statusesApi.delete(column.id, this.projectId, this.replacingStatus || undefined); - if (res.done) { - const groupIndex = this.groups.findIndex(g => g.id === column.id); - this.groups?.splice(groupIndex, 1); - this.groupIds = this.groupIds.filter(id => id !== column.id); - await this.refresh(); - return true; - } else { - if (res.message?.charAt(0) === "$") { - this.replacingStatus = this.defaultStatus?.id || null; - this.showStatusesReplaceModal = true; - this.statusDeleteWarning = res.message.substring(1); - } - } - this.deletingStatus = false; - this.cdr.markForCheck(); - } catch (e) { - this.deletingStatus = false; - this.cdr.markForCheck(); - } - this.cdr.markForCheck(); - return false; - } - - contextMenu($event: MouseEvent, menu: NzDropdownMenuComponent, task: IProjectTask): void { - this.contextMenuService.create($event, menu); - this.contextSelectedTask = task; - this.map.selectTask(task); - } - - closeStatusesReplaceModal() { - this.showStatusesReplaceModal = false; - this.statusDeleteWarning = null; - this.selectedForDelete = null; - this.replacingStatus = null; - } - - async moveAndDelete() { - const deleted = await this.deleteStatus(this.selectedForDelete as ITaskListGroup); - // if (deleted && this.selectedForDelete?.tasks) { - // for (const _task of this.selectedForDelete.tasks) { - // if (this.replacingStatus) { - // _task.status = this.replacingStatus; - // const group = this.service.groups.find(g => g.id === this.replacingStatus); - // if (group) { - // this.map.addGroupTask(_task.status as string, _task as IProjectTask); - // group.tasks.push(_task); - // void this.init(); - // } - // } - // } - // this.closeStatusesReplaceModal(); - // this.cdr.markForCheck(); - // } - if (deleted) { - void this.init(); - } - this.closeStatusesReplaceModal(); - this.cdr.markForCheck(); - } - - openCreateStatusDrawer() { - this.showStatusModal = true; - } - - trackByFn(index: number, data: any) { - return data.id; - } - - // start: create new task - async showTaskCreateInput(columnId: string, tasksContainer: HTMLDivElement) { - await this.resetAll(); - await this.createTempTask(columnId); - this.tasksContainerMain = tasksContainer; - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - document.querySelectorAll(`.bottom-task-add`).forEach(function (elem) { - elem.classList.remove('hide'); - }); - }); - }); - } - - scrollBottom() { - setTimeout(() => { - if (this.tasksContainerMain) { - const scrollableTaskContainer = this.tasksContainerMain; - const selectedInput = this.inputValue.nativeElement as HTMLInputElement; - scrollableTaskContainer.scrollTop = (this.tasksContainer.nativeElement as HTMLDivElement).scrollHeight + 100; - selectedInput.focus(); - } - },); - this.cdr.detectChanges(); - } - - async onBlur() { - setTimeout(() => { - if (!this.newTaskName) { - this.deleteTask(); - this.resetAll(); - } - }, 150) - } - - async onCreateButtonClicked(columnId: string) { - // await new Promise((resolve) => setTimeout(resolve, 150)); - this.isCreateButtonClicked = true; - if (!this.newTaskName || this.newTaskName == DEFAULT_TASK_NAME || this.newTaskName?.trim() === "") { - this.deleteTask(); - this.resetAll(); - } else { - await this.updateInstantTask(columnId); - } - } - - createTempTask(columnId: string) { - if (this.creatingTask) return; - this.creatingTask = true; - this.updatingTask = true; - this.createButtons[columnId] = true; - - const body: ITaskCreateRequest = { - name: this.newTaskName || DEFAULT_TASK_NAME, - project_id: this.projectId || "", - reporter_id: this.session?.id, - team_id: this.session?.team_id, - status_id: columnId - }; - - this.socket.emit(SocketEvents.QUICK_TASK.toString(), JSON.stringify(body)); - - this.socket.once(SocketEvents.QUICK_TASK.toString(), (task: IProjectTask) => { - this.createdMainTask = task; - this.updatingTask = false; - this.creatingTask = false; - if (task?.id) { - this.getInstantTask(task.id as string, this.projectId as string, columnId, task); - } - }); - } - - private getInstantTask(taskId: string, projectId: string, columnId: string, task: IProjectTask) { - try { - this.createButtons[columnId] = false; - this.statusesColumns[columnId] = true; - this.createdTaskId = taskId; - this.kanbanService.model.task = task; - this.scrollBottom(); - } catch (e) { - log_error(e); - } - } - - private handleNewTaskAssignees = (response: ITaskAssigneesUpdateResponse) => { - if (!this.createdMainTask) return; - try { - if (response) { - this.createdMainTask.assignees = (response.assignees || []).map(m => m.team_member_id); - this.createdMainTask.names = response.names || []; - this.cdr.markForCheck(); - } - } catch (e) { - // ignore - } - } - - async updateInstantTask(columnId: string) { - - if (!this.createdTaskId || !this.newTaskName || this.newTaskName?.trim() === "" || this.newTaskName == DEFAULT_TASK_NAME || !this.isCreateButtonClicked) { - void this.deleteTask(); - this.resetAll(); - return; - } - - try { - this.updatingTask = true; - this.socket.emit(SocketEvents.TASK_NAME_CHANGE.toString(), JSON.stringify({ - task_id: this.createdTaskId, - name: this.newTaskName, - parent_task: '', - })); - if (this.createdMainTask) this.createdMainTask.name = this.newTaskName; - this.socket.emit(SocketEvents.TASK_END_DATE_CHANGE.toString(), JSON.stringify({ - task_id: this.createdTaskId, - end_date: formatGanttDate(this.createTaskEndDate) || null, - parent_task: '', - time_zone: this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone - })); - if (this.createdMainTask && this.createTaskEndDate) this.createdMainTask.end_date = this.createTaskEndDate; - this.updatingTask = false; - this.onNewTaskReceived(columnId) - } catch (e) { - log_error(e) - } - - this.newTaskName = null; - this.createdTaskId = null; - this.createTaskEndDate = null; - this.creatingTask = false; - this.updatingTask = false; - this.isCreateButtonClicked = false; - this.kanbanService.resetModel(); - this.createdMainTask = null; - // this.statusesColumns[columnId] = false; - this.createTempTask(columnId); - this.cdr.markForCheck(); - } - - onNewTaskReceived(groupId: string) { - if (groupId && this.createdMainTask?.id) { - if (this.map.has(this.createdMainTask?.id)) return; - this.service.addTask(this.createdMainTask, groupId); - } - } - - async deleteTask() { - const task = this.kanbanService.model.task; - if (!task || !task.id) return; - try { - const res = await this.api.delete(task.id); - if (res.done) { - const count = this.kanbanService.model.task?.sub_tasks_count || 0; - if (this.kanbanService.model.task) - this.kanbanService.model.task.sub_tasks_count = Math.max(count - 1, 0); - this.kanbanService.emitDelete({ - id: task.id, - parent_task_id: task.parent_task_id, - project_id: this.projectId as string - }); - this.kanbanService.emitRefresh(task.id); - } - } catch (e) { - log_error(e); - } - } - - checkForPastDate(endDate: any) { - const formattedEndDate = moment(endDate).format('YYYY-MM-DD'); - return formattedEndDate < moment().format('YYYY-MM-DD'); - } - - checkForSoonDate(endDate: any) { - const formattedEndDate = moment(endDate).format('YYYY-MM-DD'); - const tomorrow = moment().add(1, 'day').format('YYYY-MM-DD'); - return formattedEndDate === moment().format('YYYY-MM-DD') || formattedEndDate === tomorrow; - } - - resetAll() { - for (const singleCard in this.statusesColumns) { - this.statusesColumns[singleCard] = false; - } - this.tasksContainerMain = null; - this.newTaskName = null; - this.createdTaskId = null; - this.createTaskEndDate = null; - this.creatingTask = false; - this.updatingTask = false; - this.isCreateButtonClicked = false; - this.createdMainTask = null - this.kanbanService.resetModel(); - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - document.querySelectorAll(`.bottom-task-add`).forEach(function (elem) { - elem.classList.remove('hide'); - }); - }); - }); - } - -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/models/board.model.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/models/board.model.ts deleted file mode 100644 index 4ccebc81..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/models/board.model.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {ITaskListGroup} from 'app/administrator/modules/task-list-v2/interfaces'; - -export class Board { - constructor(public columns: ITaskListGroup[]) { - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/models/column.model.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/models/column.model.ts deleted file mode 100644 index afc9134d..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/models/column.model.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {IProjectTask} from '@interfaces/api-models/project-tasks-view-model'; - -export class Column { - constructor( - public name: string, - public id: string, - public data: IProjectTask[], - public category_id?: string, - public category_name?: string, - public color_code?: string, - public description?: string - ) { - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/pipes/validate-min-date.pipe.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/pipes/validate-min-date.pipe.ts deleted file mode 100644 index 2995b283..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-board/pipes/validate-min-date.pipe.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; -import {UtilsService} from "@services/utils.service"; - -@Pipe({ - name: 'validateMinDate' -}) - -export class ValidateMinDatePipe implements PipeTransform { - constructor( - private readonly utils: UtilsService - ) { - } - - transform(value?: string, ...args: unknown[]) { - return this.utils.checkForMinDate(value); - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-hash-map-service.service.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-hash-map-service.service.ts deleted file mode 100644 index 14941a53..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-hash-map-service.service.ts +++ /dev/null @@ -1,69 +0,0 @@ -import {Injectable} from '@angular/core'; -import {IProjectTask} from '@interfaces/api-models/project-tasks-view-model'; -import {Column} from './kanban-board/models/column.model'; -import {Subject} from 'rxjs'; - -@Injectable({ - providedIn: 'root' -}) -export class KanbanHashMapService { - private readonly _selectSbj$ = new Subject(); - private readonly _deselectSbj$ = new Subject(); - private readonly _deselectAllSbj$ = new Subject(); - - /** Map */ - private readonly _groupTaskMap = new Map(); - /** Map */ - private readonly _taskGroupIdsMap = new Map(); - /** Map */ - private readonly _selectedTasksMap = new Map(); - /** Map */ - private readonly _allTasksMap = new Map(); - - private _selectedCount = 0; - - public get tasks() { - return this._allTasksMap; - } - - public get onSelect$() { - return this._selectSbj$.asObservable(); - } - - public get onDeselect$() { - return this._deselectSbj$.asObservable(); - } - - public get onDeselectAll$() { - return this._deselectAllSbj$.asObservable(); - } - - public registerGroup(group: Column) { - for (const task of group.data) { - this.add(group.id, task); - } - } - - public add(groupId: string, task: IProjectTask) { - if (!task.id) return; - this.updateGroupTaskMap(groupId, task.id); - this._taskGroupIdsMap.set(task.id, groupId); - this._allTasksMap.set(task.id, task); - } - - private updateGroupTaskMap(groupId: string, taskId: string, selected?: boolean) { - const map = this._groupTaskMap.get(groupId); - if (map) { - if (typeof selected === "boolean") { - map[taskId] = selected; - } else { - delete map[taskId]; - } - - this._groupTaskMap.set(groupId, map); - } else { - this._groupTaskMap.set(groupId, {[taskId]: selected || false}); - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-view-v2-routing.module.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-view-v2-routing.module.ts deleted file mode 100644 index bc3e19d8..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-view-v2-routing.module.ts +++ /dev/null @@ -1,15 +0,0 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {KanbanBoardComponent} from './kanban-board/kanban-board.component'; - -const routes: Routes = [ - {path: "", component: KanbanBoardComponent} -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class KanbanViewV2RoutingModule { - -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-view-v2.module.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-view-v2.module.ts deleted file mode 100644 index 96ba5391..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-view-v2.module.ts +++ /dev/null @@ -1,121 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {KanbanBoardComponent} from './kanban-board/kanban-board.component'; -import {TaskCardComponent} from './kanban-board/components/task-card/task-card.component'; -import {TaskEndDateComponent} from './kanban-board/components/task-end-date/task-end-date.component'; -import {TaskLabelsComponent} from './kanban-board/components/task-labels/task-labels.component'; -import {TaskNameComponent} from './kanban-board/components/task-name/task-name.component'; -import {TaskPriorityComponent} from './kanban-board/components/task-priority/task-priority.component'; -import {TaskProgressComponent} from './kanban-board/components/task-progress/task-progress.component'; -import {TaskSubtaskCountComponent} from './kanban-board/components/task-subtask-count/task-subtask-count.component'; -import {KanbanViewV2RoutingModule} from './kanban-view-v2-routing.module'; -import {NzAvatarModule} from "ng-zorro-antd/avatar"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzCheckboxModule} from "ng-zorro-antd/checkbox"; -import {NzDatePickerModule} from "ng-zorro-antd/date-picker"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {NzEmptyModule} from "ng-zorro-antd/empty"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NzListModule} from "ng-zorro-antd/list"; -import {NzPopconfirmModule} from "ng-zorro-antd/popconfirm"; -import {NzPopoverModule} from "ng-zorro-antd/popover"; -import {NzProgressModule} from "ng-zorro-antd/progress"; -import {NzSelectModule} from "ng-zorro-antd/select"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzSpinModule} from "ng-zorro-antd/spin"; -import {NzTableModule} from "ng-zorro-antd/table"; -import {NzTagModule} from "ng-zorro-antd/tag"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzModalModule} from 'ng-zorro-antd/modal'; -import {DragDropModule} from '@angular/cdk/drag-drop'; -import {TaskViewModule} from "@admin/components/task-view/task-view.module"; -import {StatusFormComponent} from '@admin/components/status-form/status-form.component'; -import {FromNowPipe} from "@pipes/from-now.pipe"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {NzPipesModule} from "ng-zorro-antd/pipes"; -import {FirstCharUpperPipe} from "@pipes/first-char-upper.pipe"; -import {WlSafeArrayPipe} from "@pipes/wl-safe-array.pipe"; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; -import {FormsModule, ReactiveFormsModule} from '@angular/forms'; -import {NzDrawerModule} from 'ng-zorro-antd/drawer'; -import {NzAlertModule} from 'ng-zorro-antd/alert'; -import {NzCardModule} from 'ng-zorro-antd/card'; -import {TaskMembersComponent} from './kanban-board/components/task-members/task-members.component'; -import {AvatarsComponent} from "../../components/avatars/avatars.component"; -import {ValidateMinDatePipe} from './kanban-board/pipes/validate-min-date.pipe'; -import {DateFormatterPipe} from '@pipes/date-formatter.pipe'; -import { - TaskCreationAssigneesComponent -} from './kanban-board/components/task-creation-assignees/task-creation-assignees.component'; -import {RxFor} from '@rx-angular/template/for'; - -@NgModule({ - declarations: [ - KanbanBoardComponent, - TaskCardComponent, - TaskEndDateComponent, - TaskLabelsComponent, - TaskNameComponent, - TaskPriorityComponent, - TaskProgressComponent, - TaskSubtaskCountComponent, - TaskMembersComponent, - ValidateMinDatePipe, - TaskCreationAssigneesComponent - ], - exports: [ - KanbanBoardComponent, - ValidateMinDatePipe - ], - imports: [ - CommonModule, - KanbanViewV2RoutingModule, - NzAvatarModule, - NzBadgeModule, - NzButtonModule, - NzCheckboxModule, - NzDatePickerModule, - NzDropDownModule, - NzEmptyModule, - NzFormModule, - NzIconModule, - NzInputModule, - NzListModule, - NzPopconfirmModule, - NzPopoverModule, - NzProgressModule, - NzSelectModule, - NzSkeletonModule, - NzSpaceModule, - NzSpinModule, - NzTableModule, - NzTagModule, - NzToolTipModule, - NzTypographyModule, - NzModalModule, - DragDropModule, - TaskViewModule, - StatusFormComponent, - FromNowPipe, - SearchByNamePipe, - NzPipesModule, - FirstCharUpperPipe, - WlSafeArrayPipe, - SafeStringPipe, - FormsModule, - ReactiveFormsModule, - NzDrawerModule, - NzAlertModule, - NzCardModule, - AvatarsComponent, - DateFormatterPipe, - RxFor, - ] -}) -export class KanbanViewV2Module { -} diff --git a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-view-v2.service.ts b/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-view-v2.service.ts deleted file mode 100644 index 0964556e..00000000 --- a/worklenz-frontend/src/app/administrator/modules/kanban-view-v2/kanban-view-v2.service.ts +++ /dev/null @@ -1,100 +0,0 @@ -import {ITaskViewTaskIds} from '@admin/components/task-view/interfaces'; -import {Injectable} from '@angular/core'; -import {IProjectTask} from '@interfaces/api-models/project-tasks-view-model'; -import {ITaskFormViewModel} from '@interfaces/task-form-view-model'; -import {ReplaySubject, Subject} from 'rxjs'; -import {ITaskListGroupChangeResponse} from '../task-list-v2/interfaces'; -import {Socket} from 'ngx-socket-io'; -import {Column} from './kanban-board/models/column.model'; -import {ITaskStatus} from '@interfaces/task-status'; -import {ITaskAssigneesUpdateResponse} from '@interfaces/task-assignee-update-response'; - -@Injectable({ - providedIn: 'root' -}) -export class KanbanV2Service { - private readonly _selectTaskSbj$ = new Subject(); - private readonly _selectSubTaskSbj$ = new Subject(); - private readonly _refreshSbj$ = new Subject(); - private readonly _deleteSbj$ = new Subject(); - private readonly _subTasksRefreshSbj$ = new ReplaySubject(); - private readonly _commentsChangeSbj$ = new Subject<{ task: string; count: number; }>(); - private readonly _attachmentsChangeSbj$ = new Subject<{ task: string; count: number; }>(); - private readonly taskAddOrDeleteSbj$ = new Subject(); - private readonly onCreateStatus$ = new Subject(); - private readonly onCreateSubTask$ = new Subject(); - private readonly onDeleteTask$ = new Subject(); - private readonly onDeleteSubTaskSbj$ = new Subject(); - private readonly onAssignMembersSbj$ = new Subject(); - private readonly refreshGroupsSbj$ = new Subject(); - - public groups: Column[] | ITaskStatus[] = []; - - constructor( - private readonly socket: Socket, - ) { - } - - private _model: ITaskFormViewModel = {}; - - get model(): ITaskFormViewModel { - return this._model; - } - - get onCreateStatus() { - return this.onCreateStatus$.asObservable(); - } - - get onCreateSubTask() { - return this.onCreateSubTask$.asObservable(); - } - - get onDeleteSubTask() { - return this.onDeleteSubTaskSbj$.asObservable(); - } - - get onDeleteTask() { - return this.onDeleteTask$.asObservable(); - } - - get onAssignMembers() { - return this.onAssignMembersSbj$.asObservable(); - } - - public emitOnCreateStatus(data: ITaskStatus) { - this.onCreateStatus$.next(data); - } - - public emitOnCreateSubTask(data: IProjectTask) { - this.onCreateSubTask$.next(data); - } - - public emitDeleteTask(data: IProjectTask) { - this.onDeleteTask$.next(data); - } - - public emitDeleteSubTask(data: IProjectTask) { - this.onDeleteSubTaskSbj$.next(data); - } - - public emitRefresh(taskId: string) { - this._refreshSbj$.next(taskId); - } - - public emitDelete({id, parent_task_id, project_id}: { id: string, parent_task_id?: string, project_id: string }) { - this._deleteSbj$.next({id, parent_task_id, project_id}); - } - - public emitOnAssignMembers(data: ITaskAssigneesUpdateResponse) { - this.onAssignMembersSbj$.next(data); - } - - public emitRefreshGroups() { - this.refreshGroupsSbj$.next(); - } - - public resetModel() { - this._model = {}; - } - -} diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/gantt-chart-v2-routing.module.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/gantt-chart-v2-routing.module.ts deleted file mode 100644 index 4b5dbef9..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/gantt-chart-v2-routing.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; - -const routes: Routes = []; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class GanttChartV2RoutingModule { -} diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/gantt-chart-v2.module.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/gantt-chart-v2.module.ts deleted file mode 100644 index 304dcdc5..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/gantt-chart-v2.module.ts +++ /dev/null @@ -1,85 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; - -import {GanttChartV2RoutingModule} from './gantt-chart-v2-routing.module'; -import {NzCheckboxModule} from "ng-zorro-antd/checkbox"; -import {NzGridModule} from "ng-zorro-antd/grid"; -import {NzTagModule} from "ng-zorro-antd/tag"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {TaskViewModule} from "@admin/components/task-view/task-view.module"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzAvatarModule} from "ng-zorro-antd/avatar"; -import {FirstCharUpperPipe} from "@pipes/first-char-upper.pipe"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {RxFor} from "@rx-angular/template/for"; -import {ProjectRoadmapV2CustomComponent} from "./project-roadmap-v2-custom/project-roadmap-v2-custom.component"; -import {RMTaskNameComponent} from './project-roadmap-v2-custom/components/task-name/task-name.component'; -import {RMStartDateComponent} from './project-roadmap-v2-custom/components/start-date/start-date.component'; -import {RMEndDateComponent} from './project-roadmap-v2-custom/components/end-date/end-date.component'; -import {DragAndMoveDirective} from './project-roadmap-v2-custom/directives/drag-move.directive'; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NzWaveModule} from "ng-zorro-antd/core/wave"; -import {FormsModule, ReactiveFormsModule} from "@angular/forms"; -import {DateFormatterPipe} from "@pipes/date-formatter.pipe"; -import {NzDatePickerModule} from "ng-zorro-antd/date-picker"; -import {TaskListV2Module} from "../task-list-v2/task-list-v2.module"; -import {TaskBarComponent} from './project-roadmap-v2-custom/components/task-bar/task-bar.component'; -import {AddTaskInputComponent} from './project-roadmap-v2-custom/components/add-task-input/add-task-input.component'; -import {FiltersComponent} from './project-roadmap-v2-custom/components/filters/filters.component'; -import {EllipsisPipe} from "@pipes/ellipsis.pipe"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {NzMenuModule} from "ng-zorro-antd/menu"; -import {CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport} from "@angular/cdk/scrolling"; -import { AddTaskRowComponent } from './project-roadmap-v2-custom/components/add-task-row/add-task-row.component'; -import {ScrollingModule} from '@angular/cdk/scrolling'; - -@NgModule({ - declarations: [ - ProjectRoadmapV2CustomComponent, - RMTaskNameComponent, - RMStartDateComponent, - RMEndDateComponent, - DragAndMoveDirective, - TaskBarComponent, - AddTaskInputComponent, - FiltersComponent, - AddTaskRowComponent - ], - imports: [ - CommonModule, - GanttChartV2RoutingModule, - NzCheckboxModule, - NzGridModule, - NzTagModule, - NzIconModule, - TaskViewModule, - NzTypographyModule, - NzAvatarModule, - FirstCharUpperPipe, - NzToolTipModule, - NzSkeletonModule, - RxFor, - NzButtonModule, - NzInputModule, - NzWaveModule, - ReactiveFormsModule, - FormsModule, - DateFormatterPipe, - NzDatePickerModule, - TaskListV2Module, - EllipsisPipe, - NzDropDownModule, - NzMenuModule, - CdkVirtualScrollViewport, - CdkVirtualForOf, - CdkFixedSizeVirtualScroll, - ScrollingModule - ], - exports: [ - ProjectRoadmapV2CustomComponent - ], -}) -export class GanttChartV2Module { -} diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-input/add-task-input.component.html b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-input/add-task-input.component.html deleted file mode 100644 index f9332b79..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-input/add-task-input.component.html +++ /dev/null @@ -1,21 +0,0 @@ -
-
- -
-
- - {{label}} -
-
diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-input/add-task-input.component.scss b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-input/add-task-input.component.scss deleted file mode 100644 index 0723b13e..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-input/add-task-input.component.scss +++ /dev/null @@ -1,25 +0,0 @@ -.input-icon { - font-size: 11px; -} - -input { - background: white; - border: 1px solid #40a9ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); -} - -.task-name { - border: 1px solid transparent; - - &:hover { - cursor: text; - background: #fff; - border: 1px solid #bfbfbf; - border-radius: 4px; - } -} - -.name-input-container { - max-width: 300px; - min-width: 300px; -} diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-input/add-task-input.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-input/add-task-input.component.spec.ts deleted file mode 100644 index 3cc41562..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-input/add-task-input.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { AddTaskInputComponent } from './add-task-input.component'; - -describe('AddTaskInputComponent', () => { - let component: AddTaskInputComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [AddTaskInputComponent] - }); - fixture = TestBed.createComponent(AddTaskInputComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-input/add-task-input.component.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-input/add-task-input.component.ts deleted file mode 100644 index b47f6d8b..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-input/add-task-input.component.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { - ChangeDetectionStrategy, ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - Input, NgZone, - OnDestroy, - Output, - ViewChild -} from '@angular/core'; -import {FormBuilder, FormGroup, Validators} from "@angular/forms"; -import {smallId} from "@shared/utils"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {Socket} from "ngx-socket-io"; -import {AuthService} from "@services/auth.service"; -import {RoadmapV2Service} from "../../services/roadmap-v2-service.service"; -import {RoadmapV2HashmapService} from "../../services/roadmap-v2-hashmap.service"; -import {ITaskCreateRequest} from "@interfaces/api-models/task-create-request"; -import {SocketEvents} from "@shared/socket-events"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {DRAWER_ANIMATION_INTERVAL} from "@shared/constants"; - -@Component({ - selector: 'worklenz-rm-add-task-input', - templateUrl: './add-task-input.component.html', - styleUrls: ['./add-task-input.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class AddTaskInputComponent { - @ViewChild('taskInput', {static: false}) taskInput!: ElementRef; - readonly form!: FormGroup; - - @Input() subTaskInput = false; - @Input() projectId: string | null = null; - @Input() parentTask: string | null = null; - @Input() groupId: string | null = null; - @Input({required: true}) chartStart: string | null = null; - @Input() label = "Add Task"; - - @Output() focusChange: EventEmitter = new EventEmitter(); - - taskInputVisible = false; - creating = false; - - protected readonly id = smallId(4); - - private readonly _session: ILocalSession | null = null; - constructor( - private readonly socket: Socket, - private readonly auth: AuthService, - private readonly fb: FormBuilder, - private readonly ngZone: NgZone, - private readonly cdr: ChangeDetectorRef, - private readonly service: RoadmapV2Service, - private readonly map: RoadmapV2HashmapService - ) { - this.form = this.fb.group({ - name: [null, [Validators.required, Validators.pattern(/^(\s+\S+\s*)*(?!\s).*$/)]] - }); - this._session = this.auth.getCurrentSession(); - } - - focusTaskInput() { - this.taskInputVisible = true; - this.focusChange.emit(this.taskInputVisible); - - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.taskInput?.nativeElement.focus(); - this.taskInput?.nativeElement.select(); - }, 100); // wait for the animation end - }); - } - - addTaskInputBlur() { - this.taskInputVisible = false; - this.focusChange.emit(this.taskInputVisible); - } - - async onInputBlur() { - if (this.isValidInput()) { - await this.addInstantTask(); - return; - } - this.addTaskInputBlur(); - } - - private createRequest() { - if (!this.projectId || !this._session) return null; - const sess = this._session; - const body: ITaskCreateRequest = { - name: this.form.value.name, - project_id: this.projectId, - reporter_id: sess.id, - team_id: sess.team_id, - chart_start: this.chartStart ? this.chartStart : '' - }; - const groupBy = this.service.getCurrentGroup(); - if (groupBy.value === this.service.GROUP_BY_STATUS_VALUE) { - body.status_id = this.groupId || undefined; - } else if (groupBy.value === this.service.GROUP_BY_PRIORITY_VALUE) { - body.priority_id = this.groupId || undefined; - } else if (groupBy.value === this.service.GROUP_BY_PHASE_VALUE) { - body.phase_id = this.groupId || undefined; - } - if (this.parentTask) { - body.parent_task_id = this.parentTask; - } - return body; - } - - private isValidInput() { - return this.form.valid && this.form.value.name.trim().length; - } - - async addInstantTask() { - if (this.creating) return; - if (!this.projectId || !this._session) return; - if (this.isValidInput()) { - try { - const req = this.createRequest(); - if (!req) return; - this.creating = true; - this.socket.emit(SocketEvents.QUICK_TASK.toString(), JSON.stringify(req)); - this.socket.once(SocketEvents.QUICK_TASK.toString(), (task: IProjectTask) => { - this.creating = false; - this.onNewTaskReceived(task); - if (task.parent_task_id) { - this.service.emitUpdateGroupProgress(task.id as string); - } - }); - } catch (e) { - this.creating = false; - } - this.cdr.markForCheck(); - } - } - - public reset(scroll = true) { - this.creating = false; - this.form.controls["name"].setValue(null); - this.taskInputVisible = true; - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.taskInput?.nativeElement.focus(); - if (scroll) - window.scrollTo(0, document.body.scrollHeight); - }, DRAWER_ANIMATION_INTERVAL); // wait for the animation end - }); - this.cdr.markForCheck(); - } - - private onNewTaskReceived(task: IProjectTask) { - if (this.groupId && task.id) { - if (this.map.has(task.id)) return; - this.service.addTask(task, this.groupId); - this.reset(false); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-row/add-task-row.component.html b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-row/add-task-row.component.html deleted file mode 100644 index a80ebc9f..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-row/add-task-row.component.html +++ /dev/null @@ -1,3 +0,0 @@ -
-
-
diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-row/add-task-row.component.scss b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-row/add-task-row.component.scss deleted file mode 100644 index 66a04686..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-row/add-task-row.component.scss +++ /dev/null @@ -1,14 +0,0 @@ -.indicator { - height: 30px; - background: rgb(217, 227, 238); - border: 1px solid rgb(173, 181, 189); - border-radius: 4px; - position: absolute; - z-index: 9; - transition: 0.15s all; -} - -.h-default { - min-height: 42px; - max-height: 42px; -} diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-row/add-task-row.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-row/add-task-row.component.spec.ts deleted file mode 100644 index 59cf8a23..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-row/add-task-row.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { AddTaskRowComponent } from './add-task-row.component'; - -describe('AddTaskRowComponent', () => { - let component: AddTaskRowComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [AddTaskRowComponent] - }); - fixture = TestBed.createComponent(AddTaskRowComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-row/add-task-row.component.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-row/add-task-row.component.ts deleted file mode 100644 index 9f779301..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/add-task-row/add-task-row.component.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - Input, - Output, - ViewChild -} from '@angular/core'; -import {Socket} from "ngx-socket-io"; -import {AuthService} from "@services/auth.service"; -import {RoadmapV2Service} from "../../services/roadmap-v2-service.service"; -import {ITaskCreateRequest} from "@interfaces/api-models/task-create-request"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {DEFAULT_TASK_NAME, UNMAPPED} from "@shared/constants"; -import {log_error} from "@shared/utils"; -import {SocketEvents} from "@shared/socket-events"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; - -@Component({ - selector: 'worklenz-add-task-row', - templateUrl: './add-task-row.component.html', - styleUrls: ['./add-task-row.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class AddTaskRowComponent { - @ViewChild('taskInput', {static: false}) taskInput!: ElementRef; - @Input() projectId: string | null = null; - @Input() parentTask: string | null = null; - @Input() groupId: string | null = null; - @Input({required: true}) chartStart: string | null = null; - @Output() openTask = new EventEmitter(); - - left: number = 0; - width: number = 0; - - private readonly _session: ILocalSession | null = null; - - constructor( - private readonly socket: Socket, - private readonly auth: AuthService, - private readonly cdr: ChangeDetectorRef, - private readonly service: RoadmapV2Service, - ) { - this._session = this.auth.getCurrentSession(); - } - - onDragStart(event: MouseEvent) { - this.left = event.offsetX - (event.offsetX % 35); - const pageX = event.pageX; - const onMouseMove = (e: MouseEvent) => { - const deltaX = e.pageX - pageX; - requestAnimationFrame(() => { - this.width = deltaX; - this.service.highlighterWidth = this.width; - this.service.highlighterLeft = this.left; - this.cdr.markForCheck(); - }); - }; - const onMouseUp = (event: MouseEvent) => { - document.removeEventListener('mousemove', onMouseMove); - document.removeEventListener('mouseup', onMouseUp); - if (pageX > event.pageX) { - this.width = 0; - this.left = 0; - this.cdr.markForCheck(); - return; - } - requestAnimationFrame(() => { - this.width = this.width + (35 - (this.width % 35)); - this.service.highlighterWidth = this.width; - this.service.highlighterLeft = this.left; - this.cdr.markForCheck(); - this.createTask(); - }) - }; - document.addEventListener('mousemove', onMouseMove); - document.addEventListener('mouseup', onMouseUp); - } - - createTask() { - if (!this.projectId || !this._session) return; - const body = this.createRequest(); - try { - this.socket.emit(SocketEvents.QUICK_TASK.toString(), JSON.stringify(body)); - this.socket.once(SocketEvents.QUICK_TASK.toString(), (task: IProjectTask) => { - this.onNewTaskReceived(task); - if (task.parent_task_id) { - this.service.emitUpdateGroupProgress(task.id as string); - } - }); - } catch (e) { - log_error(e) - } - } - - private createRequest() { - if (!this.projectId || !this._session) return null; - const sess = this._session; - const body: ITaskCreateRequest = { - name: DEFAULT_TASK_NAME, - project_id: this.projectId, - reporter_id: sess.id, - team_id: sess.team_id, - chart_start: this.chartStart ? this.chartStart : '', - width: this.width, - offset: this.left, - is_dragged: true - }; - const groupBy = this.service.getCurrentGroup(); - if (groupBy.value === this.service.GROUP_BY_STATUS_VALUE) { - body.status_id = this.groupId || undefined; - } else if (groupBy.value === this.service.GROUP_BY_PRIORITY_VALUE) { - body.priority_id = this.groupId || undefined; - } else if (groupBy.value === this.service.GROUP_BY_PHASE_VALUE) { - body.phase_id = this.groupId || undefined; - } - if (this.parentTask) { - body.parent_task_id = this.parentTask; - } - return body; - } - - onNewTaskReceived(task: IProjectTask) { - task.width = this.width; - task.offset_from = this.left; - let groupId = this.service.getGroupIdByGroupedColumn(task); - if (this.isGroupByPhase()) { - if (!groupId) { - groupId = UNMAPPED - } - } - if (groupId) this.service.addTask(task, groupId); - this.openTask.emit(task); - this.cdr.markForCheck(); - this.reset(); - } - - private isGroupByPhase() { - return this.service.getCurrentGroup().value === this.service.GROUP_BY_PHASE_VALUE; - } - - reset() { - this.left = 0; - this.width = 0; - } - -} diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/end-date/end-date.component.html b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/end-date/end-date.component.html deleted file mode 100644 index c18efe6f..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/end-date/end-date.component.html +++ /dev/null @@ -1,16 +0,0 @@ -
- {{task.end_date | dateFormatter}} - - - -
diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/end-date/end-date.component.scss b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/end-date/end-date.component.scss deleted file mode 100644 index 253810e7..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/end-date/end-date.component.scss +++ /dev/null @@ -1,41 +0,0 @@ -nz-date-picker { - max-width: 150px; - border-color: transparent !important; - top: 0; - bottom: 0; - left: 0; - right: 0; - - &:hover { - border-color: #d9d9d9 !important; - } -} - -.editable { - position: relative; -} - -.date-text { - display: flex; - align-items: center; - padding-left: 8px; -} - -.past-date { - color: #ff4d4f; -} - -.soon-date { - color: #52c41a; -} - -.def-g-height { - max-height: 32px; - min-height: 32px; - line-height: 32px; -} - -.h-default { - min-height: 42px; - max-height: 42px; -} diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/end-date/end-date.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/end-date/end-date.component.spec.ts deleted file mode 100644 index a7e2121c..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/end-date/end-date.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { EndDateComponent } from './end-date.component'; - -describe('EndDateComponent', () => { - let component: EndDateComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [EndDateComponent] - }); - fixture = TestBed.createComponent(EndDateComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/end-date/end-date.component.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/end-date/end-date.component.ts deleted file mode 100644 index ef4ca798..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/end-date/end-date.component.ts +++ /dev/null @@ -1,84 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import moment from "moment"; -import {ITaskListGroup} from "../../../../task-list-v2/interfaces"; -import {formatGanttDate} from "@shared/utils"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-rm-end-date', - templateUrl: './end-date.component.html', - styleUrls: ['./end-date.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RMEndDateComponent implements OnInit, OnDestroy { - @Input({required: true}) task: IProjectTask | null = null; - @Input({required: true}) group: ITaskListGroup | null = null; - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly auth: AuthService, - ) { - } - - ngOnInit() { - this.socket.on(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.handleResponse); - this.socket.on(SocketEvents.GANNT_DRAG_CHANGE.toString(), this.handleDragChangeResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.handleResponse); - this.socket.removeListener(SocketEvents.GANNT_DRAG_CHANGE.toString(), this.handleDragChangeResponse); - } - - private handleResponse = (response: { - id: string; - parent_task: string | null; - end_date: string; - }) => { - if (!this.task) return; - if (response.id === this.task.id && this.task.end_date !== response.end_date) { - this.task.end_date = response.end_date; - this.cdr.markForCheck(); - } - }; - - handleEndDateChange(date: string, task: IProjectTask) { - this.socket.emit( - SocketEvents.TASK_END_DATE_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - end_date: formatGanttDate(date) || null, - parent_task: task.parent_task_id ? task.parent_task_id : null, - group_id: this.group?.id, - time_zone: this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone - })); - } - - checkForPastDate(endDate: any) { - const formattedEndDate = moment(endDate).format('YYYY-MM-DD'); - return formattedEndDate < moment().format('YYYY-MM-DD'); - } - - checkForSoonDate(endDate: any) { - const formattedEndDate = moment(endDate).format('YYYY-MM-DD'); - const tomorrow = moment().add(1, 'day').format('YYYY-MM-DD'); - return formattedEndDate === moment().format('YYYY-MM-DD') || formattedEndDate === tomorrow; - } - - private handleDragChangeResponse = (response: { - task_id: string; - task_width: number; - task_offset: number; - start_date: string; - end_date: string; - }) => { - if (!this.task) return; - if (this.task.id === response.task_id) { - this.task.end_date = response.end_date; - } - this.cdr.detectChanges(); - }; -} diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/filters/filters.component.html b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/filters/filters.component.html deleted file mode 100644 index ffb4796f..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/filters/filters.component.html +++ /dev/null @@ -1,20 +0,0 @@ -
- - -
- - -
    -
  • - {{item.label}} -
  • -
-
diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/filters/filters.component.scss b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/filters/filters.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/filters/filters.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/filters/filters.component.spec.ts deleted file mode 100644 index c8bc5595..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/filters/filters.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { FiltersComponent } from './filters.component'; - -describe('FiltersComponent', () => { - let component: FiltersComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [FiltersComponent] - }); - fixture = TestBed.createComponent(FiltersComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/filters/filters.component.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/filters/filters.component.ts deleted file mode 100644 index 01b02f11..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/filters/filters.component.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - NgZone, - Output -} from '@angular/core'; -import {IGroupByOption} from "../../../../task-list-v2/interfaces"; -import {Socket} from "ngx-socket-io"; -import {RoadmapV2Service} from "../../services/roadmap-v2-service.service"; - -@Component({ - selector: 'worklenz-filters', - templateUrl: './filters.component.html', - styleUrls: ['./filters.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class FiltersComponent { - @Input() projectId!: string; - - @Output() onGroupBy = new EventEmitter(); - - get selectedGroup() { - return this.service.getCurrentGroup(); - } - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - public readonly service: RoadmapV2Service, - ) { - } - - changeGroup(item: IGroupByOption) { - this.service.setCurrentGroup(item); - this.onGroupBy.emit(item); - } - - - isGroupByPhase() { - return this.selectedGroup.value === this.service.GROUP_BY_PHASE_VALUE; - } - - trackById(index: number, item: any) { - return item.id; - } - - reset() { - this.ngZone.runOutsideAngular(() => { - document.body.click(); - }); - } - -} diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/start-date/start-date.component.html b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/start-date/start-date.component.html deleted file mode 100644 index cce89e67..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/start-date/start-date.component.html +++ /dev/null @@ -1,16 +0,0 @@ -
- - - - {{task.start_date | dateFormatter}} - - -
diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/start-date/start-date.component.scss b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/start-date/start-date.component.scss deleted file mode 100644 index 0f103b8a..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/start-date/start-date.component.scss +++ /dev/null @@ -1,33 +0,0 @@ -nz-date-picker { - max-width: 150px; - border-color: transparent !important; - top: 0; - bottom: 0; - left: 0; - right: 0; - - &:hover { - border-color: #d9d9d9 !important; - } -} - -.editable { - position: relative; -} - -.date-text { - display: flex; - align-items: center; - padding-left: 8px; -} - -.def-g-height { - max-height: 32px; - min-height: 32px; - line-height: 32px; -} - -.h-default { - min-height: 42px; - max-height: 42px; -} diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/start-date/start-date.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/start-date/start-date.component.spec.ts deleted file mode 100644 index f6df4a45..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/start-date/start-date.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { StartDateComponent } from './start-date.component'; - -describe('StartDateComponent', () => { - let component: StartDateComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [StartDateComponent] - }); - fixture = TestBed.createComponent(StartDateComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/start-date/start-date.component.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/start-date/start-date.component.ts deleted file mode 100644 index 2679a13d..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/start-date/start-date.component.ts +++ /dev/null @@ -1,72 +0,0 @@ -import {ChangeDetectorRef, Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskListGroup} from "../../../../task-list-v2/interfaces"; -import {formatGanttDate} from "@shared/utils"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-rm-start-date', - templateUrl: './start-date.component.html', - styleUrls: ['./start-date.component.scss'] -}) -export class RMStartDateComponent implements OnInit, OnDestroy{ - @Input({required: true}) task: IProjectTask | null = null; - @Input({required: true}) group: ITaskListGroup | null = null; - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly auth: AuthService, - ) { - } - - ngOnInit() { - this.socket.on(SocketEvents.TASK_START_DATE_CHANGE.toString(), this.handleResponse); - this.socket.on(SocketEvents.GANNT_DRAG_CHANGE.toString(), this.handleDragChangeResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_START_DATE_CHANGE.toString(), this.handleResponse); - this.socket.removeListener(SocketEvents.GANNT_DRAG_CHANGE.toString(), this.handleDragChangeResponse); - } - - private handleResponse = (response: { - id: string; - parent_task: string | null; - start_date: string; - }) => { - if (!this.task) return; - if (response.id === this.task.id && this.task.start_date !== response.start_date) { - this.task.start_date = response.start_date; - this.cdr.markForCheck(); - } - }; - - handleStartDateChange(date: string, task: IProjectTask | null) { - if (!task) return; - this.socket.emit( - SocketEvents.TASK_START_DATE_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - start_date: (date) || null, - parent_task: task.parent_task_id ? task.parent_task_id : null, - group_id: this.group?.id, - time_zone: this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone - })); - } - - private handleDragChangeResponse = (response: { - task_id: string; - task_width: number; - task_offset: number; - start_date: string; - end_date: string; - }) => { - if (!this.task) return; - if (this.task.id === response.task_id) { - this.task.start_date = response.start_date; - } - this.cdr.detectChanges(); - }; -} diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-bar/task-bar.component.html b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-bar/task-bar.component.html deleted file mode 100644 index 5e608a20..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-bar/task-bar.component.html +++ /dev/null @@ -1,21 +0,0 @@ - -
-
-
-
-
-
-
-
-
- diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-bar/task-bar.component.scss b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-bar/task-bar.component.scss deleted file mode 100644 index 41dbd191..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-bar/task-bar.component.scss +++ /dev/null @@ -1,59 +0,0 @@ -.task-bar { - background: rgb(217, 227, 238); - border: 1px solid rgb(173, 181, 189); - position: absolute; - min-width: 32px; - min-height: 28px; - max-height: 28px; - border-radius: 4px; - z-index: 0; - cursor: move; - will-change: trasnsform; - - & .days-indicator-left { - position: absolute; - content: ''; - width: 1px; - background: rgb(217, 227, 238); - left: -1px; - bottom: 25px; - height: 100vh; - } - & .days-indicator-right { - position: absolute; - content: ''; - width: 1px; - background: rgb(217, 227, 238); - right: -1px; - bottom: 25px; - height: 100vh; - } -} - -.resize-handle { - position: absolute; - width: 6px; - height: 100%; - cursor: col-resize; - background-color: #7cb8fb; - transition: width 0.15s ease; - - &:hover { - width: 8px; - } - - &::before { - position: absolute; - content: ':'; - left: 1px; - color: white; - } -} - -.left-handle { - left: 0; -} - -.right-handle { - right: 0; -} diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-bar/task-bar.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-bar/task-bar.component.spec.ts deleted file mode 100644 index 3a1682bc..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-bar/task-bar.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskBarComponent } from './task-bar.component'; - -describe('TaskBarComponent', () => { - let component: TaskBarComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskBarComponent] - }); - fixture = TestBed.createComponent(TaskBarComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-bar/task-bar.component.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-bar/task-bar.component.ts deleted file mode 100644 index 36471f8d..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-bar/task-bar.component.ts +++ /dev/null @@ -1,289 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - HostBinding, - Input, - NgZone, - OnDestroy, - OnInit, - Output, - Renderer2 -} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITaskListGroup} from "../../../../task-list-v2/interfaces"; -import {IDragReturn} from "@interfaces/workload"; -import {SocketEvents} from "@shared/socket-events"; -import {ProjectRoadmapApiService} from "@api/project-roadmap-api.service"; -import {Socket} from "ngx-socket-io"; -import {RoadmapV2HashmapService} from "../../services/roadmap-v2-hashmap.service"; -import {RoadmapV2Service} from "../../services/roadmap-v2-service.service"; -import {IDateVerificationResponse, ITaskDragResponse, ITaskResizeResponse} from "@interfaces/roadmap"; -import moment, {Moment} from "moment"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {formatGanttDate} from "@shared/utils"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-task-bar', - templateUrl: './task-bar.component.html', - styleUrls: ['./task-bar.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskBarComponent implements OnInit, OnDestroy { - @HostBinding("class") cls = "h-100 d-flex align-items-center"; - @Input({required: true}) task: IProjectTask | null = null; - @Input() parentTask: string | null = null; - @Input({required: true}) group: ITaskListGroup | null = null; - @Input({required: true}) chartStart: string | null = null; - @Input({required: true}) chartEnd: string | null = null; - @Output() openTask = new EventEmitter(); - @Output() refreshChart = new EventEmitter(); - @Output() scrollChange = new EventEmitter(); - - protected readonly GANNT_COLUMN_WIDTH = 35; - isResized = false; - - showIndicators = false - - constructor( - private readonly api: ProjectRoadmapApiService, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly socket: Socket, - private readonly map: RoadmapV2HashmapService, - public readonly service: RoadmapV2Service, - private renderer: Renderer2, - private readonly auth: AuthService, - ) { - this.service.onResizeEnd - .pipe(takeUntilDestroyed()) - .subscribe((response: IDateVerificationResponse) => { - this.verifyDateChanges(response); - }) - - this.service.onShowIndicators.pipe(takeUntilDestroyed()).subscribe((taskId) => { - if (this.task?.id === taskId) { - this.showIndicators = true; - this.cdr.markForCheck() - } - }) - - this.service.onRemoveIndicators.pipe(takeUntilDestroyed()).subscribe((taskId) => { - if (this.task?.id === taskId) { - this.showIndicators = false; - this.cdr.markForCheck() - } - this.service.highlighterLeft = 0; - this.service.highlighterWidth = 0; - }) - - } - - ngOnInit() { - this.socket.on(SocketEvents.TASK_START_DATE_CHANGE.toString(), this.handleStartDateChangeResponse); - this.socket.on(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.handleEndDateChangeResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_START_DATE_CHANGE.toString(), this.handleStartDateChangeResponse); - this.socket.removeListener(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.handleEndDateChangeResponse); - } - - async onElementDragged(dragResponse: IDragReturn, task: IProjectTask, groupId: string) { - if (!task) return; - if (dragResponse.dragDifference == 0) { - return this.openTask.emit(task); - } - if (dragResponse.finalLeft === task.offset_from) { - task.offset_from = dragResponse.finalLeft - 1; - } - const diff = (dragResponse.finalLeft % this.GANNT_COLUMN_WIDTH); - this.socket.emit(SocketEvents.GANNT_DRAG_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - task_width: task.width, - task_duration: task.width ? task.width / this.GANNT_COLUMN_WIDTH : 0, - task_offset: dragResponse.finalLeft - diff, - from_start: (dragResponse.finalLeft - diff) / this.GANNT_COLUMN_WIDTH, - chart_start: this.chartStart, - group_id: groupId, - time_zone: this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone - })); - this.socket.once(SocketEvents.GANNT_DRAG_CHANGE.toString(), (response: ITaskDragResponse) => { - if (moment((moment(response.end_date).format("YYYY-MM-DD"))).isSameOrAfter(moment(this.chartEnd).format("YYYY-MM-DD")) || moment((moment(response.start_date).format("YYYY-MM-DD"))).isBefore(moment(this.chartStart).format("YYYY-MM-DD"))) { - this.refreshChart.emit(true); - return; - } - - this.service.handleTaskDragFinish(response); - this.service.emitRemoveIndicators(response.task_id); - this.scrollChange.emit(response.task_offset - (2 * this.GANNT_COLUMN_WIDTH)); - this.cdr.markForCheck(); - }) - } - - onResizeStart(event: MouseEvent, task: IProjectTask, direction: 'left' | 'right', taskBar: HTMLDivElement): void { - if (!task || !task.width || !task.offset_from) { - return; - } - this.isResized = false; - this.service.top = 0; - this.service.emitShowIndicators(this.task?.id as string); - const startX = event.clientX; - const startWidth = task.width; - const startLeft = task.offset_from; - const fChartStart = moment(this.chartStart).format("YYYY-MM-DD"); - const chartStart = moment(fChartStart); - - const onMouseMove = (e: MouseEvent) => { - const deltaX = e.clientX - startX; - - let newWidth = startWidth; - let newLeft = startLeft; - - if (direction === 'left') { - newWidth = startWidth - deltaX; - newLeft = startLeft + deltaX; - } else if (direction === 'right') { - newWidth = startWidth + deltaX; - } - this.service.highlighterLeft = newLeft; - this.service.highlighterWidth = newWidth; - this.cdr.markForCheck(); - - if (newWidth >= this.GANNT_COLUMN_WIDTH - 3) { - task.width = newWidth; - task.offset_from = newLeft; - this.isResized = true; - this.cdr.markForCheck(); - } - }; - - const onMouseUp = () => { - this.isResized = true; - - document.removeEventListener('mousemove', onMouseMove); - document.removeEventListener('mouseup', onMouseUp); - - if (direction === 'left') { - const diff = task.offset_from ? task.offset_from % this.GANNT_COLUMN_WIDTH : 0; - const fromStart = task.offset_from ? (task.offset_from - diff) / this.GANNT_COLUMN_WIDTH : 0; - const taskStartDate = chartStart.add(fromStart, "days"); - - this.socket.emit( - SocketEvents.TASK_START_DATE_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - start_date: (taskStartDate), - parent_task: this.parentTask ? this.parentTask : null, - group_id: this.group?.id, - time_zone: this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone - })); - - // task.offset_from = task.offset_from ? task.offset_from - diff : 0; - } - if (direction === 'right') { - const diff = task.width ? task.width % this.GANNT_COLUMN_WIDTH : 0; - let taskWidth = task.width; - if(diff > 0) { - taskWidth = task.width ? task.width + (this.GANNT_COLUMN_WIDTH - diff) : 0; - } - if (!taskWidth) return; - - const duration = task.offset_from ? (task.offset_from + taskWidth) / this.GANNT_COLUMN_WIDTH : 0; - const taskEndDate = moment(fChartStart).add(duration - 1, "days") - - this.socket.emit( - SocketEvents.TASK_END_DATE_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - end_date: (taskEndDate), - parent_task: this.parentTask ? this.parentTask : null, - group_id: this.group?.id, - time_zone: this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone - })); - - // task.offset_from = task.offset_from ? task.offset_from + taskWidth : 0; - } - }; - document.addEventListener('mousemove', onMouseMove); - document.addEventListener('mouseup', onMouseUp); - } - - - handleStartDateChangeResponse = async (response: ITaskResizeResponse) => { - if (!this.task) return; - if (this.task.id === response.id) { - const fChartStartDate = moment(this.chartStart).format("YYYY-MM-DD"); - const fChartEndDate = moment(this.chartEnd).format("YYYY-MM-DD"); - const chartStartDate = moment(fChartStartDate); - const chartEndDate = moment(fChartEndDate); - this.service.handleStartDateChange(response, chartStartDate, chartEndDate); - } - }; - - handleEndDateChangeResponse = async (response: ITaskResizeResponse) => { - if (!this.task) return; - if (this.task.id === response.id) { - const fChartStartDate = moment(this.chartStart).format("YYYY-MM-DD"); - const fChartEndDate = moment(this.chartEnd).format("YYYY-MM-DD"); - const chartStartDate = moment(fChartStartDate); - const chartEndDate = moment(fChartEndDate); - this.service.handleEndDateChange(response, chartStartDate, chartEndDate); - } - } - - verifyDateChanges(response: IDateVerificationResponse) { - const fToday = moment().format("YYYY-MM-DD"); - const today = moment(fToday); - - if (response.taskStartDate && !response.taskEndDate) { - this.handleTaskPositionSTDOnly(response.taskStartDate, response.chartStartDate, response.task); - } else if (!response.taskStartDate && response.taskEndDate) { - this.handleTaskPositionENDOnly(response.taskEndDate, response.chartStartDate, response.task); - } else if (!response.taskStartDate && !response.taskEndDate) { - this.handleTaskPositionBothNull(today, response.chartStartDate, response.task); - } else if (response.taskStartDate && response.taskEndDate) { - this.handleTaskPositionBothHave(response.taskStartDate, response.taskEndDate, response.chartStartDate, response.task); - } - - this.service.emitRemoveIndicators(this.task?.id as string); - } - - handleTaskPositionSTDOnly(startDate: string, chartStartDate: Moment, task: IProjectTask) { - const fTaskStartDate = moment(startDate).format("YYYY-MM-DD"); - const taskStartDate = moment(fTaskStartDate); - const fromStart = taskStartDate.diff(chartStartDate, "day"); - task.offset_from = fromStart * this.GANNT_COLUMN_WIDTH; - task.width = this.GANNT_COLUMN_WIDTH; - this.cdr.markForCheck(); - } - - handleTaskPositionENDOnly(endDate: string, chartStartDate: Moment, task: IProjectTask) { - const fTaskEndDate = moment(endDate).format("YYYY-MM-DD"); - const taskEndDate = moment(fTaskEndDate); - const fromStart = taskEndDate.diff(chartStartDate, "day"); - task.offset_from = fromStart * this.GANNT_COLUMN_WIDTH; - task.width = this.GANNT_COLUMN_WIDTH; - this.cdr.markForCheck(); - } - - handleTaskPositionBothNull(today: Moment, chartStartDate: Moment, task: IProjectTask) { - const fromStart = today.diff(chartStartDate, "day"); - task.offset_from = fromStart * this.GANNT_COLUMN_WIDTH; - task.width = this.GANNT_COLUMN_WIDTH; - this.cdr.markForCheck(); - } - - handleTaskPositionBothHave(startDate: string, endDate: string, chartStartDate: Moment, task: IProjectTask) { - const fTaskStartDate = moment(startDate).format("YYYY-MM-DD"); - const taskStartDate = moment(fTaskStartDate); - const fromStart = taskStartDate.diff(chartStartDate, "day"); - const fTaskEndDate = moment(endDate).format("YYYY-MM-DD"); - const taskEndDate = moment(fTaskEndDate); - const taskDuration = taskEndDate.diff(taskStartDate, "day"); - task.offset_from = fromStart * this.GANNT_COLUMN_WIDTH; - task.width = (taskDuration + 1) * this.GANNT_COLUMN_WIDTH; - this.cdr.markForCheck(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-name/task-name.component.html b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-name/task-name.component.html deleted file mode 100644 index b3ab742b..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-name/task-name.component.html +++ /dev/null @@ -1,25 +0,0 @@ -
-
-
-
- - {{task.name}} -
- - - {{task.sub_tasks_count}} - - - -
-
-
- -
- -
diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-name/task-name.component.scss b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-name/task-name.component.scss deleted file mode 100644 index a2c434c4..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-name/task-name.component.scss +++ /dev/null @@ -1,41 +0,0 @@ - -.task-open-btn { - opacity: 0; - position: absolute; - right: 0; - top: 0; - background: whitesmoke; - transition: 0.25s all; -} - -.task-name-block { - position: relative; - - &:hover { - & .task-open-btn { - opacity: 1; - } - } -} - -.task-name { - max-width: 240px; -} - -.ellipsis { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.double-arrow { - line-height: 16px; - border: none; - max-height: 16px; - margin-top: 4px; -} - -.h-default { - min-height: 42px; - max-height: 42px; -} diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-name/task-name.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-name/task-name.component.spec.ts deleted file mode 100644 index 0ffb2281..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-name/task-name.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskNameComponent } from './task-name.component'; - -describe('TaskNameComponent', () => { - let component: TaskNameComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskNameComponent] - }); - fixture = TestBed.createComponent(TaskNameComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-name/task-name.component.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-name/task-name.component.ts deleted file mode 100644 index d722a8f1..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/components/task-name/task-name.component.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - Input, - OnDestroy, - OnInit, - Output, - ViewChild -} from '@angular/core'; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {RoadmapV2Service} from "../../services/roadmap-v2-service.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {merge} from "rxjs"; - -@Component({ - selector: 'worklenz-rm-task-name', - templateUrl: './task-name.component.html', - styleUrls: ['./task-name.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RMTaskNameComponent implements OnInit, OnDestroy { - @ViewChild('input') input!: ElementRef; - @Input({required: true}) task: IProjectTask | null = null; - @Output() openTask = new EventEmitter(); - - initialTaskName: string | null = null; - - isEditing = false; - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly service: RoadmapV2Service - ) { - merge(this.service.onSubtaskAdd, this.service.onSubtaskDelete) - .pipe(takeUntilDestroyed()) - .subscribe((task) => { - this.cdr.detectChanges(); - }) - } - - ngOnInit() { - if (this.task?.name) this.initialTaskName = this.task.name; - this.socket.on(SocketEvents.TASK_NAME_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_NAME_CHANGE.toString(), this.handleResponse); - } - - enableEdit() { - this.isEditing = true; - setTimeout(() => { - if (this.input) { - this.input.nativeElement.focus(); - } - }, 250); - } - - validateName() { - if (this.task && this.task.name?.trim() === '') { - this.task.name = this.initialTaskName as string; - this.isEditing = false; - this.cdr.markForCheck(); - return; - } else { - this.changeName(); - } - } - - changeName() { - if (!this.task) return; - this.socket.emit( - SocketEvents.TASK_NAME_CHANGE.toString(), JSON.stringify({ - task_id: this.task.id, - name: this.task.name, - parent_task: null, - })); - this.isEditing = false; - } - - private handleResponse = (response: { id: string; parent_task: string; name: string; }) => { - if (!response) return; - if (this.task?.id !== response.id) return; - - if (this.task && this.task.name != response.name) { - this.task.name = response.name; - this.cdr.markForCheck(); - } - }; -} diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/directives/drag-move.directive.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/directives/drag-move.directive.ts deleted file mode 100644 index 912e3f0f..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/directives/drag-move.directive.ts +++ /dev/null @@ -1,138 +0,0 @@ -import {ChangeDetectorRef, Directive, ElementRef, EventEmitter, HostListener, Output, Renderer2} from '@angular/core'; -import {IDragReturn} from "@interfaces/workload"; -import {RoadmapV2Service} from "../services/roadmap-v2-service.service"; - -@Directive({ - selector: '[worklenzDragAndMove]' -}) - -export class DragAndMoveDirective { - - @Output() dragged: EventEmitter = new EventEmitter(); - - private isResizing = false; - private isDragging = false; - private startX = 0; - private initialLeft = 0; - - finalLeft: number | null = null; - - constructor( - private el: ElementRef, - private renderer: Renderer2, - private readonly service: RoadmapV2Service, - private readonly cdr: ChangeDetectorRef - ) { - } - - throttle(func: any, delay: any) { - let timer: any; - return function (...args: any) { - if (!timer) { - func.apply(DragAndMoveDirective, args); - timer = setTimeout(() => { - timer = null; - }, delay); - } - }; - } - - @HostListener('mousedown', ['$event']) - onMouseDown(event: MouseEvent): void { - const targetClassList = (event.target as HTMLElement).classList; - if (targetClassList.contains('resize-handle')) { - this.isResizing = true; - } else { - this.isDragging = true; - } - - this.startX = event.clientX; - this.initialLeft = Math.floor(this.el.nativeElement.id); - - // create temp placeholder - this.setPlaceHolderStyles(); - - // set highlighter style - this.service.highlighterLeft = this.el.nativeElement.offsetLeft; - this.service.highlighterWidth = this.el.nativeElement.offsetWidth; - - // set styles for element - this.renderer.setStyle(this.el.nativeElement, 'opacity', '0'); - this.renderer.setStyle(this.el.nativeElement, 'transform', `translateX(${Math.floor(this.initialLeft)}px)`); - this.renderer.setStyle(this.el.nativeElement, 'left', '0'); - this.renderer.setStyle(this.el.nativeElement, 'z-index', '1'); - - // remove temp placeholder - this.removePlaceHolderStyles(); - - document.body.style.userSelect = 'none'; - } - - @HostListener('document:mousemove', ['$event']) - onMouseMove(event: MouseEvent): void { - if (this.isResizing || !this.isDragging) { - return; - } - this.service.transition = 0.15; - const throttledMouseMove = this.throttle(() => { - requestAnimationFrame(() => { - this.renderer.setStyle(this.el.nativeElement, 'transform', `translateX(${Math.floor((this.initialLeft + event.clientX - this.startX) / 35) * 35}px)`); - }); - this.finalLeft = Math.floor((this.initialLeft + event.clientX - this.startX) / 35) * 35; - this.service.highlighterLeft = this.finalLeft - }, 200); - throttledMouseMove(); - } - - @HostListener('document:mouseup') - onMouseUp(event: MouseEvent): void { - if (this.isResizing || this.isDragging) { - this.isResizing = false; - this.isDragging = false; - document.body.style.userSelect = 'auto'; - let finalLeft = this.finalLeft ? this.finalLeft : this.initialLeft; - - this.setPlaceHolderStyles(); - - this.renderer.setStyle(this.el.nativeElement, 'opacity', '0'); - this.renderer.setStyle(this.el.nativeElement, 'transform', `translateX(0px)`); - this.renderer.setStyle(this.el.nativeElement, 'left', `${finalLeft}px`); - this.renderer.setStyle(this.el.nativeElement, 'z-index', '0'); - - this.removePlaceHolderStyles(); - - let dragDifference = finalLeft - this.initialLeft; - - const body: IDragReturn = {finalLeft, dragDifference} - - this.dragged.emit(body); - - dragDifference = 0; - this.finalLeft = null; - this.initialLeft = 0; - } - } - - private setPlaceHolderStyles() { - this.showIndicators(); - this.service.transition = 0; - this.service.width = this.el.nativeElement.offsetWidth; - this.service.top = this.el.nativeElement.offsetTop; - this.service.left = this.el.nativeElement.offsetLeft; - this.service.opacity = 1; - this.cdr.markForCheck(); - } - - private removePlaceHolderStyles() { - this.service.opacity = 0; - this.renderer.setStyle(this.el.nativeElement, 'opacity', '1'); - this.service.width = 0; - this.service.left = 0; - } - - private showIndicators() { - this.service.emitShowIndicators(this.el.nativeElement.getAttribute('task_id')) - } - - -} diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/project-roadmap-v2-custom.component.html b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/project-roadmap-v2-custom.component.html deleted file mode 100644 index e11c30ec..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/project-roadmap-v2-custom.component.html +++ /dev/null @@ -1,207 +0,0 @@ - - -
- -
-
- -
- No groups. -
-
-
-
-
-
-
-
-
-
- - {{group.name}} ({{group.tasks.length}}) -
-
-
- No tasks available -
-
-
-
- -
-
- -
-
- -
-
- -
-
-
- -
-
-
- -
-
- -
-
- -
-
-
-
-
- -
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- {{m.month}} -
-
-
-
-
-
-
-
- {{d.name}} -
-
- {{d.day}} -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - -
-
- -
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
-
-
-
-
- - diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/project-roadmap-v2-custom.component.scss b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/project-roadmap-v2-custom.component.scss deleted file mode 100644 index 62ddda13..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/project-roadmap-v2-custom.component.scss +++ /dev/null @@ -1,253 +0,0 @@ -$highlight-color: #1890ff; - -.gannt { - min-width: 95vw; - max-width: 95.5vw; - border-top: 1px solid #f0f0f0; - border-bottom: 1px solid #f0f0f0; - border-radius: 4px; - overflow: hidden; - border-right: 1px solid #f0f0f0; -} - -.top, .top-left-placeholder { - max-height: 70px; - min-height: 70px; - position: sticky; - top: 0; - z-index: 1; - background: white; -} - -.top-left-placeholder { - width: 100%; - right: 0; - box-shadow: 0px 1px #f0f0f0; - background: #fafafa; -} - -.fixed-left-column, .fixed-right-column { - overflow-y: auto; - min-height: calc(100vh - 265px); - max-height: calc(100vh - 265px); - will-change: transform; - transition: scroll-behavior 0.125s ease; -} - -.fixed-left-column { - min-width: 520px; - max-width: 520px; - overflow-x: scroll; - box-shadow: 10px 0px 8px -8px #00000026; - border-left: 1px solid #f0f0f0; - z-index: 9; -} - -.scroll-animation { - scroll-behavior: smooth; -} - -.fixed-right-column { - position: relative; - overflow-x: scroll; - box-shadow: 1px 0px #f0f0f0; -} - -.h-default { - min-height: 42px; - max-height: 42px; -} - -.day-boundary { - position: relative; - box-shadow: -1px 1px #f0f0f0; -} - -.month-boundary { - box-shadow: -1px 0px #f0f0f0; - padding-left: 14px; - background: #e6f7ff; -} - -.resize-handle { - position: absolute; - width: 5px; - height: 100%; - cursor: col-resize; - background-color: #ccc; -} - -.left-handle { - left: 0; -} - -.right-handle { - right: 0; -} - -.active-hover-bg { - cursor: pointer; -} - -.weekend { - background: linear-gradient(-45deg, rgb(230 230 230 / 50%) 12.5%, transparent 12.5%, transparent 50%, rgb(230 230 230 / 50%) 50%, rgb(230 230 230 / 50%) 62.5%, transparent 62.5%, transparent) 0% 0%/5px 5px; -} - -.middle { - position: relative; -} - -.day-cells { - position: absolute; - top: 0; - bottom: 0; - height: 100%; - z-index: -1; -} - -.inner-day-cell { - box-shadow: -1px 1px #f0f0f0; - height: 100%; -} - -.bar-top { - padding-top: 11px; -} - -.ellipsis { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.today { - position: relative; - - &::after { - position: absolute; - content: ""; - left: 0; - top: 0; - bottom: 0; - width: 2px; - z-index: -1; - background: #188ff9a6; - } -} - -.today-bg { - background: #188ff9a6; - - &::after { - display: none; - } - - & div { - color: white; - } -} - -.d-name { - font-size: 13px -} - -.d-day { - line-height: 25px -} - -.month-name { - min-height: 25px; - max-height: 25px -} - -.no-data-img-holder { - max-width: 100px; -} - -.single-group { - min-height: 36px; - max-height: 36px; - border-bottom: 1px solid #f0f0f0; - padding-left: 7px; - color: hwb(0 0% 100%/0.85); - font-weight: 500; -} - -.t-name { - min-width: 260px; - max-width: 260px; - box-shadow: 1px 0px #f0f0f0; -} - -.t-start-date { - box-shadow: 1px 0px #f0f0f0; -} - -.t-start-date, .t-end-date { - min-width: 110px; - max-width: 110px; -} - -.add-parent-task-section { - min-height: 42px; - max-height: 42px; - box-shadow: 0px 0px 0px 1px #d9d9d9; - background: #f7f7f74f; -} - -.single-group-task-subtasks { - box-shadow: inset 0px -1px #f5f5f5; - padding-left: 18px; -} - -.single-group-task { - box-shadow: 1px 1px #f0f0f0; - - &:hover { - .hidden-arrow { - display: flex !important; - } - } -} - -.hidden-arrow { - display: none !important; -} - -.hidden { - visibility: hidden; -} - -.subtasks-arrow { - width: 20px; -} - -.placeholder-drag { - background: rgb(217, 227, 238); - border: 1px solid rgb(173, 181, 189); - position: absolute; - min-height: 28px; - max-height: 28px; - border-radius: 4px; - z-index: 9; - cursor: move; -} - -.highlighter { - position: absolute; - top: 25px; - bottom: 0; - z-index: -1; - background: rgb(217, 227, 238); - transition: 0.15s all; - will-change: transform; -} - -.no-tasks { - width: 100%; - height: 42px; - background: #fafafa; - display: flex; - align-items: center; - padding-left: 12px; -} diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/project-roadmap-v2-custom.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/project-roadmap-v2-custom.component.spec.ts deleted file mode 100644 index c80fed1b..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/project-roadmap-v2-custom.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ProjectRoadmapV2CustomComponent } from './project-roadmap-v2-custom.component'; - -describe('ProjectRoadmapV2CustomComponent', () => { - let component: ProjectRoadmapV2CustomComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ProjectRoadmapV2CustomComponent] - }); - fixture = TestBed.createComponent(ProjectRoadmapV2CustomComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/project-roadmap-v2-custom.component.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/project-roadmap-v2-custom.component.ts deleted file mode 100644 index 1d8af58d..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/project-roadmap-v2-custom.component.ts +++ /dev/null @@ -1,355 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - NgZone, OnDestroy, - OnInit, - QueryList, - ViewChild, - ViewChildren -} from '@angular/core'; -import {ISingleMonth} from "@interfaces/workload"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ProjectRoadmapApiService} from "@api/project-roadmap-api.service"; -import {Socket} from "ngx-socket-io"; -import {ActivatedRoute} from "@angular/router"; -import {deepClone, log_error} from "@shared/utils"; -import {IRoadmapConfigV2} from "@interfaces/roadmap"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {IGroupByOption, ITaskListGroup} from "../../task-list-v2/interfaces"; -import {RoadmapV2HashmapService} from "./services/roadmap-v2-hashmap.service"; -import {RoadmapV2Service} from "./services/roadmap-v2-service.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; -import {ProjectsService} from "../../../projects/projects.service"; -import {DRAWER_ANIMATION_INTERVAL, UNMAPPED} from "@shared/constants"; -import {ITaskViewTaskIds} from "@admin/components/task-view/interfaces"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-project-roadmap-v2-custom', - templateUrl: './project-roadmap-v2-custom.component.html', - styleUrls: ['./project-roadmap-v2-custom.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class ProjectRoadmapV2CustomComponent implements OnInit, OnDestroy { - @ViewChild('scroller') scroller?: ElementRef; - @ViewChild('fixed_right_column') fixed_right_column?: ElementRef; - @ViewChild('fixed_left_column') fixed_left_column?: ElementRef; - @ViewChildren('task_elem') taskElements!: QueryList; - - protected readonly Number = Number; - protected readonly GANNT_COLUMN_WIDTH = 35; - protected showTaskModal = false; - - loading = false; - - initialScroll = 0; - numberOfDays: number = 0; - - projectId: string | null = null; - chartStart: string | null = null; - chartEnd: string | null = null; - - months: ISingleMonth[] = []; - protected groupIds: string[] = []; - selectedTask: IProjectTask | null = null; - - protected get groups() { - return this.service.groups; - } - - protected get expandedGroups() { - const filter = this.groups.filter(m => m.is_expanded); - const ids = filter.map(m => m.id) as string[]; - return ids || []; - } - - private isGroupByPhase() { - return this.service.getCurrentGroup().value === this.service.GROUP_BY_PHASE_VALUE; - } - - constructor( - private readonly api: ProjectRoadmapApiService, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly map: RoadmapV2HashmapService, - public readonly service: RoadmapV2Service, - private readonly taskViewService: TaskViewService, - private readonly projectService: ProjectsService, - private route: ActivatedRoute, - private readonly auth: AuthService, - ) { - this.service.onReCreateChart - .pipe(takeUntilDestroyed()) - .subscribe((loading: boolean) => { - void this.init(loading); - }) - - this.projectService.onNewTaskCreated - .pipe(takeUntilDestroyed()) - .subscribe((task: IProjectTask) => { - const groupId = this.isGroupByPhase() ? UNMAPPED : this.service.getGroupIdByGroupedColumn(task); - if (groupId) - this.service.addTask(task, groupId); - }) - - this.taskViewService.onViewBackFrom - .pipe(takeUntilDestroyed()) - .subscribe(task => { - const task_: IProjectTask = { - id: task.parent_task_id, - project_id: task.project_id, - } - this.handleTaskSelectFromView(task_); - }); - - this.taskViewService.onDelete - .pipe(takeUntilDestroyed()) - .subscribe((task) => { - if (task.parent_task_id) { - if (this.map._subTasksMap.has(task.parent_task_id)) { - this.deleteSubtask(task); - } - } else { - this.map.selectTask(task as IProjectTask); - this.service.deleteTask(task.id); - this.cdr.detectChanges(); - } - }) - - this.projectId = this.route.snapshot.paramMap.get("id"); - this.service.setCurrentGroup(this.service.GROUP_BY_OPTIONS[0]); - } - - async ngOnInit() { - await this.init(true); - } - - async ngOnDestroy() { - this.service.reset(); - } - - async init(isLoading: boolean) { - await this.createChart(isLoading); - await this.getGroups(true); - } - - private async createChart(loading: boolean) { - if (!this.projectId) return; - try { - this.loading = loading; - const timeZone = this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name as string : Intl.DateTimeFormat().resolvedOptions().timeZone - const res = await this.api.getGanntDates(this.projectId, timeZone); - if (res.done) { - this.months = res.body.date_data; - this.numberOfDays = res.body.width; - this.initialScroll = res.body.scroll_by; - this.service.offset = res.body.scroll_by; - this.chartStart = res.body.chart_start; - this.chartEnd = res.body.chart_end; - this.service.chartStartDate = res.body.chart_start; - } - this.cdr.markForCheck(); - } catch (e) { - log_error(e) - } - } - - protected async onGroupByChange(group: IGroupByOption) { - this.loading = true; - this.service.setCurrentGroup(group); - await this.getGroups(false); - setTimeout(() => { - this.cdr.markForCheck(); - }, 100); // wait for animations to be finished - } - - private getConf(parentTaskId?: string): IRoadmapConfigV2 { - const config: IRoadmapConfigV2 = { - id: this.projectId as string, - group: this.service.getCurrentGroup().value, - timezone: this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name as string : Intl.DateTimeFormat().resolvedOptions().timeZone, - isSubtasksInclude: false, - expandedGroups: this.expandedGroups - }; - - if (parentTaskId) - config.parent_task = parentTaskId; - - return config; - } - - private async getGroups(isExpandInclude: boolean) { - if (!this.projectId) return; - try { - this.map.deselectAll(); - const config = this.getConf(); - if (!isExpandInclude) config.expandedGroups = []; - config.isSubtasksInclude = this.service.isSubtasksIncluded; - const res = await this.api.getTaskGroups(config) as IServerResponse; - if (res.done) { - const groups = deepClone(res.body); - this.groupIds = groups.map((g: ITaskListGroup) => g.id); - this.mapTasks(groups); - this.service.groups = groups; - this.loading = false; - this.cdr.markForCheck(); - await this.initScrollHandler(true); - } - } catch (e) { - log_error(e) - this.loading = false; - this.cdr.markForCheck(); - } - } - - private async getSubTasks(task: IProjectTask) { - let subTasks: IProjectTask[] = []; - if (task?.id) { - try { - const config = this.getConf(task.id); - const res = await this.api.getTaskGroups(config) as IServerResponse; - if (res.done) subTasks = res.body; - } catch (e) { - // ignored - } - } - return subTasks; - } - - private mapTasks(groups: ITaskListGroup[]) { - for (const group of groups) { - this.map.registerGroup(group); - for (const task of group.tasks) { - if (task.start_date) task.start_date = new Date(task.start_date) as any; - if (task.end_date) task.end_date = new Date(task.end_date) as any; - } - } - } - - async toggleCollapse(groupId: string) { - this.service.toggleGroupExpansion(groupId); - this.cdr.detectChanges(); - } - - trackById(index: number, item: any) { - return item.id; - } - - onShowChange(show: boolean) { - if (!show) { - this.service.emitRemoveIndicators(this.selectedTask?.id as string); - this.selectedTask = null; - this.cdr.markForCheck(); - } - } - - protected openTask(task: IProjectTask) { - this.selectedTask = null; - this.selectedTask = task; - this.showTaskModal = true; - this.cdr.markForCheck() - } - - scrollListner() { - this.ngZone.runOutsideAngular(() => { - this.fixed_left_column?.nativeElement.addEventListener('scroll', () => { - if (this.fixed_right_column) this.fixed_right_column.nativeElement.scrollTop = this.fixed_left_column?.nativeElement.scrollTop; - }); - this.fixed_right_column?.nativeElement.addEventListener('scroll', () => { - if (this.fixed_left_column) this.fixed_left_column.nativeElement.scrollTop = this.fixed_right_column?.nativeElement.scrollTop; - }); - }) - } - - async initScrollHandler(needed: boolean) { - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - if (this.fixed_right_column && needed) { - this.fixed_right_column.nativeElement.scrollLeft = this.initialScroll - (2 * this.GANNT_COLUMN_WIDTH); - this.scrollListner(); - } - }, 100) - }); - this.cdr.markForCheck(); - } - - afterViewScrollHandler(fromLeft: number) { - this.ngZone.runOutsideAngular(() => { - if (this.fixed_right_column) { - this.fixed_right_column.nativeElement.classList.add('scroll-animation'); - this.fixed_right_column.nativeElement.scrollLeft = fromLeft - (2 * this.GANNT_COLUMN_WIDTH); - setTimeout(() => { - if (this.fixed_right_column) { - this.fixed_right_column.nativeElement.classList.remove('scroll-animation'); - } - }, 125); - } - }); - this.cdr.markForCheck(); - } - - protected async displaySubTasks(task: IProjectTask, groupId: string) { - if (!task.id && task.sub_tasks_loading) return; - if (!task.show_sub_tasks && task.sub_tasks_count === 0) { - task.show_sub_tasks = true; - task.sub_tasks = []; - return; - } - task.sub_tasks_loading = true; - task.show_sub_tasks = !task.show_sub_tasks; - if (task.show_sub_tasks) { - task.sub_tasks = await this.getSubTasks(task); - for (const t of task.sub_tasks) { - this.map.add(groupId, t); - } - } else { - for (const t of task.sub_tasks || []) { - this.map.deselectTask(t); - } - task.sub_tasks = []; - } - task.sub_tasks_loading = false; - this.cdr.detectChanges(); - } - - deleteSubtask(task: ITaskViewTaskIds) { - if (!task) return; - - const subtasks = this.map._subTasksMap.get(task.parent_task_id as string); - if (!subtasks) return; - - const subtask = subtasks.find(t => t.id === task.id); - if (!subtask) return; - - this.map.selectTask(subtask as IProjectTask); - this.service.deleteTask(subtask.id as string); - - const task_: IProjectTask = { - id: task.parent_task_id, - project_id: task.project_id, - } - this.handleTaskSelectFromView(task_); - - this.cdr.detectChanges(); - } - - isVisible(el: HTMLDivElement) { - if (!el) { - return false; - } - return true; - } - - private handleTaskSelectFromView(task: IProjectTask) { - this.showTaskModal = false; - setTimeout(() => { - if (task) { - this.openTask(task); - } - }, DRAWER_ANIMATION_INTERVAL); - this.cdr.detectChanges(); - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/services/roadmap-v2-hashmap.service.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/services/roadmap-v2-hashmap.service.ts deleted file mode 100644 index 8efc0938..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/services/roadmap-v2-hashmap.service.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { Injectable } from '@angular/core'; -import {Subject} from "rxjs"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITaskListGroup} from "../../../task-list-v2/interfaces"; - -@Injectable({ - providedIn: 'root' -}) -export class RoadmapV2HashmapService { - private readonly _selectSbj$ = new Subject(); - private readonly _deselectSbj$ = new Subject(); - private readonly _deselectAllSbj$ = new Subject(); - - /** Map */ - private readonly _groupTaskMap = new Map(); - /** Map */ - private readonly _taskGroupIdsMap = new Map(); - /** Map */ - public readonly _selectedTasksMap = new Map(); - /** Map */ - private readonly _allTasksMap = new Map(); - /** Map(); - - private _selectedCount = 0; - - public get tasks() { - return this._allTasksMap; - } - - public get onSelect$() { - return this._selectSbj$.asObservable(); - } - - public get onDeselect$() { - return this._deselectSbj$.asObservable(); - } - - public get onDeselectAll$() { - return this._deselectAllSbj$.asObservable(); - } - - public reset() { - this._groupTaskMap.clear(); - this._taskGroupIdsMap.clear(); - this._selectedTasksMap.clear(); - this._allTasksMap.clear(); - this._subTasksMap.clear(); - this._selectedCount = 0; - } - - public registerGroup(group: ITaskListGroup) { - for (const task of group.tasks) { - this.add(group.id, task); - } - } - - public add(groupId: string, task: IProjectTask) { - if (!task.id) return; - this.updateGroupTaskMap(groupId, task.id); - this._taskGroupIdsMap.set(task.id, groupId); - this._allTasksMap.set(task.id, task); - - if (task.parent_task_id) { - this.updateSubtasksMap(task.parent_task_id, task) - } - - } - - public addGroupTask(groupId: string, task: IProjectTask) { - if (!task.id) return; - this._taskGroupIdsMap.set(task.id, groupId); - } - - public has(taskId: string) { - return this._allTasksMap.has(taskId); - } - - public remove(task: IProjectTask) { - if (!task.id) return; - this.deselectTask(task); - this._taskGroupIdsMap.get(task.id); - this._allTasksMap.delete(task.id); - } - - private updateGroupTaskMap(groupId: string, taskId: string, selected?: boolean) { - const map = this._groupTaskMap.get(groupId); - if (map) { - if (typeof selected === "boolean") { - map[taskId] = selected; - } else { - delete map[taskId]; - } - - this._groupTaskMap.set(groupId, map); - } else { - this._groupTaskMap.set(groupId, {[taskId]: selected || false}); - } - } - - private updateSubtasksMap(parentTaskId: string, task: IProjectTask, selected?: boolean) { - const subtasks = this._subTasksMap.get(parentTaskId) || []; - const isParentTaskAvailable = subtasks.some((subtask) => subtask.id === task.id); - - // Only push the subtask if the parent task is not available - if (!isParentTaskAvailable) { - subtasks.push(task); - this._subTasksMap.set(parentTaskId, subtasks); - } - - } - - public selectTask(task: IProjectTask) { - if (this._selectedTasksMap.get(task.id as string)) return; - - this._selectedTasksMap.set(task.id as string, task); - this._selectedCount++; - - this.updateGroupTaskMap( - this._taskGroupIdsMap.get(task.id as string) as string, - task.id as string, - true - ); - - this._selectSbj$.next(task); - - } - - public deselectTask(task: IProjectTask) { - if (this._selectedTasksMap.has(task.id as string)) { - this._selectedTasksMap.delete(task.id as string); - this._selectedCount--; - - this.updateGroupTaskMap( - this._taskGroupIdsMap.get(task.id as string) as string, - task.id as string, - false - ); - - this._deselectSbj$.next(task); - } - } - - private deselectLocalGroups() { - for (const [groupId, task] of this._groupTaskMap) { - for (const taskId in task) { - this.updateGroupTaskMap(groupId, taskId, false); - } - } - } - - public deselectAll() { - if (!this._selectedTasksMap.size) return; - - this.deselectLocalGroups(); - this._selectedTasksMap.clear(); - this._selectedCount = 0; - - this._deselectAllSbj$.next(); - } - - public isAllSelected(groupId: string) { - const tasks = this._groupTaskMap.get(groupId); - - if (tasks) { - for (const taskId in tasks) - if (!tasks[taskId]) return false; - return true; - } - - return false; - } - - public isAllDeselected(groupId: string) { - const tasks = this._groupTaskMap.get(groupId); - - if (tasks) { - for (const taskId in tasks) - if (tasks[taskId]) return false; - } - - return true; - } - - public getSelectedCount() { - return this._selectedCount; - } - - public getGroupId(taskId: string) { - return this._taskGroupIdsMap.get(taskId); - } - - public getSelectedTasks() { - const tasks = []; - for (const [, task] of this._selectedTasksMap.entries()) { - tasks.push(task); - } - return tasks; - } - - public getSelectedTaskIds(): string[] { - const tasks = []; - for (const [taskId] of this._selectedTasksMap.entries()) { - tasks.push(taskId); - } - return tasks; - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/services/roadmap-v2-service.service.ts b/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/services/roadmap-v2-service.service.ts deleted file mode 100644 index 6423ea72..00000000 --- a/worklenz-frontend/src/app/administrator/modules/roadmap-v2/project-roadmap-v2-custom/services/roadmap-v2-service.service.ts +++ /dev/null @@ -1,414 +0,0 @@ -import {Injectable} from '@angular/core'; -import {BehaviorSubject, Subject} from "rxjs"; -import {IGroupByOption, ITaskListGroup, ITaskListGroupChangeResponse} from "../../../task-list-v2/interfaces"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {ITaskPrioritiesGetResponse} from "@interfaces/api-models/task-priorities-get-response"; -import {ITaskPhase} from "@interfaces/api-models/task-phase"; -import {Socket} from "ngx-socket-io"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {SocketEvents} from "@shared/socket-events"; -import {RoadmapV2HashmapService} from "./roadmap-v2-hashmap.service"; -import {IDateVerificationResponse, ITaskDragResponse, ITaskResizeResponse} from "@interfaces/roadmap"; -import moment, {Moment} from "moment"; - -@Injectable({ - providedIn: 'root' -}) -export class RoadmapV2Service { - private readonly statusesSbj$ = new Subject(); - private readonly prioritiesSbj$ = new Subject(); - private readonly taskAddOrDeleteSbj$ = new BehaviorSubject(null); - private readonly refreshSbj$ = new Subject(); - private readonly groupChangeSbj$ = new Subject<{ groupId: string; taskId: string; color: string; }>(); - private readonly phasesSbj$ = new Subject(); - private readonly updateGroupProgressSbj$ = new Subject<{ taskId: string; }>(); - private readonly refreshSubtasksIncludedSbj$ = new Subject(); - private readonly reCreateChartSbj$ = new Subject(); - private readonly resizeEndSbj$ = new Subject(); - private readonly subTaskAddSbj$ = new Subject(); - private readonly subTaskDeleteSbj$ = new Subject(); - private readonly showIndicatorsSbj$ = new Subject(); - private readonly removeIndicatorsSbj$ = new Subject(); - - public readonly HIGHLIGHT_COL_CLS = 'highlight-col'; - - public readonly GROUP_BY_STATUS_VALUE = "status"; - public readonly GROUP_BY_PRIORITY_VALUE = "priority"; - public readonly GROUP_BY_PHASE_VALUE = "phase"; - public readonly GROUP_BY_OPTIONS: IGroupByOption[] = [ - {label: "Status", value: this.GROUP_BY_STATUS_VALUE}, - {label: "Priority", value: this.GROUP_BY_PRIORITY_VALUE}, - {label: "Phase", value: this.GROUP_BY_PHASE_VALUE} - ]; - - public groups: ITaskListGroup[] = []; - - private _projectId: string | null = null; - private _statuses: ITaskStatusViewModel[] = []; - private _priorities: ITaskPrioritiesGetResponse[] = []; - private _phases: ITaskPhase[] = []; - - public isSubtasksIncluded = false; - - public offset = 0; - - public width = 0; - public top = 0; - public left = 0; - public opacity = 0; - public transition = 0.15; - - public highlighterLeft = 0; - public highlighterWidth = 0; - - public chartStartDate: string | null = null; - - private get _currentGroup(): IGroupByOption { - const key = localStorage.getItem("worklenz.roadmap.group_by"); - if (key) { - const g = this.GROUP_BY_OPTIONS.find(o => o.value === key); - if (g) - return g; - } - return this.GROUP_BY_OPTIONS[0]; - } - - private set _currentGroup(option) { - localStorage.setItem("worklenz.roadmap.group_by", option.value); - } - - public set priorities(value) { - this._priorities = value; - this.prioritiesSbj$.next(); - } - - public get priorities() { - return this._priorities; - } - - public set phases(value) { - this._phases = value; - this.phasesSbj$.next(); - } - - public get phases() { - return this._phases; - } - - get onGroupChange$() { - return this.groupChangeSbj$.asObservable(); - } - - set statuses(value) { - this._statuses = value; - this.statusesSbj$.next(); - } - - get statuses() { - return this._statuses; - } - - get onReCreateChart() { - return this.reCreateChartSbj$.asObservable(); - } - - get onResizeEnd() { - return this.resizeEndSbj$.asObservable(); - } - - get onShowIndicators() { - return this.showIndicatorsSbj$.asObservable(); - } - - get onRemoveIndicators() { - return this.removeIndicatorsSbj$.asObservable(); - } - - constructor( - private readonly socket: Socket, - private readonly map: RoadmapV2HashmapService - ) { - } - - public setProjectId(id: string) { - this._projectId = id; - } - - public getProjectId() { - return this._projectId; - } - - get onSubtaskAdd() { - return this.subTaskAddSbj$.asObservable(); - } - - get onSubtaskDelete() { - return this.subTaskDeleteSbj$.asObservable(); - } - - public setCurrentGroup(group: IGroupByOption) { - this._currentGroup = group; - } - - public getCurrentGroup() { - return this._currentGroup; - } - - public emitRefresh() { - this.refreshSbj$.next(); - } - - public emitTaskAddOrDelete(taskId: string, isSubTask: boolean) { - this.taskAddOrDeleteSbj$.next({ - taskId: taskId, - isSubTask: isSubTask - }); - } - - public emitUpdateGroupProgress(taskId: string) { - this.updateGroupProgressSbj$.next({taskId}); - } - - public emitRefreshSubtasksIncluded() { - this.refreshSubtasksIncludedSbj$.next(); - } - - public emitReCreateChart(loading: boolean) { - this.reCreateChartSbj$.next(loading); - } - - public emitResizeEnd(response: IDateVerificationResponse) { - this.resizeEndSbj$.next(response); - } - - public emitSubTaskAdd(parentTask: string) { - this.subTaskAddSbj$.next(parentTask); - } - - public emitSubTaskDelete(parentTask: string) { - this.subTaskDeleteSbj$.next(parentTask); - } - - public emitShowIndicators(taskId: string) { - this.showIndicatorsSbj$.next(taskId) - } - - public emitRemoveIndicators(taskId: string) { - this.removeIndicatorsSbj$.next(taskId) - } - - public getGroupIdByGroupedColumn(task: IProjectTask) { - const groupBy = this.getCurrentGroup().value; - if (groupBy === this.GROUP_BY_STATUS_VALUE) - return task.status as string; - - if (groupBy === this.GROUP_BY_PRIORITY_VALUE) - return task.priority as string; - - if (groupBy === this.GROUP_BY_PHASE_VALUE) - return task.phase_id as string; - - return null; - } - - public updateTaskGroup(task: IProjectTask, insert = true) { - if (!task.id) return; - - const groupId = this.getGroupIdByGroupedColumn(task); - - if (groupId) { - // Delete the task from its current group - this.deleteTask(task.id); - - // Add the task to the new group - this.addTask(task, groupId, insert); - this.emitUpdateGroupProgress(task.id); - } - } - - public toggleGroupExpansion(groupId: string) { - if (!groupId) return; - - const group = this.groups.find(g => g.id === groupId); - if (!group) return; - - group.is_expanded = !group.is_expanded; - this.groups.forEach((others) => { - if (others !== group) { - others.is_expanded = false; - } - }) - - } - - public handleTaskDragFinish(dragResponse: ITaskDragResponse) { - if (!dragResponse.task_id) return; - - if (this.map.tasks.has(dragResponse.task_id)) { - const task_ = this.map.tasks.get(dragResponse.task_id as string); - if (!task_) return; - - task_.width = dragResponse.task_width; - task_.offset_from = dragResponse.task_offset; - task_.start_date = dragResponse.start_date; - task_.end_date = dragResponse.end_date; - } - this.transition = 0.15; - } - - public handleStartDateChange(resizeResponse: ITaskResizeResponse, chartStartDate: Moment, chartEndDate: Moment) { - if (!resizeResponse.id) return; - - if (resizeResponse.start_date) { - const fTaskStartDate = moment(resizeResponse.start_date).format("YYYY-MM-DD"); - const taskStartDate = moment(fTaskStartDate); - if (taskStartDate.isBefore(chartStartDate) || taskStartDate.isSameOrAfter(chartEndDate)) { - return this.emitReCreateChart(true); - } - } - - if (this.map.tasks.has(resizeResponse.id)) { - const task = this.map.tasks.get(resizeResponse.id as string); - if (!task) return; - - const body: IDateVerificationResponse = { - task: task, - taskStartDate: resizeResponse.start_date, - taskEndDate: resizeResponse.end_date, - chartStartDate: chartStartDate, - } - this.emitResizeEnd(body); - } - - } - - public handleEndDateChange(resizeResponse: ITaskResizeResponse, chartStartDate: Moment, chartEndDate: Moment) { - if (!resizeResponse.id) return; - - if (resizeResponse.end_date) { - const fTaskEndDate = moment(resizeResponse.end_date).format("YYYY-MM-DD"); - const taskEndDate = moment(fTaskEndDate); - if (taskEndDate.isSameOrAfter(chartEndDate)) { - return this.emitReCreateChart(true); - } - } - - if (this.map.tasks.has(resizeResponse.id)) { - const task = this.map.tasks.get(resizeResponse.id as string); - if (!task) return; - - const body: IDateVerificationResponse = { - task: task, - taskStartDate: resizeResponse.start_date, - taskEndDate: resizeResponse.end_date, - chartStartDate: chartStartDate, - } - this.emitResizeEnd(body); - } - } - - public onGroupChange(taskId: string, toGroup: string) { - if (!taskId || !toGroup) return; - - const task = this.map.tasks.get(taskId); - if (!task || !task.id || task.parent_task_id) return; - - this.deleteTask(task.id); - this.addTask(task, toGroup, true); - - } - - public deleteSubtaskFromView(taskId: string) { - const task = this.map.tasks.get(taskId); - if (!task) return; - this.map.selectTask(task); - this.deleteTask(taskId); - } - - public deleteTask(taskId: string, index: number | null = null) { - // Get the group id of the task. - const groupId = this.map.getGroupId(taskId); - if (!groupId || !taskId) return; - - // Find the group that contains the task. - const group = this.groups.find(g => g.id === groupId); - if (!group) return; - - // Get the task object from the list of selected tasks. - const task = this.map.getSelectedTasks().find(t => t.id === taskId); - // If the task is a sub-task, remove it from its parent task's sub-tasks list. - if (task?.is_sub_task) { - const parentTask = group.tasks.find(t => t.id === task.parent_task_id); - if (parentTask) { - // Find the index of the sub-task in the parent task's sub-tasks list. - const index = parentTask.sub_tasks?.findIndex(t => t.id === task.id); - if (typeof index !== "undefined" && index !== -1) { - if (!parentTask.sub_tasks_count) parentTask.sub_tasks_count = 0; - parentTask.sub_tasks_count = Math.max(+parentTask.sub_tasks_count - 1, 0); - parentTask.sub_tasks?.splice(index, 1); - this.emitTaskAddOrDelete(parentTask.id as string, true); - this.emitSubTaskDelete(parentTask.id as string); - } - } - this.socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), parentTask?.id); - this.map.remove(task); - } else { // If the task is not a sub-task, remove it from the group's task list. - const taskIndex = index ?? group.tasks.findIndex(t => t.id === taskId); - if (taskIndex !== -1) { - this.map.remove(group.tasks[taskIndex]); - group.tasks.splice(taskIndex, 1); - this.emitTaskAddOrDelete(taskId, false); - } - } - - // Deselect all tasks after removing the task. - this.map.deselectAll(); - } - - public addSubtaskFromView(task: IProjectTask) { - if (!task.parent_task_id) return; - - const groupId = this.map.getGroupId(task.parent_task_id); - if (!groupId) return; - - task.width = 35; - task.offset_from = this.offset; - - this.addTask(task, groupId, true); - - } - - public addTask(task: IProjectTask, groupId: string, insert = false) { - const group = this.groups.find(g => g.id === groupId); - if (group && task.id) { - if (task.parent_task_id) { - const parentTask = group.tasks.find(t => t.id === task.parent_task_id); - if (parentTask) { - if (!parentTask.sub_tasks_count) parentTask.sub_tasks_count = 0; - parentTask.sub_tasks_count = +parentTask.sub_tasks_count + 1; - parentTask.sub_tasks?.push(task); - this.socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), parentTask?.id); - this.emitSubTaskAdd(task.parent_task_id) - } - } else { - if (insert) { - group.tasks.unshift(task); - } else { - group.tasks.push(task); - } - } - - this.map.add(groupId, task); - this.emitTaskAddOrDelete(task.parent_task_id as string, !!task.parent_task_id); - } - } - - public reset() { - this._statuses = []; - this._priorities = []; - - this._projectId = null; - this.groups = []; - this.isSubtasksIncluded = false; - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/interfaces.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/interfaces.ts deleted file mode 100644 index 02128b0a..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/interfaces.ts +++ /dev/null @@ -1,100 +0,0 @@ -// TODO: Move to separate files later - -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITaskPrioritiesGetResponse} from "@interfaces/api-models/task-priorities-get-response"; -import {ITeamMemberViewModel} from "@interfaces/api-models/team-members-get-response"; -import {ITaskLabel} from "@interfaces/task-label"; -import {ITaskStatus} from "@interfaces/task-status"; - -export interface ISelectableTaskStatus extends ITaskStatus { - selected?: boolean; -} - -export interface ITaskListConfigV2 { - id: string; - field: string | null; - order: string | null; - search: string | null; - statuses: string | null; - members: string | null; - projects: string | null; - labels?: string | null; - priorities?: string | null; - archived?: boolean; - count?: boolean; - parent_task?: string; - group?: string; - isSubtasksInclude: boolean; -} - -export interface ITaskListGroup { - id: string; - name: string; - start_date?: string; - end_date?: string; - color_code: string; - category_id?: string; - old_category_id?: string; - todo_progress?: number; - doing_progress?: number; - done_progress?: number; - tasks: IProjectTask[]; - is_expanded?: boolean; -} - -export interface ITaskListContextMenuEvent { - event: MouseEvent, - task: IProjectTask -} - -export interface IGroupByOption { - label: string; - value: string; -} - -export interface ITaskListPriorityFilter extends ITaskPrioritiesGetResponse { - selected?: boolean; -} - -export interface ITaskListLabelFilter extends ITaskLabel { - selected?: boolean; -} - -export interface ITaskListMemberFilter extends ITeamMemberViewModel { - selected?: boolean; -} - -export interface ITaskListGroupChangeResponse { - taskId?: string; - isSubTask?: boolean; -} - -export interface ITaskListSortableColumn { - label?: string; - key?: string; - sort_order?: string; - selected?: boolean; -} - -export interface ITaskListEstimationChangeResponse { - id: string; - total_minutes: number; - total_hours: number; - parent_task: string; - total_minutes_spent: number; - total_time_string?: string; -} - -export interface ILabelsChangeResponse { - id: string; - parent_task: string; - is_new: boolean; - new_label: ITaskLabel; - all_labels: ITaskLabel[], - labels: ITaskLabel[]; -} - -export interface IMembersFilterChange { - selection: string, - is_subtasks_included: boolean -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/pipes/end-name-check.pipe.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/pipes/end-name-check.pipe.ts deleted file mode 100644 index 535a6cae..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/pipes/end-name-check.pipe.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; -import {ITaskLabel} from "@interfaces/task-label"; - -@Pipe({ - name: 'endNameCheck' -}) -export class EndNameCheckPipe implements PipeTransform { - transform(value: ITaskLabel, ...args: unknown[]): unknown { - return !!(value.end && value.names); - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-columns-toggle/task-list-columns-toggle.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-columns-toggle/task-list-columns-toggle.component.html deleted file mode 100644 index 149157f0..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-columns-toggle/task-list-columns-toggle.component.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-columns-toggle/task-list-columns-toggle.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-columns-toggle/task-list-columns-toggle.component.scss deleted file mode 100644 index ee23849d..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-columns-toggle/task-list-columns-toggle.component.scss +++ /dev/null @@ -1,10 +0,0 @@ -.columns-toggle { - //position: absolute; - //top: 4px; - //right: -21px; -} - -.menu { - max-height: 300px; - overflow-y: auto; -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-columns-toggle/task-list-columns-toggle.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-columns-toggle/task-list-columns-toggle.component.spec.ts deleted file mode 100644 index 6e849ef3..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-columns-toggle/task-list-columns-toggle.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskListColumnsToggleComponent} from './task-list-columns-toggle.component'; - -describe('TaskListColumnsToggleComponent', () => { - let component: TaskListColumnsToggleComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskListColumnsToggleComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskListColumnsToggleComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-columns-toggle/task-list-columns-toggle.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-columns-toggle/task-list-columns-toggle.component.ts deleted file mode 100644 index 308e7ad0..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-columns-toggle/task-list-columns-toggle.component.ts +++ /dev/null @@ -1,62 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit} from '@angular/core'; -import {TasksApiService} from "@api/tasks-api.service"; -import {ITaskListColumn} from "@interfaces/task-list-column"; -import {TaskListV2Service} from "../task-list-v2.service"; -import {ProjectPhasesService} from "@services/project-phases.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; - -@Component({ - selector: 'worklenz-task-list-columns-toggle', - templateUrl: './task-list-columns-toggle.component.html', - styleUrls: ['./task-list-columns-toggle.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListColumnsToggleComponent implements OnInit { - @Input() projectId!: string; - - protected loading = false; - - constructor( - public readonly service: TaskListV2Service, - private readonly api: TasksApiService, - private readonly cdr: ChangeDetectorRef, - private readonly phasesService: ProjectPhasesService - ) { - this.phasesService.onLabelChange - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.getColumns(); - }); - } - - ngOnInit() { - void this.getColumns(); - } - - private async getColumns() { - if (!this.projectId) return; - try { - this.loading = true; - const res = await this.api.getListCols(this.projectId); - if (res.done) { - this.service.columns = [...res.body]; - } - this.loading = false; - } catch (e) { - this.loading = false; - } - - this.cdr.detectChanges(); - } - - protected async onColumnsToggle(checked: boolean, item: ITaskListColumn) { - if (!this.projectId) return; - try { - item.pinned = checked; - this.service.emitColsChange(); - void this.api.toggleListCols(this.projectId, item); - } catch (e) { - // ignored - } - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-filters/task-list-filters.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-filters/task-list-filters.component.html deleted file mode 100644 index 743524bb..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-filters/task-list-filters.component.html +++ /dev/null @@ -1,169 +0,0 @@ -
-
-
- - - - - - - - - - - - - - - - -
    -
  • - - -
  • -
-
- - - - -
    -
  • -
    - - {{item.name}} -
    -
  • -
-
- - - - -
    -
  • - -
  • -
  • -
    - -
    -
  • -
-
- - - - -
    -
  • - -
  • -
  • -
    - -
    - {{item.name}} - {{item.email}} -
    -
    -
  • -
-
- - - - - - - -
    -
  • - {{item.label}} -
  • -
-
- - - - - -
-
- diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-filters/task-list-filters.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-filters/task-list-filters.component.scss deleted file mode 100644 index 9a6a26da..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-filters/task-list-filters.component.scss +++ /dev/null @@ -1,27 +0,0 @@ -.members-menu { - max-height: 250px; - overflow: hidden; - overflow-y: auto; -} - -.filter-active { - background: #E6F7FF; - border: 1px solid #1890FF; -} - -.lg-only { - display: inline-block; -} - -@media(max-width: 1200px) { - .mob-p-0 { - display: none; - } - .button-mobile { - padding-left: 7px; - padding-right: 7px; - } - .lg-only { - display: none; - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-filters/task-list-filters.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-filters/task-list-filters.component.spec.ts deleted file mode 100644 index d1df9a14..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-filters/task-list-filters.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskListFiltersComponent} from './task-list-filters.component'; - -describe('TaskListFiltersComponent', () => { - let component: TaskListFiltersComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskListFiltersComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskListFiltersComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-filters/task-list-filters.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-filters/task-list-filters.component.ts deleted file mode 100644 index 6dbf7cb5..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-filters/task-list-filters.component.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - NgZone, - OnDestroy, - OnInit, - Output -} from '@angular/core'; -import {deepClone} from "@shared/utils"; -import { - IGroupByOption, IMembersFilterChange, - ISelectableTaskStatus, - ITaskListLabelFilter, - ITaskListMemberFilter, - ITaskListPriorityFilter, - ITaskListSortableColumn -} from "../interfaces"; -import {TaskListV2Service} from "../task-list-v2.service"; -import {TasksApiService} from "@api/tasks-api.service"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {TaskLabelsApiService} from "@api/task-labels-api.service"; -import {UtilsService} from "@services/utils.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {ProjectPhasesService} from "@services/project-phases.service"; -import {DRAWER_ANIMATION_INTERVAL} from "@shared/constants"; -import {AuthService} from '@services/auth.service'; -import {ProjectsService} from "../../../projects/projects.service"; - -@Component({ - selector: 'worklenz-task-list-filters', - templateUrl: './task-list-filters.component.html', - styleUrls: ['./task-list-filters.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListFiltersComponent implements OnInit, OnDestroy { - @Input() projectId!: string; - - @Output() onGroupBy = new EventEmitter(); - @Output() onFilterSortBy = new EventEmitter(); - @Output() onFilterByPriority = new EventEmitter(); - @Output() onFilterByLabel = new EventEmitter(); - @Output() onFilterByMember = new EventEmitter(); - @Output() onFilterSearch = new EventEmitter(); - @Output() onPhaseSettingsClick = new EventEmitter(); - @Output() onStatusSettingsClick = new EventEmitter(); - - readonly ASCEND = "ascend"; - readonly DESCEND = "descend"; - readonly COUNTS_LABELS_STYLE = {backgroundColor: '#1890ff', color: '#fff'}; - - priorities: ITaskListPriorityFilter[] = []; - labels: ITaskListLabelFilter[] = []; - members: ITaskListMemberFilter[] = []; - - memberSearchText: string | null = null; - labelsSearchText: string | null = null; - taskSearch: string | null = null; - - sortFiltersActive = false; - prioritiesFiltersActive = false - labelsFiltersActive = false; - membersFiltersActive = false; - - loadingAssignees = false; - - sortedColumnsCount = 0; - selectedPrioritiesCount = 0; - selectedLabelsCount = 0; - selectedMembersCount = 0; - - statuses: ISelectableTaskStatus[] = []; - - readonly sortableColumns: ITaskListSortableColumn[] = [ - {label: "Task", key: "name", sort_order: this.ASCEND}, - {label: "Status", key: "status", sort_order: this.ASCEND}, - {label: "Priority", key: "priority", sort_order: this.ASCEND}, - {label: "Start Date", key: "start_date", sort_order: this.ASCEND}, - {label: "End Date", key: "end_date", sort_order: this.ASCEND}, - {label: "Completed Date", key: "completed_at", sort_order: this.ASCEND}, - {label: "Created Data", key: "created_at", sort_order: this.ASCEND}, - {label: "Last Updated", key: "updated_at", sort_order: this.ASCEND}, - ]; - - get selectedGroup() { - return this.service.getCurrentGroup(); - } - - get phaseLabel() { - return this.phaseService.label; - } - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly tasksApi: TasksApiService, - private readonly socket: Socket, - private readonly labelsApi: TaskLabelsApiService, - private readonly utils: UtilsService, - private readonly phaseService: ProjectPhasesService, - private readonly projectsService: ProjectsService, - public readonly service: TaskListV2Service, - public readonly auth: AuthService - ) { - this.service.onPrioritiesChange$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.priorities = deepClone(this.service.priorities); - this.cdr.markForCheck(); - }); - this.phaseService.onLabelChange - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.cdr.markForCheck(); - }); - } - - ngOnInit() { - void this.getMembers(); - void this.getLabels(); - this.socket.on(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), this.handleAssigneeResponse); - this.socket.on(SocketEvents.TASK_LABELS_CHANGE.toString(), this.handleLabelsChange); - this.socket.on(SocketEvents.CREATE_LABEL.toString(), this.handleLabelsChange); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), this.handleAssigneeResponse); - this.socket.removeListener(SocketEvents.TASK_LABELS_CHANGE.toString(), this.handleLabelsChange); - this.socket.removeListener(SocketEvents.CREATE_LABEL.toString(), this.handleLabelsChange); - } - - private handleAssigneeResponse = () => { - void this.getMembers(); - }; - - private handleLabelsChange = () => { - void this.getLabels(); - }; - - changeGroup(item: IGroupByOption) { - this.service.setCurrentGroup(item); - this.onGroupBy.emit(item); - } - - isGroupByPhase() { - return this.selectedGroup.value === this.service.GROUP_BY_PHASE_VALUE; - } - - isGroupByStatus() { - return this.selectedGroup.value === this.service.GROUP_BY_STATUS_VALUE; - } - - isProjectManager() { - if (this.projectsService.projectOwnerTeamMemberId) return this.auth.getCurrentSession()?.team_member_id === this.projectsService.projectOwnerTeamMemberId; - return false; - } - - sortOrderCls(column: ITaskListSortableColumn) { - return column.sort_order === this.ASCEND - ? "sort-ascending" - : "sort-descending"; - } - - trackById(index: number, item: any) { - return item.id; - } - - private toIdsMap(array: any[]) { - return array.map(s => s.id).join("+"); - } - - async getMembers() { - if (!this.projectId) return; - this.loadingAssignees = true; - try { - const res = await this.tasksApi.getTasksAssignees(this.projectId); - if (res.done) { - this.members = res.body; - } - this.loadingAssignees = false; - } catch (e) { - this.loadingAssignees = false; - } - this.cdr.markForCheck(); - } - - private async getLabels() { - if (!this.projectId) return; - try { - const res = await this.labelsApi.getByProject(this.projectId); - if (res.done) { - this.labels = res.body; - } - } catch (e) { - // ignored - } - } - - protected onSortOrderChange(column: ITaskListSortableColumn) { - column.sort_order = column.sort_order === this.ASCEND ? this.DESCEND : this.ASCEND; - if (column.selected) - this.onSortFilterChange(); - } - - onSortFilterChange() { - const selection = this.sortableColumns.filter(p => p.selected); - this.sortFiltersActive = !!selection.length; - this.sortedColumnsCount = selection.length; - const filter = selection.map(s => `${s.key} ${s.sort_order}`).join(","); - this.onFilterSortBy.emit(filter); - } - - onPriorityFilterChange() { - const selection = this.priorities.filter(p => p.selected); - this.prioritiesFiltersActive = !!selection.length; - this.selectedPrioritiesCount = selection.length; - this.onFilterByPriority.emit(this.toIdsMap(selection)); - } - - onLabelsFilterChange() { - const selection = this.labels.filter(l => l.selected); - this.labelsFiltersActive = !!selection.length; - this.selectedLabelsCount = selection.length; - this.onFilterByLabel.emit(this.toIdsMap(selection)); - this.utils.sortBySelection(this.labels); - } - - onMembersFilterChange() { - const selection = this.members.filter(m => m.selected); - this.membersFiltersActive = !!selection.length; - this.selectedMembersCount = selection.length; - - // check whether selected members count - if (this.selectedMembersCount > 0) { - this.onFilterByMember.emit({ - selection: this.toIdsMap(selection), - is_subtasks_included: true - }); - } else { - this.onFilterByMember.emit({ - selection: this.toIdsMap(selection), - is_subtasks_included: false - }); - } - - this.utils.sortBySelection(this.members); - } - - search() { - if (!this.taskSearch) return; - this.onFilterSearch.emit(encodeURIComponent(this.taskSearch)); - // To close the search dropdown - document.body.click(); - } - - reset() { - if (!this.taskSearch) return; - this.taskSearch = null; - this.onFilterSearch.emit(this.taskSearch); - this.ngZone.runOutsideAngular(() => { - // To close the search dropdown - document.body.click(); - }); - } - - onSearchDropdownVisibleChange(visible: boolean) { - if (visible) { - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - const input = document.querySelector("#task-search-input") as HTMLInputElement; - input?.focus(); - }, DRAWER_ANIMATION_INTERVAL) - }); - } - } - - phaseSettingsClick() { - this.onPhaseSettingsClick?.emit(); - - } - statusSettingsClick() { - this.onStatusSettingsClick?.emit(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-hash-map.service.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-hash-map.service.ts deleted file mode 100644 index 33881d1b..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-hash-map.service.ts +++ /dev/null @@ -1,208 +0,0 @@ -import {Injectable} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {Subject} from "rxjs"; -import {ITaskListGroup} from "./interfaces"; - -@Injectable({ - providedIn: 'root' -}) -export class TaskListHashMapService { - private readonly _selectSbj$ = new Subject(); - private readonly _deselectSbj$ = new Subject(); - private readonly _deselectAllSbj$ = new Subject(); - - /** Map */ - private readonly _groupTaskMap = new Map(); - /** Map */ - private readonly _taskGroupIdsMap = new Map(); - /** Map */ - private readonly _selectedTasksMap = new Map(); - /** Map */ - private readonly _allTasksMap = new Map(); - /** Map(); - - private _selectedCount = 0; - - public get tasks() { - return this._allTasksMap; - } - - public get onSelect$() { - return this._selectSbj$.asObservable(); - } - - public get onDeselect$() { - return this._deselectSbj$.asObservable(); - } - - public get onDeselectAll$() { - return this._deselectAllSbj$.asObservable(); - } - - public reset() { - this._groupTaskMap.clear(); - this._taskGroupIdsMap.clear(); - this._selectedTasksMap.clear(); - this._allTasksMap.clear(); - this._subTasksMap.clear(); - this._selectedCount = 0; - } - - public registerGroup(group: ITaskListGroup) { - for (const task of group.tasks) { - this.add(group.id, task); - } - } - - public add(groupId: string, task: IProjectTask) { - if (!task.id) return; - this.updateGroupTaskMap(groupId, task.id); - this._taskGroupIdsMap.set(task.id, groupId); - this._allTasksMap.set(task.id, task); - - if (task.parent_task_id) { - this.updateSubtasksMap(task.parent_task_id, task) - } - - } - - public addGroupTask(groupId: string, task: IProjectTask) { - if (!task.id) return; - this._taskGroupIdsMap.set(task.id, groupId); - } - - public has(taskId: string) { - return this._allTasksMap.has(taskId); - } - - public remove(task: IProjectTask) { - if (!task.id) return; - this.deselectTask(task); - this._taskGroupIdsMap.get(task.id); - this._allTasksMap.delete(task.id); - } - - private updateGroupTaskMap(groupId: string, taskId: string, selected?: boolean) { - const map = this._groupTaskMap.get(groupId); - if (map) { - if (typeof selected === "boolean") { - map[taskId] = selected; - } else { - delete map[taskId]; - } - - this._groupTaskMap.set(groupId, map); - } else { - this._groupTaskMap.set(groupId, {[taskId]: selected || false}); - } - } - - private updateSubtasksMap(parentTaskId: string, task: IProjectTask, selected?: boolean) { - const subtasks = this._subTasksMap.get(parentTaskId) || []; - const isParentTaskAvailable = subtasks.some((subtask) => subtask.id === task.id); - - // Only push the subtask if the parent task is not available - if (!isParentTaskAvailable) { - subtasks.push(task); - this._subTasksMap.set(parentTaskId, subtasks); - } - - } - - public selectTask(task: IProjectTask) { - if (this._selectedTasksMap.get(task.id as string)) return; - - this._selectedTasksMap.set(task.id as string, task); - this._selectedCount++; - - this.updateGroupTaskMap( - this._taskGroupIdsMap.get(task.id as string) as string, - task.id as string, - true - ); - - this._selectSbj$.next(task); - - } - - public deselectTask(task: IProjectTask) { - if (this._selectedTasksMap.has(task.id as string)) { - this._selectedTasksMap.delete(task.id as string); - this._selectedCount--; - - this.updateGroupTaskMap( - this._taskGroupIdsMap.get(task.id as string) as string, - task.id as string, - false - ); - - this._deselectSbj$.next(task); - } - } - - private deselectLocalGroups() { - for (const [groupId, task] of this._groupTaskMap) { - for (const taskId in task) { - this.updateGroupTaskMap(groupId, taskId, false); - } - } - } - - public deselectAll() { - if (!this._selectedTasksMap.size) return; - - this.deselectLocalGroups(); - this._selectedTasksMap.clear(); - this._selectedCount = 0; - - this._deselectAllSbj$.next(); - } - - public isAllSelected(groupId: string) { - const tasks = this._groupTaskMap.get(groupId); - - if (tasks) { - for (const taskId in tasks) - if (!tasks[taskId]) return false; - return true; - } - - return false; - } - - public isAllDeselected(groupId: string) { - const tasks = this._groupTaskMap.get(groupId); - - if (tasks) { - for (const taskId in tasks) - if (tasks[taskId]) return false; - } - - return true; - } - - public getSelectedCount() { - return this._selectedCount; - } - - public getGroupId(taskId: string) { - return this._taskGroupIdsMap.get(taskId); - } - - public getSelectedTasks() { - const tasks = []; - for (const [, task] of this._selectedTasksMap.entries()) { - tasks.push(task); - } - return tasks; - } - - public getSelectedTaskIds(): string[] { - const tasks = []; - for (const [taskId] of this._selectedTasksMap.entries()) { - tasks.push(taskId); - } - return tasks; - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-add-task-input/task-list-add-task-input.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-add-task-input/task-list-add-task-input.component.html deleted file mode 100644 index 2bd6c591..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-add-task-input/task-list-add-task-input.component.html +++ /dev/null @@ -1,21 +0,0 @@ - -
- -
-
- - {{label}} -
-
diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-add-task-input/task-list-add-task-input.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-add-task-input/task-list-add-task-input.component.scss deleted file mode 100644 index e5663a78..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-add-task-input/task-list-add-task-input.component.scss +++ /dev/null @@ -1,14 +0,0 @@ -.input-icon { - font-size: 11px; -} - -.task-name { - border: 1px solid transparent; - - &:hover { - cursor: text; - background: #fff; - border: 1px solid #bfbfbf; - border-radius: 4px; - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-add-task-input/task-list-add-task-input.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-add-task-input/task-list-add-task-input.component.spec.ts deleted file mode 100644 index 8c8ec343..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-add-task-input/task-list-add-task-input.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskListAddTaskInputComponent} from './task-list-add-task-input.component'; - -describe('TaskListAddTaskInputComponent', () => { - let component: TaskListAddTaskInputComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskListAddTaskInputComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskListAddTaskInputComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-add-task-input/task-list-add-task-input.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-add-task-input/task-list-add-task-input.component.ts deleted file mode 100644 index 00f5b788..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-add-task-input/task-list-add-task-input.component.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - Input, - NgZone, - Output, - ViewChild -} from '@angular/core'; -import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITaskCreateRequest} from "@interfaces/api-models/task-create-request"; -import {AuthService} from "@services/auth.service"; -import {SocketEvents} from "@shared/socket-events"; -import {smallId} from "@shared/utils"; -import {Socket} from "ngx-socket-io"; -import {TaskListV2Service} from "../../task-list-v2.service"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NgIf, NgSwitch, NgSwitchCase} from "@angular/common"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {TaskListHashMapService} from "../../task-list-hash-map.service"; -import {KanbanV2Service} from 'app/administrator/modules/kanban-view-v2/kanban-view-v2.service'; -import {DRAWER_ANIMATION_INTERVAL} from "@shared/constants"; -import {RoadmapV2Service} from "../../../roadmap-v2/project-roadmap-v2-custom/services/roadmap-v2-service.service"; -import { - ProjectScheduleService -} from "../../../../schedule/schedule-v2/projects-schedule/service/project-schedule-service.service"; - -@Component({ - selector: 'worklenz-task-list-add-task-input', - templateUrl: './task-list-add-task-input.component.html', - styleUrls: ['./task-list-add-task-input.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [ - NzIconModule, - NgIf, - ReactiveFormsModule, - NzInputModule, - NgSwitch, - NgSwitchCase - ], - standalone: true -}) -export class TaskListAddTaskInputComponent { - @ViewChild('taskInput', {static: false}) taskInput!: ElementRef; - readonly form!: FormGroup; - - @Input() subTaskInput = false; - @Input() projectId: string | null = null; - @Input() parentTask: string | null = null; - @Input() groupId: string | null = null; - @Input() label = "Add Task"; - - @Output() focusChange: EventEmitter = new EventEmitter(); - - taskInputVisible = false; - creating = false; - - protected readonly id = smallId(4); - - private readonly _session: ILocalSession | null = null; - - constructor( - private readonly socket: Socket, - private readonly auth: AuthService, - private readonly fb: FormBuilder, - private readonly service: TaskListV2Service, - private readonly ngZone: NgZone, - private readonly cdr: ChangeDetectorRef, - private readonly map: TaskListHashMapService, - private readonly kanbanService: KanbanV2Service, - private readonly roadMapService: RoadmapV2Service, - private readonly scheduleService: ProjectScheduleService - ) { - this.form = this.fb.group({ - name: [null, [Validators.required, Validators.pattern(/^(\s+\S+\s*)*(?!\s).*$/)]] - }); - this._session = this.auth.getCurrentSession(); - } - - focusTaskInput() { - this.taskInputVisible = true; - this.focusChange.emit(this.taskInputVisible); - - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.taskInput?.nativeElement.focus(); - this.taskInput?.nativeElement.select(); - }, 100); // wait for the animation end - }); - } - - addTaskInputBlur() { - this.taskInputVisible = false; - this.focusChange.emit(this.taskInputVisible); - } - - async onInputBlur() { - if (this.isValidInput()) { - await this.addInstantTask(); - return; - } - - this.addTaskInputBlur(); - } - - private createRequest() { - if (!this.projectId || !this._session) return null; - - const sess = this._session; - const body: ITaskCreateRequest = { - name: this.form.value.name, - project_id: this.projectId, - reporter_id: sess.id, - team_id: sess.team_id - }; - - const groupBy = this.service.getCurrentGroup(); - - if (groupBy.value === this.service.GROUP_BY_STATUS_VALUE) { - body.status_id = this.groupId || undefined; - } else if (groupBy.value === this.service.GROUP_BY_PRIORITY_VALUE) { - body.priority_id = this.groupId || undefined; - } else if (groupBy.value === this.service.GROUP_BY_PHASE_VALUE) { - body.phase_id = this.groupId || undefined; - } - - if (this.parentTask) { - body.parent_task_id = this.parentTask; - } - return body; - } - - private isValidInput() { - return this.form.valid && this.form.value.name.trim().length; - } - - async addInstantTask() { - if (this.creating) return; - if (!this.projectId || !this._session) return; - - if (this.isValidInput()) { - try { - const req = this.createRequest(); - if (!req) return; - - this.creating = true; - - this.socket.emit(SocketEvents.QUICK_TASK.toString(), JSON.stringify(req)); - this.socket.once(SocketEvents.QUICK_TASK.toString(), (task: IProjectTask) => { - this.creating = false; - if (task.parent_task_id) { - this.service.emitUpdateGroupProgress(task.id as string); - this.kanbanService.emitOnCreateSubTask(task); - this.roadMapService.addSubtaskFromView(task); - // this.roadMapService.addTask(task, this.groupId as string, true) - }; - this.onNewTaskReceived(task); - }); - - } catch (e) { - this.creating = false; - } - - this.cdr.markForCheck(); - } - } - - public reset(scroll = true) { - this.creating = false; - - this.form.controls["name"].setValue(null); - this.taskInputVisible = true; - - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.taskInput?.nativeElement.focus(); - if (scroll) - window.scrollTo(0, document.body.scrollHeight); - }, DRAWER_ANIMATION_INTERVAL); // wait for the animation end - }); - - this.cdr.markForCheck(); - } - - private onNewTaskReceived(task: IProjectTask) { - if (this.groupId && task.id) { - if (this.map.has(task.id)) return; - - this.service.addTask(task, this.groupId); - this.reset(false); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-bulk-actions/task-list-bulk-actions.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-bulk-actions/task-list-bulk-actions.component.html deleted file mode 100644 index 1967e2f6..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-bulk-actions/task-list-bulk-actions.component.html +++ /dev/null @@ -1,214 +0,0 @@ -
- -
-
{{label}}
- -
- - - - -
    -
  • -
      -
    • - -
    • -
    -
  • -
  • -
      -
    • - -
    • -
    -
  • -
  • -
      -
    • - -
    • -
    -
  • -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - Change label -
-

No labels available!

- Labels can be created while updating or creating tasks. -
-
-
- - -
    -
  • - -
  • -
- -
    -
  • -
    - - Hit enter to create! -
    -
  • -
  • - -
  • -
-
- - - - - - - - - - Assign members - - - - -
    -
  • -
    - -
    - {{item.name}} - - {{item.email}} (Pending - Invitation) - -
    -
    -
  • -
- -
    -
  • - -
  • -
-
- - - - -
- - - - -
    -
  • - Create Task Template -
  • -
-
-
- - -
-
diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-bulk-actions/task-list-bulk-actions.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-bulk-actions/task-list-bulk-actions.component.scss deleted file mode 100644 index 27f2f76a..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-bulk-actions/task-list-bulk-actions.component.scss +++ /dev/null @@ -1,63 +0,0 @@ -.bulk-actions { - position: fixed; - z-index: 2; - background: #252628; - bottom: 30px; - left: 0; - right: 0; - min-width: 420px; - width: auto; - max-width: 450px; - margin: auto; - height: 50px; - display: flex; - align-items: center; - border-radius: 25px; - padding: 0 25px; - box-shadow: 0 0 0 1px #edeae9, 0 5px 20px 0 rgba(109, 110, 111, 0.08); - transform: translateY(200%); - transition: .2s ease-in; - - .bulk-actions-inner { - &, button, nz-select { - color: #fff; - } - } - - [nz-icon] { - font-size: 16px; - } - - &.open { - transform: translateY(20%); - } - - .bulk-actions-input-container { - position: absolute; - top: -40px; - width: 100%; - left: 0; - right: 0; - } -} - -.disable { - position: relative; - &:after { - position: absolute; - content: ""; - background: #e7e7e769; - top: 0; - left: 0; - right: 0; - bottom: 0; - width: 100%; - } -} - - -.li-custom *{ - min-height: 32px !important; - height: 32px !important; - padding: 5px 12px; -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-bulk-actions/task-list-bulk-actions.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-bulk-actions/task-list-bulk-actions.component.spec.ts deleted file mode 100644 index 92b55570..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-bulk-actions/task-list-bulk-actions.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskListBulkActionsComponent} from './task-list-bulk-actions.component'; - -describe('TaskListBulkActionsComponent', () => { - let component: TaskListBulkActionsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskListBulkActionsComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskListBulkActionsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-bulk-actions/task-list-bulk-actions.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-bulk-actions/task-list-bulk-actions.component.ts deleted file mode 100644 index d381bef8..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-bulk-actions/task-list-bulk-actions.component.ts +++ /dev/null @@ -1,294 +0,0 @@ -import {ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild} from '@angular/core'; -import {FormBuilder, FormGroup, Validators} from "@angular/forms"; -import {TasksApiService} from "@api/tasks-api.service"; -import {ITaskStatus} from "@interfaces/task-status"; -import {AuthService} from "@services/auth.service"; -import {NzSelectComponent} from "ng-zorro-antd/select"; -import {merge, Subject, takeUntil} from "rxjs"; -import {TaskListHashMapService} from "../../task-list-hash-map.service"; -import {TaskListV2Service} from "../../task-list-v2.service"; -import {log_error} from "@shared/utils"; -import {ITaskPriority} from "@interfaces/task-priority"; -import {ITaskPhase} from "@interfaces/api-models/task-phase"; - -@Component({ - selector: 'worklenz-task-list-bulk-actions', - templateUrl: './task-list-bulk-actions.component.html', - styleUrls: ['./task-list-bulk-actions.component.scss'] -}) -export class TaskListBulkActionsComponent implements OnDestroy { - newLabelForm!: FormGroup; - - @ViewChild("labelsSelect", {static: false}) labelsSelect!: NzSelectComponent; - - statuses: ITaskStatus[] = []; - priorities: ITaskPriority[] = []; - phases: ITaskPhase[] = []; - @Input() selectedStatus: string | null = null; - @Input() filteredAsArchived = false; - - @Output() bulkUpdateSuccess: EventEmitter = new EventEmitter(); - @Output() labelsUpdate: EventEmitter = new EventEmitter(); - @Output() taskTemplateClicked: EventEmitter = new EventEmitter(); - - changingStatus = false - changingPriority = false - changingPhase = false; - changingLabels = false; - deletingTasks = false; - archivingTasks = false; - assigningTasks = false; - assigningLabel = false; - assigningMembers = false; - labelsDropdownVisible = false; - groupChangeVisible = false; - membersDropdownVisible = false; - - projectId: string | null = null; - - selectedCount = 0; - - get deleteConfirmationMessage() { - return 'All ' + this.selectedCount + ' tasks will be deleted and cannot be undone.'; - } - - get newLabel() { - return this.newLabelForm.valid; - } - - get label() { - const w = this.selectedCount < 2 - ? 'task' - : 'tasks'; - return `${this.selectedCount} ${w} selected`; - } - - protected isOpen = false; - - private readonly destroy$ = new Subject(); - - constructor( - private readonly api: TasksApiService, - private readonly fb: FormBuilder, - private readonly auth: AuthService, - private readonly map: TaskListHashMapService, - public readonly service: TaskListV2Service - ) { - this.projectId = this.service.getProjectId(); - - this.newLabelForm = this.fb.group({ - label: [null, Validators.required] - }); - - this.service.onStatusesChange$ - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - // this.statuses = this.service.statuses; - }); - - merge(this.map.onSelect$, this.map.onDeselect$, this.map.onDeselectAll$) - .pipe(takeUntil(this.destroy$)) - .subscribe(async (value) => { - await this.getGroups(); - const count = this.map.getSelectedCount(); - this.isOpen = count > 0; - this.selectedCount = count; - }); - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } - - async getGroups() { - this.statuses = this.service.statuses; - this.priorities = this.service.priorities; - this.phases = this.service.phases; - } - - async changeStatus(selectedStatus: string | undefined) { - if(!selectedStatus) return; - try { - this.changingStatus = true; - const res = await this.api.bulkChangeStatus({ - tasks: this.map.getSelectedTaskIds(), - status_id: selectedStatus as string - }, this.projectId as string); - if (res.done) { - this.bulkUpdateSuccess.emit(); - } - this.changingStatus = false; - } catch (e) { - this.changingStatus = false; - } - } - - async changePriority(selectedPriority: string | undefined) { - if(!selectedPriority) return; - try { - this.changingPriority = true; - const res = await this.api.bulkChangePriority({ - tasks: this.map.getSelectedTaskIds(), - priority_id: selectedPriority as string - }, this.projectId as string); - if (res.done) { - this.bulkUpdateSuccess.emit(); - } - this.changingPriority = false; - } catch (e) { - this.changingPriority = false; - } - } - async changePhase(selectedPhase: string | undefined) { - if(!selectedPhase) return; - try { - this.changingPriority = true; - const res = await this.api.bulkChangePhase({ - tasks: this.map.getSelectedTaskIds(), - phase_id: selectedPhase as string - }, this.projectId as string); - if (res.done) { - this.bulkUpdateSuccess.emit(); - } - this.changingPriority = false; - } catch (e) { - this.changingPriority = false; - } - } - - async bulkDelete() { - try { - this.deletingTasks = true; - const res = await this.api.bulkDelete({tasks: this.map.getSelectedTaskIds()}, this.projectId as string); - if (res.done) { - this.bulkUpdateSuccess.emit(); - } - this.deletingTasks = false; - } catch (e) { - this.deletingTasks = false; - } - } - - async bulkArchive() { - try { - if (this.isSelectionHasSubTasks()) return; - this.archivingTasks = true; - const res = await this.api.bulkArchive({ - tasks: this.map.getSelectedTaskIds(), project_id: this.projectId as string - }, this.filteredAsArchived); - if (res.done) { - this.bulkUpdateSuccess.emit(); - } - this.archivingTasks = false; - } catch (e) { - this.archivingTasks = false; - } - } - - async bulkAssignMe() { - if (!this.projectId) return; - try { - this.assigningTasks = true; - const res = await this.api.bulkAssignMe({ - tasks: this.map.getSelectedTaskIds(), - project_id: this.projectId - }); - if (res.done) { - this.bulkUpdateSuccess.emit(); - } - this.assigningTasks = false; - } catch (e) { - this.assigningTasks = false; - } - } - - async bulkAssignLabel() { - try { - this.assigningLabel = true; - const res = await this.api.bulkAssignLabel({ - tasks: this.map.getSelectedTaskIds(), - text: this.newLabelForm.value.label || null, - labels: this.service.labels.filter(l => l.selected) - }, this.projectId as string); - if (res.done) { - this.bulkUpdateSuccess?.emit(); - this.labelsUpdate?.emit(); - this.resetLabels(); - } - this.assigningLabel = false; - } catch (e) { - this.assigningLabel = false; - } - } - - async bulkAssignMembers() { - try { - this.assigningMembers = true; - const res = await this.api.bulkAssignMembers({ - tasks: this.map.getSelectedTaskIds(), - project_id: this.projectId as string, - members: this.service.members.filter(m => m.selected) - }); - if (res.done) { - this.bulkUpdateSuccess?.emit(); - // this.labelsUpdate?.emit(); - this.resetMembers(); - } - this.assigningMembers = false; - } catch (e) { - this.assigningMembers = false; - log_error(e) - } - } - - isSelectionHasSubTasks() { - // return this.map.isSelectionHasSubTasks(); - // TODO - return 0; - } - - close() { - this.map.deselectAll(); - } - - private deselectAll() { - this.labelsDropdownVisible = false; - this.service.labels.forEach(l => l.selected = false); - } - - private deselectAllMembers() { - this.membersDropdownVisible = false; - this.service.members.forEach(l => l.selected = false); - } - - private resetLabels() { - this.deselectAll(); - this.newLabelForm.controls["label"].setValue(null); - } - - private resetMembers() { - this.deselectAllMembers(); - } - - handleLabelsDropdown(visible: boolean) { - if (!visible) { - this.resetLabels(); - } - } - - handleMembersDropdown(visible: boolean) { - if (!visible) { - this.resetMembers(); - } - } - - createLabel() { - if (this.newLabelForm.valid) - this.bulkAssignLabel(); - } - - isOwnerOrAdmin() { - return this.auth.getCurrentSession()?.owner || this.auth.getCurrentSession()?.is_admin; - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/interfaces/convert-subtask-request.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/interfaces/convert-subtask-request.ts deleted file mode 100644 index 7c88e726..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/interfaces/convert-subtask-request.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; - -export interface ISubtaskConvertRequest { - selectedTask: IProjectTask; - projectId: string; -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/subtask-convert-service.service.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/subtask-convert-service.service.ts deleted file mode 100644 index 4a863599..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/subtask-convert-service.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {Injectable} from '@angular/core'; -import {Subject} from "rxjs"; -import {ISubtaskConvertRequest} from './interfaces/convert-subtask-request'; - - -@Injectable({ - providedIn: 'root' -}) -export class SubtaskConvertService { - private readonly convertSbj$ = new Subject(); - - get onConvertingSubtask() { - return this.convertSbj$.asObservable(); - } - - emitConvertingToSubTask(data: ISubtaskConvertRequest) { - this.convertSbj$.next(data); - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/task-list-context-menu.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/task-list-context-menu.component.html deleted file mode 100644 index 7b0d31a4..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/task-list-context-menu.component.html +++ /dev/null @@ -1,56 +0,0 @@ - -
    - -
  • - - Assign to me -
  • - - - -
  • -
      -
    • - - - {{item?.name || null}} - -
    • -
    -
  • -
  • - - {{archived ? ' Unarchived' : ' Archive'}} -
  • -
  • - - Convert to a Task -
  • - - - -
  • - - Convert to Sub task -
  • -
    -
    - -
    - -
  • - - Delete -
  • -
-
- - - Move to - - - diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/task-list-context-menu.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/task-list-context-menu.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/task-list-context-menu.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/task-list-context-menu.component.spec.ts deleted file mode 100644 index dc6bc71b..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/task-list-context-menu.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskListContextMenuComponent} from './task-list-context-menu.component'; - -describe('TaskListContextMenuComponent', () => { - let component: TaskListContextMenuComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskListContextMenuComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskListContextMenuComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/task-list-context-menu.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/task-list-context-menu.component.ts deleted file mode 100644 index 86c658db..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-context-menu/task-list-context-menu.component.ts +++ /dev/null @@ -1,225 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, ViewChild} from '@angular/core'; -import {TasksApiService} from "@api/tasks-api.service"; -import {IBulkAssignRequest} from "@interfaces/api-models/bulk-assign-request"; -import {NzContextMenuService, NzDropdownMenuComponent} from "ng-zorro-antd/dropdown"; -import {Subject, takeUntil} from "rxjs"; -import {ITaskListContextMenuEvent, ITaskListGroup} from "../../interfaces"; -import {TaskListHashMapService} from "../../task-list-hash-map.service"; -import {TaskListV2Service} from "../../task-list-v2.service"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {SubtaskConvertService} from './subtask-convert-service.service'; -import {ISubtaskConvertRequest} from './interfaces/convert-subtask-request'; -import {KanbanV2Service} from 'app/administrator/modules/kanban-view-v2/kanban-view-v2.service'; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-task-list-context-menu', - templateUrl: './task-list-context-menu.component.html', - styleUrls: ['./task-list-context-menu.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListContextMenuComponent implements OnDestroy { - @ViewChild('contextMenuDropdown', {static: false}) contextMenuDropdown!: NzDropdownMenuComponent; - - @Input() archived = false; - @Input() projectId: string | null = null; - @Input() groups: ITaskListGroup[] = []; - - protected deleting = false; - protected archiving = false; - protected converting = false; - protected assigning = false; - protected hasSubTasks = false; - - selectedTask: IProjectTask | null = null; - - private readonly destroy$ = new Subject(); - - constructor( - private readonly contextMenuService: NzContextMenuService, - private readonly service: TaskListV2Service, - private readonly map: TaskListHashMapService, - private readonly api: TasksApiService, - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly subTaskConvertService: SubtaskConvertService, - private readonly kanbanService: KanbanV2Service, - private readonly auth: AuthService, - ) { - this.service.onContextMenu$ - .pipe(takeUntil(this.destroy$)) - .subscribe(value => { - this.onContextMenu(value); - }); - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } - - private onContextMenu(value: ITaskListContextMenuEvent) { - this.selectedTask = value.task; - - this.map.deselectAll(); - this.map.selectTask(value.task); - - this.hasSubTasks = this.isSelectionHasSubTasks(); - - this.cdr.detectChanges(); - - this.contextMenuService.create(value.event, this.contextMenuDropdown); - } - - async assignMe() { - if (this.assigning) return; - - const projectId = this.service.getProjectId(); - if (!projectId) return; - - try { - this.assigning = true; - - const body: IBulkAssignRequest = { - tasks: this.map.getSelectedTaskIds(), - project_id: projectId - }; - const res = await this.api.bulkAssignMe(body); - - if (res.done) { - this.service.emitOnAssignMe(res.body); - this.map.deselectAll(); - } - this.assigning = false; - } catch (e) { - this.assigning = false; - } - - this.kanbanService.emitRefreshGroups(); - this.cdr.detectChanges(); - } - - private isSelectionHasSubTasks() { - return this.map.getSelectedTasks().some(t => t.is_sub_task); - } - - changeGroup(toGroupId: string) { - if (!this.selectedTask) return; - const groupBy = this.service.getCurrentGroup(); - if (groupBy.value === this.service.GROUP_BY_STATUS_VALUE) { - this.handleStatusChange(toGroupId, this.selectedTask.id); - } else if (groupBy.value === this.service.GROUP_BY_PRIORITY_VALUE) { - this.handlePriorityChange(toGroupId, this.selectedTask.id); - } else if (groupBy.value === this.service.GROUP_BY_PHASE_VALUE) { - this.handlePhaseChange(toGroupId, this.selectedTask.id); - } - } - - handleStatusChange(statusId: string, taskId?: string) { - if (!taskId) return; - this.socket.emit(SocketEvents.TASK_STATUS_CHANGE.toString(), JSON.stringify({ - task_id: taskId, - status_id: statusId, - team_id: this.auth.getCurrentSession()?.team_id - })); - this.socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), taskId); - this.kanbanService.emitRefreshGroups(); - } - - handlePriorityChange(priorityId: string, taskId?: string) { - if (!taskId) return; - this.socket.emit(SocketEvents.TASK_PRIORITY_CHANGE.toString(), JSON.stringify({ - task_id: taskId, - priority_id: priorityId - })); - this.kanbanService.emitRefreshGroups(); - } - - handlePhaseChange(phaseId: string, taskId?: string) { - if (!taskId) return; - this.socket.emit(SocketEvents.TASK_PHASE_CHANGE.toString(), { - task_id: taskId, - phase_id: phaseId - }); - this.kanbanService.emitRefreshGroups(); - } - - async convertToTask() { - const selectedTask = this.selectedTask; - if (!selectedTask) return; - - try { - this.converting = true; - const res = await this.api.convertToTask( - selectedTask.id as string, - selectedTask.project_id as string - ); - - if (res.done) { - const task = res.body; - this.service.updateTaskGroup(task, false); - if (this.service.getCurrentGroup().value === this.service.GROUP_BY_PHASE_VALUE) - this.service.emitRefresh(); - } - - this.converting = false; - } catch (e) { - this.converting = false; - } - } - - async archive() { - if (this.archiving) return; - if (this.hasSubTasks) return; - try { - this.archiving = true; - const body = { - tasks: this.map.getSelectedTaskIds(), - project_id: this.projectId as string - }; - const res = await this.api.bulkArchive(body, this.archived); - - if (res.done) { - for (const taskId of res.body) { - this.service.deleteTask(taskId); - } - } - - this.archiving = false; - } catch (e) { - this.archiving = false; - } - this.kanbanService.emitRefreshGroups(); - this.cdr.detectChanges(); - } - - async delete() { - if (this.deleting) return; - try { - this.deleting = true; - const tasks = this.map.getSelectedTaskIds(); - const res = await this.api.bulkDelete({tasks}, this.projectId as string); - if (res.done) { - for (const taskId of res.body.deleted_tasks) { - this.service.deleteTask(taskId); - } - } - this.deleting = false; - } catch (e) { - this.deleting = false; - } - this.kanbanService.emitRefreshGroups(); - } - - showTasksModal() { - if (this.selectedTask) { - const subtaskConvertRequest: ISubtaskConvertRequest = { - selectedTask: this.selectedTask, - projectId: this.projectId as string - } - this.subTaskConvertService.emitConvertingToSubTask(subtaskConvertRequest); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-group-settings/task-list-group-settings.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-group-settings/task-list-group-settings.component.html deleted file mode 100644 index cf256670..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-group-settings/task-list-group-settings.component.html +++ /dev/null @@ -1,76 +0,0 @@ -
- - - - - - - -
- - - -
- -
- -
- - -
- - -
    -
  • - - Rename -
  • -
  • -
      -
    • - - - - - {{item?.name || null}} - - -
    • -
    -
  • -
-
- - - Change category - diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-group-settings/task-list-group-settings.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-group-settings/task-list-group-settings.component.scss deleted file mode 100644 index 59e37ca6..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-group-settings/task-list-group-settings.component.scss +++ /dev/null @@ -1,57 +0,0 @@ -.collapse { - - &.btn .collapse-icon { - transition: transform 0.1s; - transform: rotate(0deg); - } - - &.btn.active .collapse-icon { - transition: transform 0.1s; - transform: rotate(90deg); - } - - color: hwb(0 0% 100% / 0.85); - font-weight: 500; - padding: 6px 13px 6px 12px; - min-width: 120px; - width: auto; - border: none; - text-align: left; - outline: none; - border-top-right-radius: 4px; - border-top-left-radius: 4px; - -webkit-user-select: none; - user-select: none; - z-index: 8; - font-size: 14px; - height: 30px; - display: flex; - align-items: center; - - &.active { - border-bottom-left-radius: 0px; - border-bottom-right-radius: 0px; - } - - &:after { - color: #777; - font-weight: bold; - float: left; - margin-left: 5px; - } -} - -.task-drag-handler { - position: absolute; - left: -20px; - top: 1px; - bottom: 0; - display: flex; - align-items: center; - height: 27px; - cursor: grab; - - &:active { - cursor: grabbing; - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-group-settings/task-list-group-settings.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-group-settings/task-list-group-settings.component.spec.ts deleted file mode 100644 index 73a9ae85..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-group-settings/task-list-group-settings.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskListGroupSettingsComponent} from './task-list-group-settings.component'; - -describe('TaskListGroupSettingsComponent', () => { - let component: TaskListGroupSettingsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskListGroupSettingsComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskListGroupSettingsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-group-settings/task-list-group-settings.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-group-settings/task-list-group-settings.component.ts deleted file mode 100644 index e436336e..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-group-settings/task-list-group-settings.component.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - NgZone, - OnInit, - Output -} from '@angular/core'; -import {ITaskListGroup} from "../../interfaces"; -import {ITaskStatusCategory} from "@interfaces/task-status-category"; -import {calculateTaskCompleteRatio, log_error} from "@shared/utils"; -import {TaskStatusesApiService} from "@api/task-statuses-api.service"; -import {ALPHA_CHANNEL, UNMAPPED} from "@shared/constants"; -import {TaskListV2Service} from "../../task-list-v2.service"; -import {ITaskStatusUpdateModel} from "@interfaces/api-models/task-status-update-model"; -import {AuthService} from "@services/auth.service"; -import {merge} from "rxjs"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {ITaskPhase} from "@interfaces/api-models/task-phase"; -import {TaskPhasesApiService} from "@api/task-phases-api.service"; -import {ProjectsService} from "../../../../projects/projects.service"; - -@Component({ - selector: 'worklenz-task-list-group-settings', - templateUrl: './task-list-group-settings.component.html', - styleUrls: ['./task-list-group-settings.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListGroupSettingsComponent implements OnInit { - @Input() group!: ITaskListGroup; - @Input() projectId: string | null = null; - @Input() categories: ITaskStatusCategory[] = []; - @Output() toggle = new EventEmitter(); - - protected edit = false; - protected isEditColProgress = false; - protected showMenu = false; - protected isGroupByStatus = false; - protected isGroupByPhases = false; - protected isAdmin = false; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly auth: AuthService, - private readonly statusApi: TaskStatusesApiService, - private readonly list: TaskListV2Service, - private readonly ngZone: NgZone, - private readonly phaseApi: TaskPhasesApiService, - private readonly projectsService: ProjectsService - ) { - merge( - this.list.onGroupProgressChangeDone$, - this.list.onGroupChange$, - this.list.onTaskAddOrDelete$ - ) - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.handleGroupProgressChange(); - }); - } - - ngOnInit() { - this.isGroupByStatus = this.list.getCurrentGroup().value === this.list.GROUP_BY_STATUS_VALUE; - this.isGroupByPhases = this.list.getCurrentGroup().value === this.list.GROUP_BY_PHASE_VALUE; - const session = this.auth.getCurrentSession(); - if (session) - this.isAdmin = !!(session.owner || session.is_admin); - } - - canDisplayActions() { - const currentGroup = this.list.getCurrentGroup().value; - if (currentGroup === this.list.GROUP_BY_PRIORITY_VALUE) return false; - return (this.isAdmin || this.isGroupByStatus || currentGroup === this.list.GROUP_BY_PHASE_VALUE) && this.group.name !== UNMAPPED; - } - - isProgressBarAvailable() { - return !this.isGroupByStatus; - } - - private handleGroupProgressChange = () => { - const group = this.group; - if (!group) return; - - const todoCount = group.tasks.filter(t => t.status_category?.is_todo).length; - const doingCount = group.tasks.filter(t => t.status_category?.is_doing).length; - const doneCount = group.tasks.filter(t => t.status_category?.is_done).length; - - const total = group.tasks.length; - - group.todo_progress = calculateTaskCompleteRatio(todoCount, total); - group.doing_progress = calculateTaskCompleteRatio(doingCount, total); - group.done_progress = calculateTaskCompleteRatio(doneCount, total); - this.cdr.markForCheck(); - } - - protected async changeStatusCategory(group: ITaskListGroup, categoryId?: string) { - if (!categoryId) return; - group.category_id = categoryId; - await this.onBlurEditColumn(group); - this.list.emitRefresh(); - } - - protected editGroupName() { - this.edit = true; - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - const selector = `#group-name-${this.group.id}`; - const input = document.querySelector(selector) as HTMLInputElement; - if (input) { - input.focus(); - input.select(); - } - }); - }); - } - - protected onToggleClick($event: MouseEvent) { - if (this.edit) return; - this.toggle.emit($event); - } - - protected async onBlurEditColumn(group: ITaskListGroup) { - if (!this.projectId || this.isEditColProgress) return; - - try { - this.isEditColProgress = true; - const groupBy = this.list.getCurrentGroup().value; - if (groupBy === this.list.GROUP_BY_STATUS_VALUE) { - await this.update(group); - } else if (groupBy === this.list.GROUP_BY_PHASE_VALUE) { - await this.updatePhase(group); - } - this.isEditColProgress = false; - this.edit = false; - } catch (e) { - log_error(e); - this.isEditColProgress = false; - } - - this.cdr.markForCheck(); - } - - protected async updateName(group: ITaskListGroup) { - if (!this.projectId || this.isEditColProgress) return; - - try { - this.isEditColProgress = true; - const groupBy = this.list.getCurrentGroup().value; - if (groupBy === this.list.GROUP_BY_STATUS_VALUE) { - await this.updateGroupName(group); - } else if (groupBy === this.list.GROUP_BY_PHASE_VALUE) { - await this.updatePhase(group); - } - this.isEditColProgress = false; - this.edit = false; - } catch (e) { - log_error(e); - this.isEditColProgress = false; - } - - this.cdr.markForCheck(); - } - - private async updateGroupName(group: ITaskListGroup) { - if (!group?.id || !this.projectId) return; - try { - const body = { - name: group.name, - project_id: this.projectId, - category_id: group.category_id - }; - const res = await this.statusApi.updateName(group.id, body, this.projectId as string); - if (res.done) { - const groups = this.list.groups; - const group = groups.find(p => p.id === res.body.id); - if (group) { - this.group.name = group.name = res.body.name || ''; - this.group.color_code = group.color_code = res.body.color_code || ''; - } - this.list.groups = groups; - } - } catch (e) { - // ignored - } - - this.cdr.markForCheck(); - } - - private async updatePhase(group: ITaskListGroup) { - if (!group?.id || !this.projectId) return; - if (!this.isAdmin && !this.isProjectManager()) return; - try { - const body = { - id: group.id, - name: group.name - }; - const res = await this.phaseApi.update(this.projectId, body as ITaskPhase); - if (res.done) { - const phases = this.list.phases; - const phase = phases.find(p => p.id === res.body.id); - if (phase) { - this.group.name = phase.name = res.body.name; - } - this.list.phases = phases; - } - } catch (e) { - // ignored - } - - this.cdr.markForCheck(); - } - - isProjectManager() { - if (this.projectsService.projectOwnerTeamMemberId) return this.auth.getCurrentSession()?.team_member_id === this.projectsService.projectOwnerTeamMemberId; - return false; - } - - private async update(group: ITaskListGroup) { - if (!this.isAdmin && !this.isProjectManager()) return; - const body: ITaskStatusUpdateModel = { - name: group.name, - project_id: this.projectId as string, - category_id: group.category_id - }; - const res = await this.statusApi.update(group.id, body, this.projectId as string); - if (res.done) { - if (res.body.color_code != null) { - group.color_code = res.body.color_code + ALPHA_CHANNEL; - } - } - } - - -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-header/task-list-header.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-header/task-list-header.component.html deleted file mode 100644 index 7d82fe0d..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-header/task-list-header.component.html +++ /dev/null @@ -1,100 +0,0 @@ - -
- - -
- -
- - - -
Key
-
- - -
- - -
Task
- - - -
Description
-
- - - -
Progress
-
- - - -
Members
-
- - - -
Labels
-
- - - -
- {{phaseLabel | ellipsis : 10}} - -
-
- - - -
Status
-
- - - -
Priority
-
- - - -
Time Tracking
-
- - - -
Estimation
-
- - - -
Start Date
-
- - - -
Due Date
-
- - - -
Completed Date
-
- - - -
Created Date
-
- - - -
Last Updated
-
- - - -
Reporter
-
diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-header/task-list-header.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-header/task-list-header.component.scss deleted file mode 100644 index bfe58f22..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-header/task-list-header.component.scss +++ /dev/null @@ -1,100 +0,0 @@ -.flex-row { - padding: 4px 11px; - background-color: #fafafa; - border-top: 1px solid #f0f0f0; - border-bottom: 1px solid #f0f0f0; - border-right: 1px solid #f0f0f0; - display: flex; - align-items: center; - flex-direction: row; -} - -.task-drag-handler { - padding: 0 0 0 4px !important; - width: 24px; - border-bottom: 1px solid #f0f0f0; - border-right: none !important; - position: sticky; - left: 0; - z-index: 1; -} - -.task-check { - text-align: center; - padding: 8px 6px 8px 0px !important; - position: sticky; - left: 24px; - z-index: 1; -} - -.task-key { - width: 85px; -} - -.task-arrow { - width: 24px; - padding: 0px !important; - display: flex; - align-items: center; - position: sticky; - border-right: 0; - left: 47px; - z-index: 1; -} - -.task-name { - width: 450px; - min-width: 450px; - position: sticky; - left: 71px; - z-index: 1; - - nz-filter-trigger { - margin-left: auto; - } - - &.left-0 { - left: 47px; - } - -} - -.task-description { - width: 225px; -} - -.task-progress { - width: 80px; -} - -.task-members { - width: 160px; -} - -.task-labels { - width: 220px; -} - -.task-status { - width: 120px; -} - -.task-phase { - width: 150px; -} - -.task-priority { - width: 120px; -} - -.task-time-tracking { - width: 120px; -} - -.task-estimation { - width: 120px; -} - -.task-start-date, .task-due-date, .task-completed-date, .task-created-date, .task-update-date { - width: 150px; -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-header/task-list-header.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-header/task-list-header.component.spec.ts deleted file mode 100644 index aa449200..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-header/task-list-header.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskListHeaderComponent} from './task-list-header.component'; - -describe('TaskListHeaderComponent', () => { - let component: TaskListHeaderComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskListHeaderComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskListHeaderComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-header/task-list-header.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-header/task-list-header.component.ts deleted file mode 100644 index 1da28e3e..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-header/task-list-header.component.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - HostBinding, - Input, - OnInit, - Output -} from '@angular/core'; -import {merge} from "rxjs"; - -import {TaskListV2Service} from "../../task-list-v2.service"; -import {TaskListHashMapService} from "../../task-list-hash-map.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {ProjectPhasesService} from "@services/project-phases.service"; -import {AuthService} from "@services/auth.service"; -import {ProjectsService} from "../../../../projects/projects.service"; - -@Component({ - selector: 'worklenz-task-list-header', - templateUrl: './task-list-header.component.html', - styleUrls: ['./task-list-header.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListHeaderComponent implements OnInit { - @HostBinding("class") headerCls = "flex-table header"; - - @Output() selectChange = new EventEmitter(); - @Output() phaseSettingsClick = new EventEmitter(); - - @Input() groupId!: string; - - checked = false; - indeterminate = false; - - keyActive = false; - descriptionActive = false; - progressActive = false; - assigneesActive = false; - labelsActive = false; - statusActive = false; - priorityActive = false; - timeTrackingActive = false; - estimationActive = false; - startDateActive = false; - dueDateActive = false; - completedDateActive = false; - createdDateActive = false; - lastUpdatedActive = false; - reporterActive = false; - phaseActive = false; - - get phaseLabel() { - return this.phasesService.label; - } - - constructor( - public readonly service: TaskListV2Service, - private readonly map: TaskListHashMapService, - private readonly cdr: ChangeDetectorRef, - private readonly phasesService: ProjectPhasesService, - public readonly auth: AuthService, - private readonly projectsService: ProjectsService - ) { - this.service.onColumnsChange$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.cdr.markForCheck(); - this.updateState(); - }); - - this.map.onDeselectAll$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.checked = false; - this.indeterminate = false; - this.cdr.markForCheck(); - }); - - this.phasesService.onLabelChange - .pipe(takeUntilDestroyed()) - .subscribe(value => { - this.cdr.markForCheck(); - }); - - merge(this.map.onSelect$, this.map.onDeselect$) - .pipe(takeUntilDestroyed()) - .subscribe(() => { - if (this.map.isAllDeselected(this.groupId)) { - this.checked = false; - this.indeterminate = false; - } else if (this.map.isAllSelected(this.groupId)) { - this.checked = true; - this.indeterminate = false; - } else { - this.indeterminate = true; - } - - this.cdr.markForCheck(); - }); - } - - ngOnInit() { - this.updateState(); - } - - isProjectManager() { - if (this.projectsService.projectOwnerTeamMemberId) return this.auth.getCurrentSession()?.team_member_id === this.projectsService.projectOwnerTeamMemberId; - return false; - } - - private updateState() { - this.keyActive = this.active(this.service.COLUMN_KEYS.KEY); - this.descriptionActive = this.active(this.service.COLUMN_KEYS.DESCRIPTION); - this.progressActive = this.active(this.service.COLUMN_KEYS.PROGRESS); - this.assigneesActive = this.active(this.service.COLUMN_KEYS.ASSIGNEES); - this.labelsActive = this.active(this.service.COLUMN_KEYS.LABELS); - this.statusActive = this.active(this.service.COLUMN_KEYS.STATUS); - this.priorityActive = this.active(this.service.COLUMN_KEYS.PRIORITY); - this.timeTrackingActive = this.active(this.service.COLUMN_KEYS.TIME_TRACKING); - this.estimationActive = this.active(this.service.COLUMN_KEYS.ESTIMATION); - this.startDateActive = this.active(this.service.COLUMN_KEYS.START_DATE); - this.dueDateActive = this.active(this.service.COLUMN_KEYS.DUE_DATE); - this.completedDateActive = this.active(this.service.COLUMN_KEYS.COMPLETED_DATE); - this.createdDateActive = this.active(this.service.COLUMN_KEYS.CREATED_DATE); - this.lastUpdatedActive = this.active(this.service.COLUMN_KEYS.LAST_UPDATED); - this.reporterActive = this.active(this.service.COLUMN_KEYS.REPORTER); - this.phaseActive = this.active(this.service.COLUMN_KEYS.PHASE); - } - - private active(key: string) { - return this.service.canActive(key); - } - - onAllChecked(checked: boolean) { - this.selectChange?.emit(checked); - } - - protected onPhaseSettingsClick() { - this.phaseSettingsClick.emit(); - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-duration/task-list-phase-duration.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-duration/task-list-phase-duration.component.html deleted file mode 100644 index 352ef55f..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-duration/task-list-phase-duration.component.html +++ /dev/null @@ -1,25 +0,0 @@ - -
- -
-
-
-
- -
-
diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-duration/task-list-phase-duration.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-duration/task-list-phase-duration.component.scss deleted file mode 100644 index f39d29dd..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-duration/task-list-phase-duration.component.scss +++ /dev/null @@ -1,4 +0,0 @@ -nz-date-picker { - max-width: 125px; - max-height: 26px; -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-duration/task-list-phase-duration.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-duration/task-list-phase-duration.component.spec.ts deleted file mode 100644 index 108a2f3e..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-duration/task-list-phase-duration.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskListPhaseDurationComponent } from './task-list-phase-duration.component'; - -describe('TaskListPhaseDuratinoComponent', () => { - let component: TaskListPhaseDurationComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskListPhaseDurationComponent] - }); - fixture = TestBed.createComponent(TaskListPhaseDurationComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-duration/task-list-phase-duration.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-duration/task-list-phase-duration.component.ts deleted file mode 100644 index bf3059c3..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-duration/task-list-phase-duration.component.ts +++ /dev/null @@ -1,65 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {ITaskListGroup} from "../../interfaces"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; - -@Component({ - selector: 'worklenz-task-list-phase-duration', - templateUrl: './task-list-phase-duration.component.html', - styleUrls: ['./task-list-phase-duration.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListPhaseDurationComponent implements OnInit, OnDestroy{ - @Input() group!: ITaskListGroup; - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef - ) {} - - ngOnInit() { - this.socket.on(SocketEvents.PHASE_START_DATE_CHANGE.toString(), this.handleStartDateChangeResponse); - this.socket.on(SocketEvents.PHASE_END_DATE_CHANGE.toString(), this.handleEndDateChangeResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.PHASE_START_DATE_CHANGE.toString(), this.handleStartDateChangeResponse); - this.socket.removeListener(SocketEvents.PHASE_END_DATE_CHANGE.toString(), this.handleEndDateChangeResponse); - } - - private handleStartDateChangeResponse = (response: { - phase_id: string; - start_date: string; - }) => { - if (response.phase_id === this.group.id && this.group.start_date !== response.start_date) { - this.group.start_date = response.start_date; - this.cdr.markForCheck(); - } - }; - - private handleEndDateChangeResponse = (response: { - phase_id: string; - end_date: string; - }) => { - if (response.phase_id === this.group.id && this.group.end_date !== response.end_date) { - this.group.end_date = response.end_date; - this.cdr.markForCheck(); - } - }; - - handleStartDateChange(date: string) { - this.socket.emit( - SocketEvents.PHASE_START_DATE_CHANGE.toString(), JSON.stringify({ - phase_id: this.group.id, - start_date: date || null, - })); - } - - handleEndDateChange(date: string) { - this.socket.emit( - SocketEvents.PHASE_END_DATE_CHANGE.toString(), JSON.stringify({ - phase_id: this.group.id, - end_date: date || null, - })); - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-settings-drawer/task-list-phase-settings-drawer.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-settings-drawer/task-list-phase-settings-drawer.component.html deleted file mode 100644 index ed098528..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-settings-drawer/task-list-phase-settings-drawer.component.html +++ /dev/null @@ -1,97 +0,0 @@ - - - -
- - Phase Label: - - - - -
- - - -
-
-
- - Phase Options - -
-
- -
-
-
- -
- -
-
- - -
-
-
- -
- -
- -   - - -
    -
  • -   - -
  • -
-
-
-
- - - -
-
-
-
-
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-settings-drawer/task-list-phase-settings-drawer.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-settings-drawer/task-list-phase-settings-drawer.component.scss deleted file mode 100644 index 165d7f65..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-settings-drawer/task-list-phase-settings-drawer.component.scss +++ /dev/null @@ -1,63 +0,0 @@ - - -.drag-handle { - cursor: grab; - opacity: 0.8; - margin-right: 4px; - height: 100%; - display: flex; - align-items: center; -} - -.drag-list { - position: relative; -} - -.loading-section { - position: absolute; - background: rgba(255, 255, 255, 0.65); - width: 100%; - height: 100%; - align-items: center; - display: flex; - z-index: 9; - justify-content: center; -} - -.drag-list { - display: block; - overflow: hidden; -} - -.drag-box { - box-sizing: border-box; - width: 100%; -} - -.cdk-drag-preview { - box-sizing: border-box; - border-radius: 4px; - box-shadow: 0 5px 5px -3px rgb(255 255 255), 0 8px 10px 1px rgb(238 238 238 / 59%), 0 3px 14px 2px rgb(255 255 255); - z-index: 999999 !important; - background-color: white !important; - min-height: 40px !important; - display: flex !important; - align-items: center !important; - padding-left: 4px; -} - -.cdk-drag-placeholder { - opacity: 1; -} - -.cdk-drag-animating { - transition: transform 150ms cubic-bezier(0, 0, 0.2, 1); -} - -.drag-drag:last-child { - border: none; -} - -.drag-list.cdk-drop-list-dragging .drag-drag:not(.cdk-drag-placeholder) { - transition: transform 150ms cubic-bezier(0, 0, 0.2, 1); -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-settings-drawer/task-list-phase-settings-drawer.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-settings-drawer/task-list-phase-settings-drawer.component.spec.ts deleted file mode 100644 index eee61d88..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-settings-drawer/task-list-phase-settings-drawer.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskListPhaseSettingsDrawerComponent} from './task-list-phase-settings-drawer.component'; - -describe('TaskListPhaseSettingsDrawerComponent', () => { - let component: TaskListPhaseSettingsDrawerComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskListPhaseSettingsDrawerComponent] - }); - fixture = TestBed.createComponent(TaskListPhaseSettingsDrawerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-settings-drawer/task-list-phase-settings-drawer.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-settings-drawer/task-list-phase-settings-drawer.component.ts deleted file mode 100644 index c3b653dc..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-phase-settings-drawer/task-list-phase-settings-drawer.component.ts +++ /dev/null @@ -1,256 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, ElementRef, - EventEmitter, - Input, - Output, QueryList, - ViewChildren -} from '@angular/core'; -import {TaskPhasesApiService} from "@api/task-phases-api.service"; -import {ITaskPhase} from "@interfaces/api-models/task-phase"; -import {TaskListV2Service} from "../../task-list-v2.service"; -import {ProjectPhasesService} from "@services/project-phases.service"; -import {ProjectsService} from "../../../../projects/projects.service"; -import {AuthService} from "@services/auth.service"; -import {PhaseColorCodes} from "@shared/constants"; -import {CdkDragDrop, moveItemInArray} from '@angular/cdk/drag-drop'; -import {deepClone, log_error} from "@shared/utils"; - -@Component({ - selector: 'worklenz-task-list-phase-settings-drawer', - templateUrl: './task-list-phase-settings-drawer.component.html', - styleUrls: ['./task-list-phase-settings-drawer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListPhaseSettingsDrawerComponent { - @ViewChildren('input') inputs: QueryList | undefined; - - @Input() projectId: string | null = null; - - @Input() show = false; - @Output() showChange = new EventEmitter(); - @Output() getGroups = new EventEmitter(); - - loading = false; - creating = false; - updatingLabel = false; - sorting = false; - - updating: { [x: string]: boolean } = {}; - deleting: { [x: string]: boolean } = {}; - updateCache: { [x: string]: string } = {}; - - readonly COLOR_CODES = PhaseColorCodes; - - oldLabel: string | null = null; - phaseLabel: string | null = null; - phasesList:ITaskPhase[] = [] - - get options() { - return this.list.phases; - } - - constructor( - private readonly api: TaskPhasesApiService, - private readonly cdr: ChangeDetectorRef, - public readonly list: TaskListV2Service, - private readonly service: ProjectPhasesService, - private readonly projectsService: ProjectsService, - public readonly auth: AuthService - ) { - } - - close() { - this.show = false; - this.showChange.emit(false); - } - - addNewOption() { - void this.create(); - } - - onVisibleChange(visible: boolean) { - if (visible) { - void this.get(true); - this.phaseLabel = this.service.label; - } - } - - removeOption(id: string) { - if (!id) return; - void this.delete(id); - } - - async updateOption(phase: ITaskPhase) { - await this.update(phase); - delete this.updateCache[phase.id]; - } - - setNameCache(id: string, name: string) { - this.updateCache[id] = name; - } - - isProjectManager() { - if (this.projectsService.projectOwnerTeamMemberId) return this.auth.getCurrentSession()?.team_member_id === this.projectsService.projectOwnerTeamMemberId; - return false; - } - - private async create() { - if (!this.projectId || this.creating) return; - try { - this.creating = true; - - const res = await this.api.create(this.projectId, this.isProjectManager()); - if (res.done) { - await this.get(false); - this.service.emitOptionsChange(); - this.focusNewElement(); - } - this.creating = false; - } catch (e) { - this.creating = false; - } - - this.cdr.markForCheck(); - } - - focusNewElement() { - setTimeout(() => { - this.inputs?.first.nativeElement.focus() - }, 150) - } - - private async get(loading: boolean) { - if (!this.projectId) return; - try { - this.loading = loading; - const res = await this.api.get(this.projectId); - if (res.done) { - this.list.phases = res.body; - this.phasesList = res.body; - } - this.loading = false; - } catch (e) { - this.loading = false; - } - - this.cdr.markForCheck(); - } - - public async updateLabel(label: string | null) { - if (!this.projectId) return; - - if (!label?.trim()) { - this.phaseLabel = this.oldLabel; - return; - } - - try { - this.updatingLabel = true; - const res = await this.api.updateLabel(this.projectId, label?.trim(), this.isProjectManager()); - if (res.done) { - this.service.updateLabel(label?.trim()); - } - this.updatingLabel = false; - } catch (e) { - this.updatingLabel = false; - } - - this.cdr.markForCheck(); - } - - private async updateColor(phase: ITaskPhase) { - if (!phase?.id || !this.projectId) return; - try { - this.updating[phase.id] = true; - const res = await this.api.updateColor(this.projectId, phase); - if (res.done) { - this.service.emitOptionsChange(); - } - this.updating[phase.id] = false; - } catch (e) { - phase.name = this.updateCache[phase.id]; - this.updating[phase.id] = false; - } - this.cdr.markForCheck(); - } - - private async update(phase: ITaskPhase) { - if (!phase?.id || !this.projectId || this.updating[phase.id]) return; - if (this.updateCache[phase.id] === phase.name) return; - - try { - this.updating[phase.id] = true; - const res = await this.api.update(this.projectId, phase, this.isProjectManager()); - if (res.done) { - await this.get(false); - this.service.emitOptionsChange(); - } - this.updating[phase.id] = false; - } catch (e) { - phase.name = this.updateCache[phase.id]; - this.updating[phase.id] = false; - } - this.cdr.markForCheck(); - } - - private async delete(id: string) { - if (!id || !this.projectId || this.deleting[id]) return; - try { - this.deleting[id] = true; - const res = await this.api.delete(id, this.projectId, this.isProjectManager()); - if (res.done) { - const index = this.list.phases.findIndex(o => o.id === id); - if (index > -1) { - this.list.phases.splice(index, 1); - this.service.emitOptionsChange(); - } - } - this.deleting[id] = false; - } catch (e) { - this.deleting[id] = false; - } - - this.cdr.markForCheck(); - } - - async setColorCode(option: ITaskPhase, color: string) { - option.color_code = color+'69'; - await this.updateColor(option); - } - - -async drop(event: CdkDragDrop) { - if (event.previousIndex === event.currentIndex) return; - moveItemInArray(this.list.phases, event.previousIndex, event.currentIndex); - this.list.phases = deepClone(this.list.phases); - this.cdr.markForCheck(); - await this.updateSortOrder(event.previousIndex, event.currentIndex, this.list.phases) -} - - async updateSortOrder(fromIndex: number, toIndex: number, phases: ITaskPhase[]) { - if(!this.projectId || !phases.length || phases.length === 0) return; - try { - this.sorting = true; - const body = { - from_index: fromIndex, - to_index: toIndex, - phases: phases, - project_id: this.projectId - } - const res = await this.api.updateSortOrder(body, this.projectId); - if(res.done) { - this.getGroups.emit(); - await this.get(false); - this.sorting = false; - this.cdr.markForCheck(); - } - } catch (e) { - log_error(e) - this.sorting = false; - this.cdr.markForCheck(); - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/ellipsis-tooltip-title.pipe.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/ellipsis-tooltip-title.pipe.ts deleted file mode 100644 index a88a870f..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/ellipsis-tooltip-title.pipe.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; - -@Pipe({ - name: 'ellipsisTooltipTitle' -}) -export class EllipsisTooltipTitlePipe implements PipeTransform { - - transform(value: string | undefined, limit: number): string { - if (!value) return ''; - return value.length > limit ? value : ''; - } - -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/sub-tasks-arrow-color.pipe.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/sub-tasks-arrow-color.pipe.ts deleted file mode 100644 index 431172fe..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/sub-tasks-arrow-color.pipe.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; - -@Pipe({ - name: 'subTasksArrowColor' -}) -export class SubTasksArrowColorPipe implements PipeTransform { - transform(value: IProjectTask, ...args: unknown[]): string { - return !!value.sub_tasks_count ? '#191919' : 'rgba(0, 0, 0, 0.45)'; - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/sub-tasks-arrow-icon.pipe.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/sub-tasks-arrow-icon.pipe.ts deleted file mode 100644 index 04746246..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/sub-tasks-arrow-icon.pipe.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; - -@Pipe({ - name: 'subTasksArrowIcon' -}) -export class SubTasksArrowIconPipe implements PipeTransform { - transform(value?: boolean, ...args: unknown[]): string { - return value ? 'down' : 'right'; - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/truncate-if-long.pipe.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/truncate-if-long.pipe.ts deleted file mode 100644 index b962bb9a..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/truncate-if-long.pipe.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; - -@Pipe({ - name: 'truncateIfLong' -}) -export class TruncateIfLongPipe implements PipeTransform { - transform(value?: string, len = 0): string { - if (!value) return ''; - return value.length > len ? `${value.slice(0, len)}...` : value; - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/validate-max-date.pipe.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/validate-max-date.pipe.ts deleted file mode 100644 index a8382488..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/validate-max-date.pipe.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; -import {UtilsService} from "@services/utils.service"; - -@Pipe({ - name: 'validateMaxDate' -}) -export class ValidateMaxDatePipe implements PipeTransform { - constructor( - private readonly utils: UtilsService - ) { - } - - transform(value?: string, ...args: unknown[]) { - return this.utils.checkForMaxDate(value); - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/validate-min-date.pipe.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/validate-min-date.pipe.ts deleted file mode 100644 index afebca0a..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/pipes/validate-min-date.pipe.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; -import {UtilsService} from "@services/utils.service"; - -@Pipe({ - name: 'validateMinDate' -}) -export class ValidateMinDatePipe implements PipeTransform { - constructor( - private readonly utils: UtilsService - ) { - } - - transform(value?: string, ...args: unknown[]) { - return this.utils.checkForMinDate(value); - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-description/task-list-description.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-description/task-list-description.component.html deleted file mode 100644 index 84f60c15..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-description/task-list-description.component.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-description/task-list-description.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-description/task-list-description.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-description/task-list-description.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-description/task-list-description.component.spec.ts deleted file mode 100644 index 5f189b79..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-description/task-list-description.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskListDescriptionComponent} from './task-list-description.component'; - -describe('TaskListDescriptionComponent', () => { - let component: TaskListDescriptionComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskListDescriptionComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskListDescriptionComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-description/task-list-description.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-description/task-list-description.component.ts deleted file mode 100644 index 205552f2..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-description/task-list-description.component.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - HostBinding, - Input, - OnDestroy, - OnInit -} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; - -@Component({ - selector: 'worklenz-task-list-description', - templateUrl: './task-list-description.component.html', - styleUrls: ['./task-list-description.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListDescriptionComponent implements OnInit, OnDestroy { - @Input() task: IProjectTask = {}; - @HostBinding("class") cls = "flex-row task-description"; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly socket: Socket - ) { - } - - ngOnInit() { - this.socket.on(SocketEvents.TASK_DESCRIPTION_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_DESCRIPTION_CHANGE.toString(), this.handleResponse); - } - - private handleResponse = (response: { id: string; description: string; }) => { - if (this.task.id === response?.id) { - this.task.description = response.description; - this.cdr.markForCheck(); - } - }; -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-end-date/task-list-end-date.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-end-date/task-list-end-date.component.html deleted file mode 100644 index c12e1a64..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-end-date/task-list-end-date.component.html +++ /dev/null @@ -1,17 +0,0 @@ -
- {{task.end_date | dateFormatter}} - - - -
diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-end-date/task-list-end-date.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-end-date/task-list-end-date.component.scss deleted file mode 100644 index 04dbbb1d..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-end-date/task-list-end-date.component.scss +++ /dev/null @@ -1,30 +0,0 @@ -nz-date-picker { - max-width: 150px; - border-color: transparent !important; - top: 0; - bottom: 0; - left: 0; - right: 0; - - &:hover { - border-color: #d9d9d9 !important; - } -} - -.editable { - position: relative; -} - -.date-text { - display: flex; - align-items: center; - padding-left: 15px; -} - -.past-date { - color: #ff4d4f; -} - -.soon-date { - color: #52c41a; -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-end-date/task-list-end-date.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-end-date/task-list-end-date.component.spec.ts deleted file mode 100644 index 6bb202c4..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-end-date/task-list-end-date.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskListEndDateComponent} from './task-list-end-date.component'; - -describe('TaskListEndDateComponent', () => { - let component: TaskListEndDateComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskListEndDateComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskListEndDateComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-end-date/task-list-end-date.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-end-date/task-list-end-date.component.ts deleted file mode 100644 index 983a09a0..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-end-date/task-list-end-date.component.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - HostBinding, - Input, - NgZone, - OnDestroy, - OnInit, - Renderer2 -} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {Socket} from "ngx-socket-io"; -import {TaskListV2Service} from "../../../task-list-v2.service"; -import {SocketEvents} from "@shared/socket-events"; -import moment from 'moment'; -import {KanbanV2Service} from 'app/administrator/modules/kanban-view-v2/kanban-view-v2.service'; -import {formatGanttDate} from "@shared/utils"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-task-list-end-date', - templateUrl: './task-list-end-date.component.html', - styleUrls: ['./task-list-end-date.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListEndDateComponent implements OnInit, OnDestroy { - @Input() task: IProjectTask = {}; - @HostBinding("class") cls = "flex-row task-due-date"; - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly service: TaskListV2Service, - private readonly renderer: Renderer2, - private readonly kanbanService: KanbanV2Service, - private readonly auth: AuthService, - ) { - } - - ngOnInit() { - this.socket.on(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.handleResponse); - } - - private handleResponse = (response: { - id: string; - parent_task: string | null; - end_date: string; - }) => { - if (response.id === this.task.id && this.task.end_date !== response.end_date) { - this.task.end_date = response.end_date; - this.kanbanService.emitRefreshGroups(); - this.cdr.markForCheck(); - } - }; - - handleEndDateChange(date: string, task: IProjectTask) { - this.socket.emit( - SocketEvents.TASK_END_DATE_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - end_date: formatGanttDate(date) || null, - parent_task: task.parent_task_id, - time_zone: this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone - })); - } - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - checkForPastDate(endDate: any) { - const formattedEndDate = moment(endDate).format('YYYY-MM-DD'); - return formattedEndDate < moment().format('YYYY-MM-DD'); - } - - checkForSoonDate(endDate: any) { - const formattedEndDate = moment(endDate).format('YYYY-MM-DD'); - const tomorrow = moment().add(1, 'day').format('YYYY-MM-DD'); - return formattedEndDate === moment().format('YYYY-MM-DD') || formattedEndDate === tomorrow; - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-labels/task-list-labels.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-labels/task-list-labels.component.html deleted file mode 100644 index 796396fd..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-labels/task-list-labels.component.html +++ /dev/null @@ -1,49 +0,0 @@ -
- - - {{item.name | ellipsis:10}} - - {{item.name | ellipsis:10}} - - - - - - - - - -
- - -
- - - Hit enter to create! - -
- -
- -
-
diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-labels/task-list-labels.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-labels/task-list-labels.component.scss deleted file mode 100644 index ad21ba4f..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-labels/task-list-labels.component.scss +++ /dev/null @@ -1,36 +0,0 @@ -.dropdown-ul { - max-height: 250px; - overflow: hidden; - overflow-y: auto; -} - -.label-tag-container { - max-width: 220px; - overflow: hidden; - flex-wrap: wrap; - padding-top: 8px; - padding-bottom: 8px; - padding-right: 0px; -} - -nz-tag { - display: block; - overflow: hidden; - white-space: break-spaces; - margin-left: 3px; - margin-right: 3px; - line-height: 16px; - padding-left: 3px; - padding-right: 3px; -} - -.empty-label { - padding-left: 8px; - padding-right: 8px; - padding-top: 2px; - padding-bottom: 3px; - - & .ant-typography { - display: flex; - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-labels/task-list-labels.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-labels/task-list-labels.component.spec.ts deleted file mode 100644 index b8b34f45..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-labels/task-list-labels.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskListLabelsComponent} from './task-list-labels.component'; - -describe('TaskListLabelsComponent', () => { - let component: TaskListLabelsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskListLabelsComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskListLabelsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-labels/task-list-labels.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-labels/task-list-labels.component.ts deleted file mode 100644 index d0e516eb..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-labels/task-list-labels.component.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostBinding, - Input, - NgZone, - OnDestroy, - OnInit, - ViewChild -} from '@angular/core'; - -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITaskLabel} from "@interfaces/task-label"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {AuthService} from "@services/auth.service"; -import {ALPHA_CHANNEL} from "@shared/constants"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {TaskListV2Service} from "../../../task-list-v2.service"; -import {UtilsService} from "@services/utils.service"; -import {ILabelsChangeResponse} from "../../../interfaces"; -import {KanbanV2Service} from 'app/administrator/modules/kanban-view-v2/kanban-view-v2.service'; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; - -@Component({ - selector: 'worklenz-task-list-labels', - templateUrl: './task-list-labels.component.html', - styleUrls: ['./task-list-labels.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListLabelsComponent implements OnInit, OnDestroy { - @ViewChild('labelsSearchInput', {static: false}) labelsSearchInput!: ElementRef; - @Input() task: IProjectTask = {}; - @HostBinding("class") cls = "flex-row task-labels"; - - readonly alpha = ALPHA_CHANNEL; - - searchText: string | null = null; - - labels: ITaskLabel[] = []; - - show = false; - - get hasFilteredLabel() { - return !!this.filteredLabels.length; - } - - get filteredLabels() { - return this.searchPipe.transform(this.labels, this.searchText); - } - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly searchPipe: SearchByNamePipe, - private readonly auth: AuthService, - private readonly socket: Socket, - private readonly utils: UtilsService, - private readonly ngZone: NgZone, - private readonly kanbanService: KanbanV2Service, - public readonly service: TaskListV2Service - ) { - this.service.onLabelsChange$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.updateLabels(); - this.cdr.markForCheck(); - }); - } - - ngOnInit() { - this.updateLabels(); - this.socket.on(SocketEvents.TASK_LABELS_CHANGE.toString(), this.handleLabelsChange); - this.socket.on(SocketEvents.CREATE_LABEL.toString(), this.handleLabelsChange); - } - - ngOnDestroy() { - this.labels = []; - this.socket.removeListener(SocketEvents.TASK_LABELS_CHANGE.toString(), this.handleLabelsChange); - this.socket.removeListener(SocketEvents.CREATE_LABEL.toString(), this.handleLabelsChange); - } - - private updateLabels() { - this.labels = this.service.labels; - } - - trackById(index: number, item: ITaskLabel) { - return item.id; - } - - private sortBySelected(labels: ITaskLabel[]) { - this.utils.sortBySelection(labels); - } - - private handleLabelsChange = (response: ILabelsChangeResponse) => { - if (response && response.id === this.task.id) { - this.task.labels = response.labels; - this.task.all_labels = response.all_labels; - - if (response.new_label) { - if (response.is_new) { - const labels = [...this.service.labels]; - labels.push(response.new_label); - this.service.labels = [...labels]; - } else { - const label = this.labels.find(l => l.id === response.new_label.id); - if (label) - label.selected = true; - } - } - this.kanbanService.emitRefreshGroups(); - this.cdr.markForCheck(); - } - } - - handleLabelsVisibleChange(visible: boolean, tr: HTMLDivElement) { - this.show = visible; - visible ? tr.classList.add(this.service.HIGHLIGHT_COL_CLS) : tr.classList.remove(this.service.HIGHLIGHT_COL_CLS); - if (visible) { - const labels = this.task.all_labels?.map(l => l.id) ?? []; - for (const label of this.labels) - label.selected = labels.includes(label.id); - this.focusLabelsSearchInput(); - } else { - this.searchText = null; - for (const label of this.labels) - label.selected = false; - } - - this.sortBySelected(this.labels); - } - - private focusLabelsSearchInput() { - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.labelsSearchInput?.nativeElement?.focus(); - }, 100); - }); - } - - handleLabelChange(label: ITaskLabel) { - this.socket.emit(SocketEvents.TASK_LABELS_CHANGE.toString(), JSON.stringify({ - task_id: this.task.id, - label_id: label.id, - parent_task: this.task.parent_task_id - })); - - this.sortBySelected(this.labels); - } - - createLabel() { - if (this.hasFilteredLabel || !this.searchText) return; - - const session = this.auth.getCurrentSession(); - this.socket.emit(SocketEvents.CREATE_LABEL.toString(), JSON.stringify({ - task_id: this.task.id, - label: this.searchText.trim(), - team_id: session?.team_id, - parent_task: this.task.parent_task_id - })); - this.searchText = null; - - this.cdr.detectChanges(); - } - - closeDropdown() { - this.ngZone.runOutsideAngular(() => { - document.body.click(); - }); - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-members/task-list-members.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-members/task-list-members.component.html deleted file mode 100644 index 3e9785ac..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-members/task-list-members.component.html +++ /dev/null @@ -1,61 +0,0 @@ -
-
- - -
-
- - -
- -
-
    - -
  • -
    - -
    - {{item.name}} - - {{item.email}} (Pending Invitation) - -
    -
    -
  • -
- -
- - -
-
diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-members/task-list-members.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-members/task-list-members.component.scss deleted file mode 100644 index 7ea94ff9..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-members/task-list-members.component.scss +++ /dev/null @@ -1,17 +0,0 @@ -.disable { - position: relative; - &:after { - position: absolute; - content: ""; - background: #e7e7e769; - top: 0; - left: 0; - right: 0; - bottom: 0; - width: 100%; - } -} - -.z-top { - z-index: 9; -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-members/task-list-members.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-members/task-list-members.component.spec.ts deleted file mode 100644 index bd4b93ed..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-members/task-list-members.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskListMembersComponent} from './task-list-members.component'; - -describe('TaskListMembersComponent', () => { - let component: TaskListMembersComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskListMembersComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskListMembersComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-members/task-list-members.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-members/task-list-members.component.ts deleted file mode 100644 index 453e2058..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-members/task-list-members.component.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - HostBinding, - Input, - NgZone, - OnDestroy, - OnInit, - Output, - ViewChild -} from '@angular/core'; -import {ILocalSession} from "@interfaces/api-models/local-session"; - -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITeamMemberViewModel} from "@interfaces/api-models/team-members-get-response"; -import {ITaskAssigneesUpdateResponse} from "@interfaces/task-assignee-update-response"; -import {AuthService} from "@services/auth.service"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {filter} from "rxjs"; -import {TaskListV2Service} from "../../../task-list-v2.service"; -import {UtilsService} from "@services/utils.service"; -import {KanbanV2Service} from 'app/administrator/modules/kanban-view-v2/kanban-view-v2.service'; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {TaskTimerService} from "@admin/components/task-timer/task-timer.service"; -import {ITimerChange} from "@admin/components/task-timer/interfaces"; - -@Component({ - selector: 'worklenz-task-list-members', - templateUrl: './task-list-members.component.html', - styleUrls: ['./task-list-members.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListMembersComponent implements OnInit, OnDestroy { - @ViewChild('memberSearchInput', {static: false}) memberSearchInput!: ElementRef; - @Input() task: IProjectTask = {}; - @HostBinding("class") cls = "flex-row task-members"; - - searchText: string | null = null; - members: ITeamMemberViewModel[] = []; - - private session: ILocalSession | null = null; - - show = false; - isOwnerOrAdmin = false; - - get avatarClass() { - return this.task.assignees?.length - ? 'add-button avatar-dashed ms-1 bg-white' - : 'avatar-dashed bg-white'; - } - - constructor( - private readonly service: TaskListV2Service, - private readonly cdr: ChangeDetectorRef, - private readonly socket: Socket, - private readonly auth: AuthService, - private readonly utils: UtilsService, - private readonly ngZone: NgZone, - private readonly kanbanService: KanbanV2Service, - private readonly timerService: TaskTimerService - ) { - this.service.onMembersChange$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.cdr.markForCheck(); - this.updateMembers(); - }); - - this.timerService.onListTimerStop - .pipe(takeUntilDestroyed()) - .subscribe((response: string) => { - if(this.task.id === response) { - this.handleMemberAssignByTimer(response); - } - }) - - this.service.onAssignMe$ - .pipe( - filter(value => value.id === this.task.id), - takeUntilDestroyed() - ) - .subscribe((value) => { - this.handleResponse(value); - }); - } - - ngOnInit() { - this.session = this.auth.getCurrentSession(); - this.isOwnerOrAdmin = this.auth.isOwnerOrAdmin(); - this.updateMembers(); - this.socket.on(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.members = []; - this.socket.removeListener(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), this.handleResponse); - } - - private handleResponse = (response: ITaskAssigneesUpdateResponse) => { - try { - if (response && response.id === this.task.id) { - this.task.assignees = response.assignees || []; - this.task.names = response.names || []; - this.cdr.markForCheck(); - } - } catch (e) { - // ignore - } - } - - private updateMembers() { - this.members = this.service.members; - } - - private sortMembersBySelection(members: ITeamMemberViewModel[]) { - this.utils.sortBySelection(members); - this.utils.sortByPending(members); - } - - trackById(index: number, item: ITeamMemberViewModel) { - return item.id; - } - - handleVisibleChange(visible: boolean, tr: HTMLDivElement) { - this.show = visible; - visible ? tr.classList.add(this.service.HIGHLIGHT_COL_CLS) : tr.classList.remove(this.service.HIGHLIGHT_COL_CLS); - if (visible) { - const assignees = this.task.assignees?.map(a => a.team_member_id) || []; - for (const member of this.members) - member.selected = assignees.includes(member.id); - this.focusMemberSearchInput(); - } else { - this.searchText = null; - for (const member of this.members) - member.selected = false; - } - this.sortMembersBySelection(this.members); - } - - private focusMemberSearchInput() { - setTimeout(() => { - this.memberSearchInput?.nativeElement?.focus(); - }, 100); - } - - handleMemberChange(member: ITeamMemberViewModel, checked: boolean) { - if (!this.session) return; - const body = { - team_member_id: member.id, - project_id: this.service.getProjectId(), - task_id: this.task.id, - reporter_id: this.session.id, - mode: checked ? 0 : 1, - parent_task: this.task.parent_task_id - }; - this.socket.emit(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), JSON.stringify(body)); - this.sortMembersBySelection(this.members); - } - - handleMemberAssignByTimer(taskId: string) { - if (!this.session) return; - const body = { - team_member_id: this.session.team_member_id, - project_id: this.service.getProjectId(), - task_id: taskId, - reporter_id: this.session.id, - mode: 0, - parent_task: this.task.parent_task_id - }; - this.socket.emit(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), JSON.stringify(body)); - this.sortMembersBySelection(this.members); - this.cdr.markForCheck(); - } - - closeDropdown() { - this.ngZone.runOutsideAngular(() => { - document.body.click(); - }); - } - - onInviteClick() { - document.body.click(); - this.service.emitInviteMembers(); - } - - transform(items: any[], searchTerm: string | null): any[] { - if (!searchTerm) - return items; - - return items.filter(item => { - return item.name.toLowerCase().includes(searchTerm.toLowerCase()); - }); - } - - selectLastValue(event: any) { - if (!event.target.value) { - return; - } else { - const filteredMembers = this.members.filter(member => member.name && member.name.toLowerCase().includes(event.target.value.toLowerCase())); - if (filteredMembers.length == 1) { - this.handleMemberChange(filteredMembers[0], !filteredMembers[0].selected); - filteredMembers[0].selected = !filteredMembers[0].selected; - } - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-phase/task-list-phase.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-phase/task-list-phase.component.html deleted file mode 100644 index 0891ad3d..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-phase/task-list-phase.component.html +++ /dev/null @@ -1,20 +0,0 @@ -
- - - - - -
diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-phase/task-list-phase.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-phase/task-list-phase.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-phase/task-list-phase.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-phase/task-list-phase.component.spec.ts deleted file mode 100644 index f6a40d8e..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-phase/task-list-phase.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskListPhaseComponent} from './task-list-phase.component'; - -describe('TaskListPhaseComponent', () => { - let component: TaskListPhaseComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskListPhaseComponent] - }); - fixture = TestBed.createComponent(TaskListPhaseComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-phase/task-list-phase.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-phase/task-list-phase.component.ts deleted file mode 100644 index 2555f9bc..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-phase/task-list-phase.component.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostBinding, - Input, - NgZone, - Renderer2 -} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {filter} from "rxjs"; -import {TaskListV2Service} from "../../../task-list-v2.service"; -import {Socket} from "ngx-socket-io"; -import {KanbanV2Service} from "../../../../kanban-view-v2/kanban-view-v2.service"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskPhase} from "@interfaces/api-models/task-phase"; -import {ITaskPhaseChangeResponse} from "@interfaces/task-phase-change-response"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {ALPHA_CHANNEL} from "@shared/constants"; - -@Component({ - selector: 'worklenz-task-list-phase', - templateUrl: './task-list-phase.component.html', - styleUrls: ['./task-list-phase.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListPhaseComponent { - @Input() task: IProjectTask = {}; - @HostBinding("class") cls = "flex-row task-phase"; - - readonly PHASE_COLOR = "#a9a9a9" + ALPHA_CHANNEL; - readonly PLACEHOLDER_COLOR = 'rgba(0, 0, 0, 0.85) !important'; - - phases: ITaskPhase[] = []; - - loading = false; - - constructor( - private readonly service: TaskListV2Service, - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly element: ElementRef, - private readonly renderer: Renderer2, - private readonly kanbanService: KanbanV2Service - ) { - this.service.onPhaseChange$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.updatePhases(); - this.cdr.markForCheck(); - }); - - this.service.onGroupChange$ - .pipe( - filter(value => value.taskId === this.task.id), - filter(() => this.isGroupByPhase()), - takeUntilDestroyed() - ) - .subscribe(value => { - if(value.groupId === "Unmapped") value.color = ''; - this.task.phase_id = value.groupId; - this.task.phase_color = value.color.slice(0, -2); - this.cdr.markForCheck(); - }); - } - - ngOnInit() { - this.updatePhases(); - this.socket.on(SocketEvents.TASK_PHASE_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_PHASE_CHANGE.toString(), this.handleResponse); - } - - private isGroupByPhase() { - return this.service.getCurrentGroup().value === this.service.GROUP_BY_PHASE_VALUE; - } - - trackById(index: number, item: ITaskStatusViewModel) { - return item.id; - } - - handleChange(phaseId: string, taskId?: string) { - if (!taskId) return; - this.socket.emit(SocketEvents.TASK_PHASE_CHANGE.toString(), { - task_id: taskId, - phase_id: phaseId, - parent_task: this.task.parent_task_id - }); - } - - private handleResponse = (response: ITaskPhaseChangeResponse) => { - if (response && response.task_id === this.task.id) { - this.task.phase_color = response.color_code || undefined; - this.task.phase_id = response.id; - - if (this.isGroupByPhase()) { - if (!this.task.is_sub_task) { - this.service.updateTaskGroup(this.task, false); - } - if (this.service.isSubtasksIncluded) { - this.service.emitRefreshSubtasksIncluded(); - } - } - - this.kanbanService.emitRefreshGroups(); - this.cdr.markForCheck(); - } - } - - private updatePhases() { - this.phases = this.service.phases; - } - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - handleOpen(open: boolean) { - this.toggleHighlightCls(open, this.element.nativeElement); - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-priority/task-list-priority.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-priority/task-list-priority.component.html deleted file mode 100644 index 066981bb..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-priority/task-list-priority.component.html +++ /dev/null @@ -1,15 +0,0 @@ -
- - - - - -
diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-priority/task-list-priority.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-priority/task-list-priority.component.scss deleted file mode 100644 index dd984827..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-priority/task-list-priority.component.scss +++ /dev/null @@ -1,5 +0,0 @@ -nz-select { - max-width: 96px; - width: 100%; - text-overflow: ellipsis; -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-priority/task-list-priority.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-priority/task-list-priority.component.spec.ts deleted file mode 100644 index 537b1f20..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-priority/task-list-priority.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskListPriorityComponent} from './task-list-priority.component'; - -describe('TaskListPriorityComponent', () => { - let component: TaskListPriorityComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskListPriorityComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskListPriorityComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-priority/task-list-priority.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-priority/task-list-priority.component.ts deleted file mode 100644 index 6b1b7b10..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-priority/task-list-priority.component.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostBinding, - Input, - NgZone, - OnDestroy, - OnInit, - Renderer2 -} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITaskPrioritiesGetResponse} from "@interfaces/api-models/task-priorities-get-response"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {filter} from "rxjs"; -import {TaskListV2Service} from "../../../task-list-v2.service"; -import {KanbanV2Service} from 'app/administrator/modules/kanban-view-v2/kanban-view-v2.service'; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; - -@Component({ - selector: 'worklenz-task-list-priority', - templateUrl: './task-list-priority.component.html', - styleUrls: ['./task-list-priority.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListPriorityComponent implements OnInit, OnDestroy { - @Input() task: IProjectTask = {}; - @HostBinding("class") cls = "flex-row task-priority"; - - priorities: ITaskPrioritiesGetResponse[] = []; - - loading = false; - - constructor( - private readonly service: TaskListV2Service, - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly element: ElementRef, - private readonly renderer: Renderer2, - private readonly kanbanService: KanbanV2Service - ) { - this.service.onPrioritiesChange$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.updatePriorities(); - this.cdr.markForCheck(); - }); - - this.service.onGroupChange$ - .pipe( - filter(value => value.taskId === this.task.id), - filter(() => this.isGroupByPriority()), - takeUntilDestroyed() - ) - .subscribe(value => { - this.task.priority = value.groupId; - this.task.priority_color = value.color; - this.cdr.markForCheck(); - }); - } - - ngOnInit() { - this.updatePriorities(); - this.socket.on(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.priorities = []; - this.socket.removeListener(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.handleResponse); - } - - private isGroupByPriority() { - return this.service.getCurrentGroup().value === this.service.GROUP_BY_PRIORITY_VALUE; - } - - trackById(index: number, item: ITaskStatusViewModel) { - return item.id; - } - - handlePriorityChange(priorityId: string, data: IProjectTask) { - this.socket.emit(SocketEvents.TASK_PRIORITY_CHANGE.toString(), JSON.stringify({ - task_id: data.id, - priority_id: priorityId, - parent_task: this.task.parent_task_id - })); - } - - private handleResponse = (response: { - priority_id: string | undefined; - name: string | undefined; id: string; parent_task: string; color_code: string; - }) => { - if (response && response.id === this.task.id) { - this.task.priority_color = response.color_code; - this.task.priority = response.priority_id; - - if (this.isGroupByPriority()) { - if (!this.task.is_sub_task) { - this.service.updateTaskGroup(this.task, false); - } - if (this.service.isSubtasksIncluded) { - this.service.emitRefreshSubtasksIncluded(); - } - } - - this.kanbanService.emitRefreshGroups(); - - this.cdr.markForCheck(); - } - } - - private updatePriorities() { - this.priorities = this.service.priorities; - } - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - handleOpen(open: boolean) { - this.toggleHighlightCls(open, this.element.nativeElement); - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-row.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-row.component.html deleted file mode 100644 index 4ebd7833..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-row.component.html +++ /dev/null @@ -1,222 +0,0 @@ -
- -
- - -
-
- -
-
- - -
- -
- - - -
- - {{task.task_key | ellipsis: 10}} - -
-
- - -
-
- -
-
- - -
-
-
- -
-
-
- - - - {{ task.name }}   -
-
- - -
- - - - - - - - - - - - - - - {{task.sub_tasks_count}} - - - -
-
-
- - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- {{task.total_time_string}} -
-
-
- - - - - - - - - - - - - -
-
- {{(task.completed_at | fromNow) || '-'}} -
-
-
- - - -
-
- {{(task.created_at | fromNow) || '-'}} -
-
-
- - - -
-
- {{(task.updated_at | fromNow) || '-'}} -
-
-
- - - -
-
- {{task.reporter}} -
-
-
- - -
- -
-
- -
-
- -
- -
diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-row.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-row.component.scss deleted file mode 100644 index 90d3d5c1..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-row.component.scss +++ /dev/null @@ -1,381 +0,0 @@ -$highlight-color: #1890ff; - -:host { - position: relative; - display: table-row; - vertical-align: inherit; - border-color: inherit; - user-select: none; - - &:hover { - .plus-icon { - display: block; - } - - td { - background: #ecf0f3; - - &:nth-child(1) { - //border-left: 2px solid lighten($highlight-color, 30%); - } - - &:last-child { - //border-right: 2px solid lighten($highlight-color, 30%); - } - - & { - //border-top: 2px solid lighten($highlight-color, 30%); - //border-bottom: 2px solid lighten($highlight-color, 30%); - } - } - - .hidden-arrow { - display: flex !important; - } - } -} - -.hidden-arrow { - display: none !important; -} - -.dropdown-highlight:hover { - background-color: rgba(208, 238, 250, 0.33); - border: #5587f5 1px solid; - border-radius: 3px; -} - -.plus-icon { - display: none; - position: absolute; - right: 0; - z-index: 1; - top: 0; - bottom: 0; - height: 100%; - // margin-top: auto; - // margin-bottom: auto; -} - -.expanded { - transform: rotate(-90deg); -} - -.sub-tasks-arrow { - position: relative; - cursor: pointer; - left: 3px; - width: 16px; - padding: 2px; - border: 1px solid transparent; - z-index: 1; - - .sub-arrow { - width: 10px; - height: 10px; - color: #191919; - margin-left: -2px; - } -} - -.task-name-text { - border: 1px solid transparent; - padding-left: 2px; - border-radius: 4px; - - &:hover { - border: 1px solid #d9d9d9; - } -} - -.task-name { - border: 1px solid transparent; - - &:hover { - cursor: text; - background: #fff; - border-radius: 4px; - } -} - -.highlight-col { - border: 1px solid #1890ff !important; - - nz-date-picker { - box-shadow: none; - } -} - -.editable { - .add-button { - visibility: hidden; - } - - &:hover { - .add-button { - visibility: visible; - } - } -} - -.ant-popover { - width: 500px; -} - -.flex-table { - display: flex; -} - -.rows { - .flex-row { - padding: 3px 12px; - border-bottom: 1px solid #f0f0f0; - border-right: 1px solid #f0f0f0; - background: white; - display: flex; - align-items: center; - max-height: 50px; - border-radius: 0px; - } - - &:hover .flex-row { - background: #f8f7f9; - } -} - -.subtask { - .flex-row { - background: #fcfcfc; - } - - //& .task-arrow::before{ - // position: absolute; - // top: 0; - // bottom: 0; - // content: ""; - // width: 2px; - // left: 2px; - // right: 0; - // margin-left: auto; - // margin-right: auto; - // z-index: 2; - // background-color: rgb(24 144 255 / 65%); - //} -} - -.task-check { - text-align: center; - padding: 8px 6px 8px 0px !important; - border-left: none; - position: sticky; - left: 24px; - z-index: 1; -} - -.task-arrow { - width: 24px; - min-width: 24px; - padding: 8px 11px 8px 2px !important; - border-right: none !important; - position: sticky; - left: 47px; - z-index: 1; - - &.highlight-col { - border-top: 1px solid #188fff !important; - border-left: 1px solid #188fff !important; - border-bottom: 1px solid #188fff !important; - } -} - -.task-name { - width: 450px; - min-width: 450px; - position: sticky; - left: 71px; - z-index: 1; - border-radius: 0px; - padding-right: 65px !important; - - &.highlight-col { - border-top: 1px solid #188fff !important; - border-right: 1px solid #188fff !important; - border-bottom: 1px solid #188fff !important; - border-left: none !important; - } - - &.left-0 { - left: 47px; - } - -} - -.task-key { - width: 85px; - min-width: 85px; - padding-left: 4px !important; - padding-right: 4px !important; - justify-content: center; - - nz-tag { - padding-left: 4px; - padding-right: 4px; - max-width: 80px; - text-overflow: ellipsis; - overflow: hidden; - } -} - -.task-description { - width: 225px; - min-width: 225px; - overflow: hidden; - display: grid !important; -} - -.task-progress { - width: 80px; - min-width: 80px; -} - -.task-labels { - padding: 0px 0px !important; - - .editable { - padding: 6px 11px; - align-items: center; - display: flex; - } -} - -.task-members { - padding: 0px 0px !important; - - .editable { - padding: 6px 11px; - align-items: center; - display: flex; - } -} - -.task-members { - width: 160px; - min-width: 160px; -} - -.task-labels { - width: 220px; - min-width: 220px; -} - -.task-status { - width: 120px; - min-width: 120px; -} - -.task-phase { - width: 150px; - min-width: 150px; -} - -.task-priority { - width: 120px; - min-width: 120px; -} - -.task-time-tracking { - width: 120px; - min-width: 120px; -} - -.task-estimation { - width: 120px; - min-width: 120px; -} - -.task-start-date, .task-due-date, .task-completed-date, .task-created-date, .task-update-date { - width: 150px; - min-width: 150px; -} - -.task-due-date { - padding: 0px 0px !important; - - .editable { - align-items: center; - display: flex; - } -} - -.task-drag-handler { - padding: 0px 0px 0px 4px !important; - width: 24px; - min-width: 24px; - border-right: none !important; - position: sticky; - left: 0px; - z-index: 1; - background-color: white; -} - -.drag-handle { - cursor: grab; - opacity: 0.8; - - &:hover { - span { - color: rgb(24, 144, 255); - } - } - - &:active { - cursor: grabbing; - } -} - -.task-name-text { - width: 100%; - -webkit-line-clamp: 1; - display: -webkit-box; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; -} - -.inner-icon-cont { - // width: 100px; - width: max-content; - display: flex; - justify-content: flex-end; - align-items: center; - column-gap: 4px; -} - -.name-input { - padding: 5px 12px; - border-left: 1px solid transparent; -} - -.double-arrow { - line-height: 16px; - border: none; - cursor: pointer; -} - -.task-placeholder { - width: 100%; - height: 42px; - border: 1px dashed #d9d9d9; - background: #fafafa; -} - -.v-line { - background-color: #188fff !important; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: -5px; - width: 1px; - margin: auto; -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-row.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-row.component.spec.ts deleted file mode 100644 index f9694e37..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-row.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskListRowComponent} from './task-list-row.component'; - -describe('TaskListRowComponent', () => { - let component: TaskListRowComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskListRowComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskListRowComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-row.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-row.component.ts deleted file mode 100644 index c782dbcf..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-row.component.ts +++ /dev/null @@ -1,299 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - HostBinding, - HostListener, - Input, - NgZone, - OnDestroy, - OnInit, - Output, - Renderer2 -} from '@angular/core'; - -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {UtilsService} from "@services/utils.service"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {filter, merge} from "rxjs"; -import {TaskListHashMapService} from "../../task-list-hash-map.service"; - -import {TaskListV2Service} from "../../task-list-v2.service"; -import {ITaskListEstimationChangeResponse} from "../../interfaces"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {ITaskListStatusChangeResponse} from '@interfaces/task-list-status-change-response'; - -@Component({ - selector: 'worklenz-task-list-row', - templateUrl: './task-list-row.component.html', - styleUrls: ['./task-list-row.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListRowComponent implements OnInit, OnDestroy { - @Input({required: true}) task!: IProjectTask; - @HostBinding("class") cls = "position-relative task-row"; - - @Output() onShowSubTasks = new EventEmitter(); - @Output() onOpenTask = new EventEmitter(); - - private readonly highlight = 'highlight-col'; - protected readonly Number = Number; - - // Selected for edit - protected editId: string | null = null; - - protected selected = false; - - protected keyActive = false; - protected descriptionActive = false; - protected progressActive = false; - protected assigneesActive = false; - protected labelsActive = false; - protected phaseActive = false; - protected statusActive = false; - protected priorityActive = false; - protected timeTrackingActive = false; - protected estimationActive = false; - protected startDateActive = false; - protected dueDateActive = false; - protected completedDateActive = false; - protected createdDateActive = false; - protected lastUpdatedActive = false; - protected reporterActive = false; - - public get id() { - return this.task.id; - } - - constructor( - private readonly element: ElementRef, - private readonly renderer: Renderer2, - public readonly service: TaskListV2Service, - private readonly cdr: ChangeDetectorRef, - private readonly socket: Socket, - private readonly map: TaskListHashMapService, - private readonly ngZone: NgZone, - private readonly view: TaskViewService, - public readonly utils: UtilsService - ) { - this.service.onColumnsChange$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.markForCheck(); - this.updateState(); - }); - - merge( - this.map.onSelect$.pipe( - filter(value => value.id === this.id), - filter(() => !this.selected) - ), - this.map.onDeselect$.pipe( - filter(value => value.id === this.id), - filter(() => this.selected) - ), - this.map.onDeselectAll$.pipe( - filter(() => this.selected) - ) - ).pipe( - takeUntilDestroyed() - ).subscribe(value => { - this.selected = !this.selected; - this.toggleSelection(); - this.markForCheck(); - }); - - this.view.onCommentsChange - .pipe( - filter(value => value.task === this.task.id), - takeUntilDestroyed() - ) - .subscribe(value => { - this.task.comments_count = value.count; - this.cdr.markForCheck(); - }); - - this.view.onAttachmentsChange - .pipe( - filter(value => value.task === this.task.id), - takeUntilDestroyed() - ) - .subscribe(value => { - this.task.attachments_count = value.count; - this.cdr.markForCheck(); - }); - - this.view.onTaskSubscriberChange$ - .pipe(takeUntilDestroyed()) - .subscribe((value) => { - this.subscriberChange(value.taskId, value.subscribers); - }); - } - - private toggleSelection() { - this.ngZone.runOutsideAngular(() => { - const cls = "selected"; - const ele = this.element.nativeElement; - - if (this.selected) { - this.renderer.addClass(ele, cls); - } else { - this.renderer.removeClass(ele, cls); - } - }); - } - - ngOnInit() { - this.updateState(); - this.registerSocketEvents(); - this.socket.on(SocketEvents.TASK_STATUS_CHANGE.toString(), this.handleCompletedAt); - } - - ngOnDestroy() { - this.unregisterSocketEvents(); - this.socket.removeListener(SocketEvents.TASK_STATUS_CHANGE.toString(), this.handleCompletedAt); - } - - private updateState() { - this.keyActive = this.active(this.service.COLUMN_KEYS.KEY); - this.descriptionActive = this.active(this.service.COLUMN_KEYS.DESCRIPTION); - this.progressActive = this.active(this.service.COLUMN_KEYS.PROGRESS); - this.assigneesActive = this.active(this.service.COLUMN_KEYS.ASSIGNEES); - this.labelsActive = this.active(this.service.COLUMN_KEYS.LABELS); - this.statusActive = this.active(this.service.COLUMN_KEYS.STATUS); - this.priorityActive = this.active(this.service.COLUMN_KEYS.PRIORITY); - this.timeTrackingActive = this.active(this.service.COLUMN_KEYS.TIME_TRACKING); - this.estimationActive = this.active(this.service.COLUMN_KEYS.ESTIMATION); - this.startDateActive = this.active(this.service.COLUMN_KEYS.START_DATE); - this.dueDateActive = this.active(this.service.COLUMN_KEYS.DUE_DATE); - this.completedDateActive = this.active(this.service.COLUMN_KEYS.COMPLETED_DATE); - this.createdDateActive = this.active(this.service.COLUMN_KEYS.CREATED_DATE); - this.lastUpdatedActive = this.active(this.service.COLUMN_KEYS.LAST_UPDATED); - this.reporterActive = this.active(this.service.COLUMN_KEYS.REPORTER); - this.phaseActive = this.active(this.service.COLUMN_KEYS.PHASE); - } - - private registerSocketEvents() { - this.socket.on(SocketEvents.TASK_NAME_CHANGE.toString(), this.handleNameChangeResponse); - this.socket.on(SocketEvents.TASK_TIME_ESTIMATION_CHANGE.toString(), this.handleEstimationChangeResponse); - } - - private unregisterSocketEvents() { - this.socket.removeListener(SocketEvents.TASK_NAME_CHANGE.toString(), this.handleNameChangeResponse); - this.socket.removeListener(SocketEvents.TASK_TIME_ESTIMATION_CHANGE.toString(), this.handleEstimationChangeResponse); - } - - private active(key: string) { - return this.service.canActive(key); - } - - @HostListener("contextmenu", ["$event"]) - private onContextMenu(event: MouseEvent) { - this.service.emitOnContextMenu(event, this.task); - } - - focus(tr: HTMLDivElement) { - setTimeout(() => { - const element = tr.querySelector("input"); - element?.focus(); - }); - } - - onCheckChange(checked: boolean) { - if (checked) { - this.map.selectTask(this.task); - } else { - this.map.deselectTask(this.task); - } - - this.toggleSelection(); - } - - openSubTasks() { - this.onShowSubTasks?.emit(this.task); - } - - openTask(task: IProjectTask) { - this.onOpenTask?.emit(task); - } - - selectCol(element: HTMLDivElement) { - if (element.classList.contains(this.highlight)) return; - element.classList.add(this.highlight); - } - - deselectCol(element: HTMLDivElement) { - element.classList.remove(this.highlight); - this.editId = null; - } - - handleNameChange(data?: IProjectTask) { - if (!data) return; - this.socket.emit(SocketEvents.TASK_NAME_CHANGE.toString(), JSON.stringify({ - task_id: data.id, - name: data.name, - parent_task: this.task.parent_task_id - })); - this.editId = null; - } - - onTaskNameClick(event: MouseEvent, tr1: HTMLDivElement, task: IProjectTask) { - event.stopPropagation(); - this.focus(tr1); - this.editId = task.id || null; - } - - public markForCheck() { - this.cdr.markForCheck(); - } - - public detectChanges() { - this.cdr.detectChanges(); - } - - public onDragStart() { - this.map.deselectAll(); - this.detectChanges(); - } - - private handleNameChangeResponse = (response: { id: string; parent_task: string; name: string; }) => { - if (!response) return; - if (this.id !== response.id) return; - - if (this.task && this.task.name != response.name) { - this.task.name = response.name; - this.markForCheck(); - } - }; - - private handleEstimationChangeResponse = (response: ITaskListEstimationChangeResponse) => { - if (response.id === this.id) { - this.task.total_time_string = response.total_time_string; - this.cdr.markForCheck(); - } - }; - - private subscriberChange = (taskId: string, subscribers: number) => { - if (!taskId) return; - if (this.id !== taskId) return; - - if (subscribers == 0 || subscribers < 0) { - this.task.has_subscribers = false; - } else { - this.task.has_subscribers = true; - } - - this.cdr.markForCheck(); - } - - private handleCompletedAt = (response: ITaskListStatusChangeResponse) => { - if (!response.id) return; - if (this.id !== response.id) return; - this.task.completed_at = response.completed_at; - } - -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-start-date/task-list-start-date.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-start-date/task-list-start-date.component.html deleted file mode 100644 index 2ba46ad4..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-start-date/task-list-start-date.component.html +++ /dev/null @@ -1,17 +0,0 @@ -
- - - - {{task.start_date | dateFormatter}} - - -
diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-start-date/task-list-start-date.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-start-date/task-list-start-date.component.scss deleted file mode 100644 index 2e6f79b0..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-start-date/task-list-start-date.component.scss +++ /dev/null @@ -1,25 +0,0 @@ -//.picker-padding { -// padding: 4px 11px 4px; -//} -nz-date-picker { - max-width: 150px; - border-color: transparent !important; - top: 0; - bottom: 0; - left: 0; - right: 0; - - &:hover { - border-color: #d9d9d9 !important; - } -} - -.editable { - position: relative; -} - -.date-text { - display: flex; - align-items: center; - padding-left: 15px; -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-start-date/task-list-start-date.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-start-date/task-list-start-date.component.spec.ts deleted file mode 100644 index 006a87e4..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-start-date/task-list-start-date.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskListStartDateComponent} from './task-list-start-date.component'; - -describe('TaskListStartDateComponent', () => { - let component: TaskListStartDateComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskListStartDateComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskListStartDateComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-start-date/task-list-start-date.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-start-date/task-list-start-date.component.ts deleted file mode 100644 index 66805fe7..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-start-date/task-list-start-date.component.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - HostBinding, - Input, - NgZone, - OnDestroy, - OnInit, - Renderer2 -} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {TaskListV2Service} from "../../../task-list-v2.service"; -import {formatGanttDate} from "@shared/utils"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-task-list-start-date', - templateUrl: './task-list-start-date.component.html', - styleUrls: ['./task-list-start-date.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListStartDateComponent implements OnInit, OnDestroy { - @Input() task: IProjectTask = {}; - @HostBinding("class") cls = "flex-row task-due-date"; - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly service: TaskListV2Service, - private readonly renderer: Renderer2, - private readonly auth: AuthService, - ) { - } - - ngOnInit() { - this.socket.on(SocketEvents.TASK_START_DATE_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_START_DATE_CHANGE.toString(), this.handleResponse); - } - - private handleResponse = (response: { - id: string; - parent_task: string | null; - start_date: string; - }) => { - if (response.id === this.task.id && this.task.start_date !== response.start_date) { - this.task.start_date = response.start_date; - this.cdr.markForCheck(); - } - }; - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - handleStartDateChange(date: string, task: IProjectTask) { - this.socket.emit( - SocketEvents.TASK_START_DATE_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - start_date: formatGanttDate(date) || null, - parent_task: task.parent_task_id, - time_zone: this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone - })); - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-status/task-list-status.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-status/task-list-status.component.html deleted file mode 100644 index 3610373e..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-status/task-list-status.component.html +++ /dev/null @@ -1,14 +0,0 @@ -
- - - - - -
diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-status/task-list-status.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-status/task-list-status.component.scss deleted file mode 100644 index dd984827..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-status/task-list-status.component.scss +++ /dev/null @@ -1,5 +0,0 @@ -nz-select { - max-width: 96px; - width: 100%; - text-overflow: ellipsis; -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-status/task-list-status.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-status/task-list-status.component.spec.ts deleted file mode 100644 index 7f004ec5..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-status/task-list-status.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskListStatusComponent} from './task-list-status.component'; - -describe('TaskListStatusComponent', () => { - let component: TaskListStatusComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskListStatusComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskListStatusComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-status/task-list-status.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-status/task-list-status.component.ts deleted file mode 100644 index 8bcaae42..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-status/task-list-status.component.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostBinding, - Input, - NgZone, - OnDestroy, - OnInit, - Renderer2 -} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {filter} from "rxjs"; -import {TaskListV2Service} from "../../../task-list-v2.service"; -import {KanbanV2Service} from "app/administrator/modules/kanban-view-v2/kanban-view-v2.service"; -import {ITaskListStatusChangeResponse} from "@interfaces/task-list-status-change-response"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-task-list-status', - templateUrl: './task-list-status.component.html', - styleUrls: ['./task-list-status.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListStatusComponent implements OnInit, OnDestroy { - @Input() task: IProjectTask = {}; - @HostBinding("class") cls = "flex-row task-status"; - - statuses: ITaskStatusViewModel[] = []; - - loading = false; - - constructor( - private readonly service: TaskListV2Service, - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly element: ElementRef, - private readonly renderer: Renderer2, - private readonly kanbanService: KanbanV2Service, - private readonly auth: AuthService, - ) { - this.service.onStatusesChange$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.updateStatuses(); - this.cdr.markForCheck(); - }); - - this.service.onGroupChange$ - .pipe( - filter(value => value.taskId === this.task.id), - filter(() => this.isGroupByStatus()), - takeUntilDestroyed() - ) - .subscribe(value => { - this.task.status = value.groupId; - this.task.status_color = value.color; - this.getTaskProgress(value.taskId); - this.cdr.markForCheck(); - }); - } - - ngOnInit() { - this.updateStatuses(); - this.socket.on(SocketEvents.TASK_STATUS_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_STATUS_CHANGE.toString(), this.handleResponse); - } - - private getTaskProgress(taskId: string) { - this.socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), taskId); - } - - private isGroupByStatus() { - return this.service.getCurrentGroup().value === this.service.GROUP_BY_STATUS_VALUE; - } - - trackById(index: number, item: ITaskStatusViewModel) { - return item.id; - } - - handleStatusChange(statusId: string, taskId?: string) { - if (!taskId) return; - - this.socket.emit(SocketEvents.TASK_STATUS_CHANGE.toString(), JSON.stringify({ - task_id: taskId, - status_id: statusId, - parent_task: this.task.parent_task_id, -team_id: this.auth.getCurrentSession()?.team_id - })); - - this.getTaskProgress(taskId); - } - - private handleResponse = (response: ITaskListStatusChangeResponse) => { - if (response && response.id === this.task.id) { - this.task.status_color = response.color_code; - this.task.complete_ratio = +response.complete_ratio || 0; - this.task.status = response.status_id; - this.task.status_category = response.statusCategory; - - if (this.isGroupByStatus()) { - if (!this.task.is_sub_task) { - this.service.updateTaskGroup(this.task, false); - } - if (this.service.isSubtasksIncluded) { - this.service.emitRefreshSubtasksIncluded(); - } - } - - this.service.emitUpdateGroupProgress(this.task.id); - this.kanbanService.emitRefreshGroups(); - - this.cdr.markForCheck(); - } - } - - private updateStatuses() { - this.statuses = this.service.statuses; - } - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - handleOpen(open: boolean) { - this.toggleHighlightCls(open, this.element.nativeElement); - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-timer/task-list-timer.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-timer/task-list-timer.component.html deleted file mode 100644 index 81f36dcb..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-timer/task-list-timer.component.html +++ /dev/null @@ -1,53 +0,0 @@ -
- - - - - - - - - - - - - - - - - {{item.user_name}} logged {{item.time_spent_text}} - {{item.logged_by_timer ? 'via Timer' : ''}} about {{item.created_at | fromNow}} - - - - - - - {{item.start_time | date: dateFormat}} - {{item.end_time | date: dateFormat}} - - - {{item.start_time | date: dateFormat}} - - - - - - - - - -
diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-timer/task-list-timer.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-timer/task-list-timer.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-timer/task-list-timer.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-timer/task-list-timer.component.spec.ts deleted file mode 100644 index f2166f94..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-timer/task-list-timer.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskListTimerComponent} from './task-list-timer.component'; - -describe('TaskListTimerComponent', () => { - let component: TaskListTimerComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskListTimerComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskListTimerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-timer/task-list-timer.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-timer/task-list-timer.component.ts deleted file mode 100644 index d480cfb9..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-list-timer/task-list-timer.component.ts +++ /dev/null @@ -1,67 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, HostBinding, Input} from '@angular/core'; -import {TasksLogTimeService} from "@api/tasks-log-time.service"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITaskLogViewModel} from "@interfaces/api-models/task-log-create-request"; -import moment from "moment"; -import {time} from "html2canvas/dist/types/css/types/time"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-task-list-timer', - templateUrl: './task-list-timer.component.html', - styleUrls: ['./task-list-timer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListTimerComponent { - @Input() task: IProjectTask = {}; - @HostBinding("class") cls = "flex-row task-time-tracking justify-content-center"; - - timeLogs: ITaskLogViewModel[] = []; - - loading = false; - - readonly dateFormat = 'MMM d, y, h:mm:ss a'; - - constructor( - private readonly api: TasksLogTimeService, - private readonly cdr: ChangeDetectorRef, - private readonly auth: AuthService, - ) { - } - - private format(duration: moment.Duration, showSeconds: boolean) { - const format = `h[h] m[m] ${showSeconds ? "s[s]" : ""}`; - return moment.utc(duration.asMilliseconds()).format( - duration.hours() > 0 ? format : "m[m] s[s]" - ); - } - - private buildText(models: ITaskLogViewModel[]) { - for (const model of models) { - const duration = moment.duration(model.time_spent, "seconds"); - model.time_spent_text = this.format(duration, model.logged_by_timer || false); - } - } - - async handleVisibleChange(visible: boolean, task: IProjectTask) { - try { - if (!task.id) return; - if (visible) { - this.loading = true; - const res = await this.api.getByTask(task.id, this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name as string : Intl.DateTimeFormat().resolvedOptions().timeZone); - if (res.done) { - const data = res.body; - this.buildText(data); - this.timeLogs = data; - } - } else { - this.timeLogs = []; - } - this.loading = false; - } catch (e) { - this.loading = false; - } - - this.cdr.detectChanges(); - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-progress/task-progress.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-progress/task-progress.component.html deleted file mode 100644 index 1d9acae5..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-progress/task-progress.component.html +++ /dev/null @@ -1,10 +0,0 @@ -
- -
diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-progress/task-progress.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-progress/task-progress.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-progress/task-progress.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-progress/task-progress.component.spec.ts deleted file mode 100644 index 565ea32b..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-progress/task-progress.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskProgressComponent} from './task-progress.component'; - -describe('TaskProgressComponent', () => { - let component: TaskProgressComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskProgressComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskProgressComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-progress/task-progress.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-progress/task-progress.component.ts deleted file mode 100644 index cd862edd..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-row/task-progress/task-progress.component.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - HostBinding, - Input, - OnDestroy, - OnInit -} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; - -@Component({ - selector: 'worklenz-task-progress', - templateUrl: './task-progress.component.html', - styleUrls: ['./task-progress.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskProgressComponent implements OnInit, OnDestroy { - @Input() task: IProjectTask = {}; - @HostBinding("class") cls = "flex-row task-progress text-align-center"; - - get percent() { - return this.task.complete_ratio || 0; - } - - get width() { - return (this.task.complete_ratio || 0) >= 100 ? 16 : 26; - } - - get strokeWidth() { - return (this.task.complete_ratio || 0) >= 100 ? 9 : 7; - } - - get tooltipTitle() { - return (this.task.completed_count || 0) + '/' + (this.task.total_tasks_count || 0); - } - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef - ) { - } - - ngOnInit() { - this.socket.on(SocketEvents.GET_TASK_PROGRESS.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.GET_TASK_PROGRESS.toString(), this.handleResponse); - } - - private handleResponse = (response: { - id: string; - parent_task: string; - complete_ratio: number; - completed_count: number; - total_tasks_count: number; - }) => { - if (response && (response.parent_task === this.task.id || response.id === this.task.id)) { - this.task.complete_ratio = +response.complete_ratio; - this.task.total_tasks_count = response.total_tasks_count; - this.task.completed_count = response.completed_count; - this.cdr.markForCheck(); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-table.component.html b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-table.component.html deleted file mode 100644 index e527351a..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-table.component.html +++ /dev/null @@ -1,192 +0,0 @@ -
- -
- - - -
- - Show archived -
- -
-
-
- - - -
-
- -
- - - There are no status groups to show in this project.
Please go to the "Board" section to create status groups as you desire. -
- - There are no phases to show in this project. -
You can Phases as you desire. -
-
-
- -
-
-
- - - -
- - -
-
-
- - - - -
- No tasks available -
- - - - - - -
- - - -
-
-   -
-
- - -
-
-
-
-
-
- -
-
- -
- -
-
-
-
-
-
- - - - - -
- - - - - - - - - - - - diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-table.component.scss b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-table.component.scss deleted file mode 100644 index b2bd5372..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-table.component.scss +++ /dev/null @@ -1,395 +0,0 @@ -// :host { -// overflow: hidden; -// display: block; -// overflow-x: auto; -// } - -.task-status-color { - border-width: 1.4px; - border-style: solid; - border-radius: 4px; - width: 1px; -} - -.task-name-color { - height: 20px; - margin: auto; - border-color: rgb(169, 169, 169); - background-color: rgb(169, 169, 169); - position: absolute; - top: 0; - bottom: 0; - left: 0; - border-width: 1.2px; -} - -.editable-cell { - white-space: nowrap; - max-width: 255px; - overflow: hidden; - text-overflow: ellipsis; -} - -nz-date-picker { - background: transparent; -} - -//.dropdown-animation { -// //transition: all .05s ease-in; -//} - -.expanded { - transform: rotate(-90deg); -} - -.dropdown-highlight { - padding: 1px; -} - -.highlight-col { - border: 1px solid #1890ff !important; -} - -.dropdown-highlight:hover { - background-color: rgba(208, 238, 250, 0.33); - border: #5587f5 1px solid; - border-radius: 3px; -} - -.pointer-text { - cursor: text; -} - -.plus-icon { - display: none; - position: absolute; - right: 0; - z-index: 9; - background-color: #e2e7ea; -} - -tr:hover .plus-icon { - display: block; -} - -.selected { - background: #1890ff14 !important; - - .task-name, - .task-drag-handler { - background: #1890ff14 !important; - } -} - -.sub-task-background-color { - background-color: #f5f5f58a; -} - -.drop-down-btn { - padding: 4px 11px; - - .anticon-caret-down { - color: rgba(0, 0, 0, 0.65); - } -} - -.status-color { - width: 11px; - height: 11px; - border-radius: 2px; - margin-right: 7px; -} - -div { - box-sizing: border-box; -} - -.flex-table { - display: flex; - width: max-content; -} - -.table-container { - overflow: auto; - display: flex; - // flex-direction: column; -} - -.tasks-table { - width: max-content; - margin-left: 3px; - border-right: 1px solid #f0f0f0; -} - -.column-trigger { - background-color: #F4F5F7; - height: 40px; - width: 40px; - justify-content: center; - display: flex; - align-items: center; -} - -.header { - margin-bottom: 0px; - position: sticky; - top: 0; - background-color: white; - z-index: 2; - - .flex-row { - padding: 4px 11px; - background-color: #fafafa; - border-top: 1px solid #f0f0f0; - border-bottom: 1px solid #f0f0f0; - border-right: 1px solid #f0f0f0; - display: flex; - align-items: center; - flex-direction: row; - } -} - -.br-right { - border-right: 1px solid #f0f0f0; -} - -.task-check { - text-align: center; - padding: 8px 6px 8px 0px !important; - position: sticky; - left: 24px; - z-index: 1; -} - -.task-key { - width: 85px; -} - -.task-arrow { - width: 24px; - padding: 0px !important; - display: flex; - align-items: center; - border: none !important; - position: sticky; - left: 47px; - z-index: 1; -} - -.task-name { - width: 450px; - min-width: 450px; - position: sticky; - left: 71px; - z-index: 1; - background-color: white; - - nz-filter-trigger { - margin-left: auto; - } -} - -.task-description { - width: 225px; -} - -.task-progress { - width: 80px; -} - -.task-members { - width: 160px; -} - -.task-labels { - width: 210px; -} - -.task-status { - width: 120px; -} - -.task-priority { - width: 120px; -} - -.task-time-tracking { - width: 120px; -} - -.task-estimation { - width: 120px; -} - -.task-start-date, .task-due-date, .task-completed-date, .task-created-date, .task-update-date { - width: 150px; -} - -.task-drag-handler { - padding: 0px 0px 0px 4px !important; - width: 24px; - border-bottom: 1px solid #f0f0f0; - border-right: none !important; - position: sticky; - left: 0px; -} - -.inner-subtask-create { - height: 42px; - display: flex; - align-items: center; - max-width: 1316px; - overflow: hidden; - background-color: rgb(250 250 250); - position: sticky; - left: 0; - border-bottom: 1px solid #eaeaea; - border-top: 1px solid #eaeaea; - @media(max-width: 1400px) { - max-width: 1136px; - } - @media(max-width: 1200px) { - max-width: 956px; - } - - &.highlight-col { - background-color: white; - } -} - -.new-subtask-divider { - width: 50px; - - &.divider-large { - width: 135px; - } -} - -worklenz-quick-task { - display: block; -} - - -.overflow-x-auto { - overflow-x: auto; - overflow-y: hidden; -} - -.panel { - padding: 0 0; - background-color: white; - max-height: calc(100% + 8px); - overflow: hidden; - transition: max-height 0.1s ease-out; - border-right: 1px solid #f0f0f0; - // &.show { - // height: auto; - // transition: height 0.05s ease-out; - // } -} - -.panel-left-border { - position: absolute; - content: ''; - top: 0; - bottom: 0; - width: 3px; - z-index: 3; - border-bottom-left-radius: 4px; -} - -.collapse.btn .collapse-icon { - transition: transform 0.1s; - transform: rotate(0); -} - -.collapse.btn.active .collapse-icon { - transition: transform 0.1s; - transform: rotate(90deg); -} - - -.drop-down-btn { - padding: 4px 11px; - - .anticon-caret-down { - color: #d9d9d9; - } -} - -.status-color { - width: 11px; - height: 11px; - border-radius: 2px; - margin-right: 7px; -} - -.drop-down-btn { - nz-badge { - margin-top: -2px; - } - - &.active { - color: #1890ff; - border-color: #1890ff; - background-color: #E6F7FF; - - .drop-down-btn { - .anticon-caret-down { - color: #1890ff; - } - } - } -} - -.ant-badge-count-sm { - font-size: 11px; -} - -.tab-name-edit { - // width: 0px; - overflow: hidden; - - span { - margin-right: 0px; - font-size: 14px; - } - - svg { - margin-right: 0px; - } -} - -.new-task-input { - // height: 32px; - padding-left: 4px; - padding-top: 4px; - padding-bottom: 4px; - border-bottom: 1px solid #f0f0f0; - border-top: 1px solid #f0f0f0; - max-width: 1316px; - overflow-x: auto; - @media(max-width: 1400px) { - max-width: 1136px; - } - @media(max-width: 1200px) { - max-width: 956px; - } -} - -.tasks-empty-placeholder { - width: 100%; - height: 42px; - background: #fafafa; -} - -.no-data-img-holder { - width: 100px; - margin-top: 42px; -} - -.add-field-button { - position: absolute; - top: 46px; - right: 0; -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-table.component.spec.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-table.component.spec.ts deleted file mode 100644 index 9ffbad75..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-table.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskListTableComponent} from './task-list-table.component'; - -describe('TaskListTableComponent', () => { - let component: TaskListTableComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskListTableComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskListTableComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-table.component.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-table.component.ts deleted file mode 100644 index 1f48083e..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-table/task-list-table.component.ts +++ /dev/null @@ -1,722 +0,0 @@ -import {CdkDragDrop, moveItemInArray, transferArrayItem} from "@angular/cdk/drag-drop"; -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostListener, - NgZone, - OnDestroy, - OnInit, - QueryList, - Renderer2, - ViewChildren -} from '@angular/core'; -import {ActivatedRoute} from "@angular/router"; -import {TaskLabelsApiService} from "@api/task-labels-api.service"; -import {TaskPrioritiesService} from "@api/task-priorities.service"; -import {TaskStatusesApiService} from "@api/task-statuses-api.service"; -import {TasksApiService} from "@api/tasks-api.service"; -import {TeamMembersApiService} from "@api/team-members-api.service"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {UtilsService} from "@services/utils.service"; -import {Socket} from "ngx-socket-io"; -import {filter, merge} from "rxjs"; -import { - IGroupByOption, - IMembersFilterChange, - ITaskListConfigV2, - ITaskListGroup, - ITaskListGroupChangeResponse -} from "../interfaces"; -import {TaskListHashMapService} from "../task-list-hash-map.service"; -import {TaskListV2Service} from "../task-list-v2.service"; -import {TaskListAddTaskInputComponent} from "./task-list-add-task-input/task-list-add-task-input.component"; -import {TaskListRowComponent} from "./task-list-row/task-list-row.component"; -import {TaskTemplatesService} from "@api/task-templates.service"; -import {SocketEvents} from "@shared/socket-events"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; -import {ITaskStatusCategory} from "@interfaces/task-status-category"; -import {KanbanV2Service} from "../../kanban-view-v2/kanban-view-v2.service"; -import {deepClone, waitForSeconds} from "@shared/utils"; -import {TaskPhasesApiService} from "@api/task-phases-api.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {ProjectPhasesService} from "@services/project-phases.service"; -import {Promise} from "@rx-angular/cdk/zone-less/browser"; -import {ProjectFormService} from "@services/project-form-service.service"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-task-list-table', - templateUrl: './task-list-table.component.html', - styleUrls: ['./task-list-table.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListTableComponent implements OnInit, OnDestroy { - @ViewChildren('row') rows!: QueryList; - @ViewChildren('scrollPanel') scrollPanels!: QueryList; - - protected projectId: string | null = null; - protected sortField: string | null = null; - protected sortOrder: string | null = null; - protected searchValue: string | null = null; - protected statusesFilterString: string | null = null; - protected membersFilterString: string | null = null; - protected projectsFilterString: string | null = null; - protected labelsFilterString: string | null = null; - protected priorityFilterString: string | null = null; - - scrollBy = 0; - - protected showArchived = false; - protected selected = false; - protected loadingGroups = false; - protected loadingMembers = false; - protected loadingColumns = false; - protected loadingLabels = false; - protected loadingStatuses = false; - protected loadingPriorities = false; - protected loadingPhases = false; - protected loadingCategories = false; - protected showTaskModal = false; - protected showTaskTemplatesDrawer = false; - protected loadingArchived = false; - protected loadingFiltering = false; - protected groupChanging = false; - protected displayPhaseModal = false; - - // Select all - protected checked = false; - protected indeterminate = false; - showStatusModal = false; - isScrolled = false; - - protected taskId: string | null = null; - - protected selectedTask: IProjectTask | null = null; - protected groupIds: string[] = []; - protected categories: ITaskStatusCategory[] = []; - - private scrolling = false; - - protected get loading() { - return this.loadingColumns || this.loadingGroups || this.loadingFiltering || this.groupChanging; - } - - protected get defaultStatus() { - return this.service.statuses.length - ? this.service.statuses[0].id as string - : null; - } - - protected get groups() { - return this.service.groups; - } - - constructor( - private readonly route: ActivatedRoute, - private readonly api: TasksApiService, - private readonly cdr: ChangeDetectorRef, - private readonly tmApi: TeamMembersApiService, - private readonly labelsApi: TaskLabelsApiService, - private readonly statusesApi: TaskStatusesApiService, - private readonly prioritiesApi: TaskPrioritiesService, - private readonly phasesApi: TaskPhasesApiService, - private readonly ngZone: NgZone, - private readonly map: TaskListHashMapService, - private readonly socket: Socket, - private readonly renderer: Renderer2, - private readonly taskTemplates: TaskTemplatesService, - private readonly view: TaskViewService, - private readonly kanbanService: KanbanV2Service, - private readonly phaseService: ProjectPhasesService, - public readonly service: TaskListV2Service, - public readonly utils: UtilsService, - private readonly projectFormService: ProjectFormService, - private readonly auth: AuthService, - ) { - // The id parameter represents the project id - this.projectId = this.route.snapshot.paramMap.get("id"); - this.taskId = this.route.snapshot.queryParamMap.get("task"); - - // set group parameter to the from reporting and check them - this.route.queryParams.subscribe(params => { - const typeParam = params['group']; - if (typeParam) { - switch (typeParam) { - case "status": - this.service.setCurrentGroup(this.service.GROUP_BY_OPTIONS[0]); - break; - case "priority": - this.service.setCurrentGroup(this.service.GROUP_BY_OPTIONS[1]); - break; - case "phase": - this.service.setCurrentGroup(this.service.GROUP_BY_OPTIONS[2]); - break; - default : - this.service.setCurrentGroup(this.service.GROUP_BY_OPTIONS[0]); - break; - } - } - }); - - this.service.setProjectId(this.projectId as string); - - this.service.onTaskAddOrDelete$ - .pipe(takeUntilDestroyed()) - .subscribe((value) => { - if (value) - this.handleNewTaskReceive(value); - }); - - this.taskTemplates.onTemplateImport - .pipe(takeUntilDestroyed()) - .subscribe(() => { - void this.getGroups(true); - }); - - this.service.onRefresh$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - void this.getGroups(false); - }); - - this.service.onRefreshSubtasksIncluded - .pipe(takeUntilDestroyed()) - .subscribe(() => { - void this.getGroups(false); - }); - - this.view.onSelectTask - .pipe(takeUntilDestroyed()) - .subscribe((taskId: string) => { - const task = this.map.tasks.get(taskId); - if (task) { - void this.handleTaskSelectFromView(task); - } - }); - // - // this.view.onSelectSubTask - // .pipe(takeUntilDestroyed()) - // .subscribe((task: IProjectTask) => { - // if (task) { - // void this.handleTaskSelectFromView(task); - // } - // }); - - merge(this.phaseService.onPhaseOptionsChange, this.view.onPhaseChange) - .pipe( - filter(() => this.service.getCurrentGroup().value === this.service.GROUP_BY_PHASE_VALUE), - takeUntilDestroyed() - ) - .subscribe(() => { - void this.getGroups(false); - }); - - this.projectFormService.onMemberAssignOrRemoveReProject - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.cdr.markForCheck(); - this.getTeamMembers(); - }) - } - - ngOnInit(): void { - this.service.isSubtasksIncluded = false; - void Promise.all([ - this.getGroups(true), - this.getColumns(), - this.getTeamMembers(), - this.getLabels(), - this.getStatuses(), - this.getPriorities(), - this.getCategories(), - this.getPhases(), - ]); - - this.socket.on(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), this.handleSortOrderResponse); - - if (this.taskId && this.projectId) { - this.openTask({ - id: this.taskId, - project_id: this.projectId - }); - } - } - - ngOnDestroy() { - this.ngZone.runOutsideAngular(() => { - this.service.reset(); - this.service.groups = []; - this.map.reset(); - }); - - this.socket.removeListener(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), this.handleSortOrderResponse); - } - - protected isGroupByPhase() { - return this.service.getCurrentGroup().value === this.service.GROUP_BY_PHASE_VALUE; - } - - /** @param parentTaskId if provided, the list considers as sub-tasks of the given parentTaskId */ - private getConf(parentTaskId?: string): ITaskListConfigV2 { - const config: ITaskListConfigV2 = { - id: this.projectId as string, - field: this.sortField, - order: this.sortOrder, - search: this.searchValue, - statuses: this.statusesFilterString, - members: this.membersFilterString, - projects: this.projectsFilterString, - priorities: this.priorityFilterString, - labels: this.labelsFilterString, - archived: this.showArchived, - group: this.service.getCurrentGroup().value, - isSubtasksInclude: false - }; - - if (parentTaskId) - config.parent_task = parentTaskId; - - return config; - } - - protected async displaySubTasks(task: IProjectTask, row: TaskListRowComponent, groupId: string) { - if (!task.id && task.sub_tasks_loading) return; - - // Ignore loading sub-tasks from api when we know it's empty - if (!task.show_sub_tasks && task.sub_tasks_count === 0) { - task.show_sub_tasks = true; - task.sub_tasks = []; - return; - } - - task.sub_tasks_loading = true; - task.show_sub_tasks = !task.show_sub_tasks; - if (task.show_sub_tasks) { - task.sub_tasks = await this.getSubTasks(task); - for (const t of task.sub_tasks) { - this.map.add(groupId, t); - } - } else { - for (const t of task.sub_tasks || []) { - this.map.deselectTask(t); - } - task.sub_tasks = []; - } - - task.sub_tasks_loading = false; - - row.detectChanges(); - this.cdr.markForCheck(); - } - - protected toggleGroup(event: MouseEvent, panel: HTMLDivElement) { - this.ngZone.runOutsideAngular(() => { - const target = event.target as Element; - if (!target) return; - target.closest('.btn')?.classList.toggle("active"); - const maxHeight = panel.style.maxHeight === "0px" ? panel.scrollHeight + 8 + 'px' : "0px"; - this.renderer.setStyle(panel, "max-height", maxHeight); - }); - } - - protected trackById(index: number, item: IProjectTask | ITaskListGroup) { - return item.id; - } - - protected onDrop(event: CdkDragDrop) { - const fromIndex = event.previousIndex; - const toIndex = event.currentIndex; - - const fromGroup = event.previousContainer.data; - const toGroup = event.container.data; - - // collapse button icon rotate - if (fromGroup.tasks.length == 0) { - this.ngZone.runOutsideAngular(() => { - const buttonElement = document.getElementById(`${event.previousContainer.id}`)?.closest("div")?.parentNode?.parentNode?.parentNode?.querySelector("button.collapse.active"); - buttonElement?.classList.remove('active'); - }); - } - - const task = event.item.data; - - const toPos = toGroup.tasks[toIndex]?.sort_order; - - this.socket.emit(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), { - project_id: this.service.getProjectId(), - from_index: fromGroup.tasks[fromIndex].sort_order, - to_index: toPos || toGroup.tasks[toGroup.tasks.length - 1]?.sort_order || -1, - to_last_index: !toPos, - from_group: fromGroup.id, - to_group: toGroup.id, - group_by: this.service.getCurrentGroup().value, - task, - team_id: this.auth.getCurrentSession()?.team_id - }); - - this.socket.once(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), () => { - this.socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), task.id); - }); - - if (fromGroup.id === toGroup.id) { // same group - moveItemInArray(event.container.data.tasks, fromIndex, toIndex); - } else { - transferArrayItem( - event.previousContainer.data.tasks, - event.container.data.tasks, - event.previousIndex, - event.currentIndex - ); - - this.map.remove(task); - this.map.add(toGroup.id, task); - - this.service.emitGroupChange(toGroup.id, task.id as string, toGroup.color_code); - } - - this.kanbanService.emitRefreshGroups(); - } - - protected openTask(task: IProjectTask) { - this.selectedTask = task; - this.showTaskModal = true; - this.cdr.markForCheck(); - } - - protected async bulkUpdateSuccess() { - await this.getGroups(true); - } - - protected async onStatusCreateOrUpdate() { - await this.getStatuses(); - await this.getGroups(false); - } - - protected toggleTaskTemplateDrawer(event: any) { - this.showTaskTemplatesDrawer = true; - } - - protected onTaskTemplateCreate() { - this.showTaskTemplatesDrawer = false; - this.map.deselectAll(); - } - - protected taskTemplateCancel(event: any) { - this.showTaskTemplatesDrawer = event; - } - - protected selectTasksInGroup(checked: boolean, group: ITaskListGroup) { - for (const task of group.tasks) { - if (checked) { - this.map.selectTask(task); - } else { - this.map.deselectTask(task); - } - } - } - - private mapTasks(groups: ITaskListGroup[]) { - for (const group of groups) { - this.map.registerGroup(group); - for (const task of group.tasks) { - if (task.start_date) task.start_date = new Date(task.start_date) as any; - if (task.end_date) task.end_date = new Date(task.end_date) as any; - } - } - } - - private toggleFocusCls(focused: boolean, element: HTMLElement) { - if (focused) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - } - - onShowChange(show: boolean) { - if (!show) { - this.selectedTask = null; - } - } - - private handleNewTaskReceive(value: ITaskListGroupChangeResponse) { - if (value.isSubTask) { - const row = this.rows - .find(r => r.id === value.taskId); - if (row) { - row.detectChanges(); - } - } - - this.cdr.markForCheck(); - } - - private handleSortOrderResponse = (tasks: IProjectTask[]) => { - for (const element of tasks) { - const taskId = element.id; - if (taskId) { - const task = this.map.tasks.get(taskId); - if (task) { - task.sort_order = element.sort_order; - task.completed_at = element.completed_at; - this.map.tasks.set(taskId, task); - } - } - } - }; - - private closeTask() { - this.showTaskModal = false; - this.selectedTask = null; - this.cdr.detectChanges(); - } - - private async handleTaskSelectFromView(task: IProjectTask) { - this.closeTask(); - if (task) { - await waitForSeconds(); - this.openTask(task); - } - } - - protected handleFocusChange(focused: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - this.toggleFocusCls(focused, element); - }); - } - - protected quickTaskFocusChange(focused: boolean, td: HTMLElement, _ref: TaskListAddTaskInputComponent) { - this.ngZone.runOutsideAngular(() => { - this.toggleFocusCls(focused, td); - }); - } - - protected async onGroupByChange(group: IGroupByOption) { - this.service.setCurrentGroup(group); - this.groupChanging = true; - await this.getGroups(true); - setTimeout(() => { - this.groupChanging = false; - this.cdr.markForCheck(); - }, 100); // wait for animations to be finished - } - - protected async onArchiveChange() { - this.loadingGroups = true; - this.loadingArchived = true; - this.service.groups = []; - - await this.getGroups(true); - this.loadingArchived = false; - this.cdr.markForCheck(); - } - - async handleFilterByMember(filterBody: IMembersFilterChange) { - this.loadingFiltering = true; - this.membersFilterString = filterBody.selection; - - if (filterBody.is_subtasks_included) { - this.service.isSubtasksIncluded = true; - } else { - this.service.isSubtasksIncluded = false; - } - - await this.getGroups(true); - this.loadingFiltering = false; - this.cdr.markForCheck(); - } - - async handleFilterByLabel(filterString: string) { - this.loadingFiltering = true; - this.labelsFilterString = filterString; - await this.getGroups(true); - this.loadingFiltering = false; - this.cdr.markForCheck(); - } - - async handleFilterByPriority(filterString: string) { - this.loadingFiltering = true; - this.priorityFilterString = filterString; - await this.getGroups(true); - this.loadingFiltering = false; - this.cdr.markForCheck(); - } - - async handleFilterSortBy(filterString: string) { - this.loadingFiltering = true; - this.sortField = filterString; - await this.getGroups(true); - this.loadingFiltering = false; - this.cdr.markForCheck(); - } - - async handleFilterSearch(searchText: string | null) { - this.loadingFiltering = true; - this.searchValue = searchText; - await this.getGroups(true); - this.loadingFiltering = false; - this.cdr.markForCheck(); - } - - /// API Calls - private async getGroups(loading = true) { - if (!this.projectId) return; - try { - this.map.deselectAll(); - this.loadingGroups = loading; - const config = this.getConf(); - config.isSubtasksInclude = this.service.isSubtasksIncluded; - const res = await this.api.getTaskListV2(config) as IServerResponse; - if (res.done) { - const groups = deepClone(res.body); - this.groupIds = groups.map((g: ITaskListGroup) => g.id); - this.mapTasks(groups); - this.service.groups = groups; - } - this.loadingGroups = false; - } catch (e) { - this.loadingGroups = false; - } - - this.cdr.markForCheck(); - } - - private async getSubTasks(task: IProjectTask) { - let subTasks: IProjectTask[] = []; - if (task?.id) { - try { - const config = this.getConf(task.id); - const res = await this.api.getTaskListV2(config) as IServerResponse; - if (res.done) subTasks = res.body; - } catch (e) { - // ignored - } - } - return subTasks; - } - - private async getColumns() { - if (!this.projectId) return; - try { - this.loadingColumns = true; - const res = await this.api.getListCols(this.projectId); - if (res.done) - this.service.columns = res.body; - this.loadingColumns = false; - } catch (e) { - this.loadingColumns = false; - } - } - - private async getTeamMembers() { - try { - this.loadingMembers = true; - const res = await this.tmApi.getAll(this.projectId); - if (res.done) - this.service.members = res.body; - this.loadingMembers = false; - } catch (e) { - this.loadingMembers = false; - } - } - - protected async getLabels() { - try { - this.loadingLabels = true; - const res = await this.labelsApi.get(this.projectId); - if (res.done) - this.service.labels = res.body; - this.loadingLabels = false; - } catch (e) { - this.loadingLabels = false; - } - } - - private async getStatuses() { - if (!this.projectId) return; - try { - this.loadingStatuses = true; - const res = await this.statusesApi.get(this.projectId); - if (res.done) - this.service.statuses = res.body; - this.loadingStatuses = false; - } catch (e) { - this.loadingStatuses = false; - } - } - - private async getPriorities() { - try { - this.loadingPriorities = true; - const res = await this.prioritiesApi.get(); - if (res.done) - this.service.priorities = res.body; - this.loadingPriorities = false; - } catch (e) { - this.loadingPriorities = false; - } - } - - private async getCategories() { - try { - this.loadingCategories = true; - const res = await this.statusesApi.getCategories(); - if (res.done) - this.categories = res.body; - this.loadingCategories = false; - } catch (e) { - this.loadingCategories = false; - } - } - - private async getPhases() { - if (!this.projectId) return; - try { - this.loadingPhases = true; - const res = await this.phasesApi.get(this.projectId); - if (res.done) - this.service.phases = res.body; - this.loadingPhases = false; - } catch (e) { - this.loadingPhases = false; - } - } - - @HostListener('scroll', ['$event.target']) - onScroll(target: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - const cls = 'scrolling-panel'; - this.scrollBy = target.scrollLeft; - if (this.scrollBy > 0) { - target.classList.add(cls) - } else { - target.classList.remove(cls) - } - }); - } - - // eslint-disable-next-line @typescript-eslint/ban-types - debounce(func: Function, delay: number) { - let timeoutId: any; - return (...args: any[]) => { - clearTimeout(timeoutId); - timeoutId = setTimeout(() => func.apply(this, args), delay); - }; - } - - openAddColumnDrawer() { - this.displayPhaseModal = true; - } - - openStatusSettingsDrawer() { - this.showStatusModal = true; - } - - public async refreshWithoutLoad() { - await this.getGroups(false); - } - -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-v2-routing.module.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-v2-routing.module.ts deleted file mode 100644 index ff66f539..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-v2-routing.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {TaskListTableComponent} from "./task-list-table/task-list-table.component"; - -const routes: Routes = [ - {path: "", component: TaskListTableComponent} -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class TaskListV2RoutingModule { -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-v2.module.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-v2.module.ts deleted file mode 100644 index 5fd5a165..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-v2.module.ts +++ /dev/null @@ -1,195 +0,0 @@ -import {TaskTemplateDrawerComponent} from "@admin/components/task-template-drawer/task-template-drawer.component"; -import {TaskViewModule} from "@admin/components/task-view/task-view.module"; -import {CdkDrag, CdkDragHandle, CdkDragPlaceholder, CdkDropList, CdkDropListGroup} from "@angular/cdk/drag-drop"; -import {CdkFixedSizeVirtualScroll, CdkVirtualForOf, ScrollingModule} from "@angular/cdk/scrolling"; -import {CommonModule, NgOptimizedImage} from '@angular/common'; -import {NgModule} from '@angular/core'; -import {FormsModule, ReactiveFormsModule} from "@angular/forms"; -import {EllipsisPipe} from "@pipes/ellipsis.pipe"; -import {FirstCharUpperPipe} from "@pipes/first-char-upper.pipe"; -import {FromNowPipe} from "@pipes/from-now.pipe"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {WithAlphaPipe} from "@pipes/with-alpha.pipe"; -import {WlSafeArrayPipe} from "@pipes/wl-safe-array.pipe"; -import {RxFor} from "@rx-angular/template/for"; -import {NzAvatarModule} from "ng-zorro-antd/avatar"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzCheckboxModule} from "ng-zorro-antd/checkbox"; -import {NzDatePickerModule} from "ng-zorro-antd/date-picker"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {NzEmptyModule} from "ng-zorro-antd/empty"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NzListModule} from "ng-zorro-antd/list"; -import {NzPopconfirmModule} from "ng-zorro-antd/popconfirm"; -import {NzPopoverModule} from "ng-zorro-antd/popover"; -import {NzProgressModule} from "ng-zorro-antd/progress"; -import {NzSelectModule} from "ng-zorro-antd/select"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzSpinModule} from "ng-zorro-antd/spin"; -import {NzTableModule} from "ng-zorro-antd/table"; -import {NzTagModule} from "ng-zorro-antd/tag"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {AvatarsComponent} from "../../components/avatars/avatars.component"; -import {TaskPriorityLabelComponent} from "../../components/task-priority-label/task-priority-label.component"; -import {TaskTimerComponent} from "../../components/task-timer/task-timer.component"; -import {EndNameCheckPipe} from './pipes/end-name-check.pipe'; -import {TaskListColumnsToggleComponent} from './task-list-columns-toggle/task-list-columns-toggle.component'; -import {TaskListFiltersComponent} from './task-list-filters/task-list-filters.component'; -import {TaskListBulkActionsComponent} from './task-list-table/task-list-bulk-actions/task-list-bulk-actions.component'; -import {TaskListHeaderComponent} from './task-list-table/task-list-header/task-list-header.component'; -import {SubTasksArrowColorPipe} from './task-list-table/task-list-row/pipes/sub-tasks-arrow-color.pipe'; -import {SubTasksArrowIconPipe} from './task-list-table/task-list-row/pipes/sub-tasks-arrow-icon.pipe'; -import {ValidateMinDatePipe} from './task-list-table/task-list-row/pipes/validate-min-date.pipe'; -import {TaskListLabelsComponent} from './task-list-table/task-list-row/task-list-labels/task-list-labels.component'; -import {TaskListMembersComponent} from './task-list-table/task-list-row/task-list-members/task-list-members.component'; -import { - TaskListPriorityComponent -} from './task-list-table/task-list-row/task-list-priority/task-list-priority.component'; -import {TaskListRowComponent} from './task-list-table/task-list-row/task-list-row.component'; -import {TaskListStatusComponent} from './task-list-table/task-list-row/task-list-status/task-list-status.component'; -import {TaskListTimerComponent} from './task-list-table/task-list-row/task-list-timer/task-list-timer.component'; -import {TaskProgressComponent} from './task-list-table/task-list-row/task-progress/task-progress.component'; -import {TaskListTableComponent} from './task-list-table/task-list-table.component'; - -import {TaskListV2RoutingModule} from './task-list-v2-routing.module'; -import {TruncateIfLongPipe} from './task-list-table/task-list-row/pipes/truncate-if-long.pipe'; -import {TaskListContextMenuComponent} from './task-list-table/task-list-context-menu/task-list-context-menu.component'; -import { - TaskListAddTaskInputComponent -} from './task-list-table/task-list-add-task-input/task-list-add-task-input.component'; -import {ValidateMaxDatePipe} from './task-list-table/task-list-row/pipes/validate-max-date.pipe'; -import {NzPipesModule} from "ng-zorro-antd/pipes"; -import { - TaskListStartDateComponent -} from './task-list-table/task-list-row/task-list-start-date/task-list-start-date.component'; -import { - TaskListEndDateComponent -} from './task-list-table/task-list-row/task-list-end-date/task-list-end-date.component'; -import { - TaskListDescriptionComponent -} from './task-list-table/task-list-row/task-list-description/task-list-description.component'; -import { - TaskListGroupSettingsComponent -} from './task-list-table/task-list-group-settings/task-list-group-settings.component'; -import {EllipsisTooltipTitlePipe} from './task-list-table/task-list-row/pipes/ellipsis-tooltip-title.pipe'; -import {NzDividerModule} from "ng-zorro-antd/divider"; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; -import { - ConvertToSubtaskModalComponent -} from "../../components/convert-to-subtask-modal/convert-to-subtask-modal.component"; -import {DateFormatterPipe} from "@pipes/date-formatter.pipe"; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {TaskListPhaseComponent} from './task-list-table/task-list-row/task-list-phase/task-list-phase.component'; -import { - TaskListPhaseSettingsDrawerComponent -} from './task-list-table/task-list-phase-settings-drawer/task-list-phase-settings-drawer.component'; -import {TasksProgressBarComponent} from "@admin/components/tasks-progress-bar/tasks-progress-bar.component"; -import { TaskListPhaseDurationComponent } from './task-list-table/task-list-phase-duration/task-list-phase-duration.component'; -import {StatusFormComponent} from "@admin/components/status-form/status-form.component"; - -@NgModule({ - declarations: [ - TaskListTableComponent, - TaskListFiltersComponent, - TaskProgressComponent, - TaskListMembersComponent, - TaskListLabelsComponent, - TaskListStatusComponent, - TaskListPriorityComponent, - TaskListTimerComponent, - EndNameCheckPipe, - TaskListHeaderComponent, - TaskListColumnsToggleComponent, - TaskListRowComponent, - SubTasksArrowColorPipe, - ValidateMinDatePipe, - SubTasksArrowIconPipe, - TaskListBulkActionsComponent, - TruncateIfLongPipe, - TaskListContextMenuComponent, - ValidateMaxDatePipe, - TaskListStartDateComponent, - TaskListEndDateComponent, - TaskListDescriptionComponent, - TaskListGroupSettingsComponent, - EllipsisTooltipTitlePipe, - TaskListPhaseComponent, - TaskListPhaseSettingsDrawerComponent, - TaskListPhaseDurationComponent - ], - exports: [ - TaskListTableComponent, - ValidateMaxDatePipe, - ValidateMinDatePipe, - TaskListRowComponent, - SubTasksArrowColorPipe, - SubTasksArrowIconPipe, - TruncateIfLongPipe - ], - imports: [ - CommonModule, - TaskListV2RoutingModule, - FormsModule, - NzFormModule, - NzButtonModule, - NzDropDownModule, - NzIconModule, - NzCheckboxModule, - NzTypographyModule, - NzBadgeModule, - NzInputModule, - NzAvatarModule, - NzToolTipModule, - CdkDropListGroup, - NzSkeletonModule, - CdkDropList, - NzTableModule, - NgOptimizedImage, - NzSpaceModule, - CdkDrag, - CdkDragHandle, - NzTagModule, - EllipsisPipe, - NzProgressModule, - AvatarsComponent, - SearchByNamePipe, - ReactiveFormsModule, - NzSelectModule, - TaskPriorityLabelComponent, - TaskTimerComponent, - NzPopoverModule, - NzEmptyModule, - NzListModule, - FromNowPipe, - NzDatePickerModule, - CdkVirtualForOf, - CdkFixedSizeVirtualScroll, - ScrollingModule, - WithAlphaPipe, - FirstCharUpperPipe, - WlSafeArrayPipe, - RxFor, - TaskViewModule, - TaskTemplateDrawerComponent, - NzPopconfirmModule, - NzSpinModule, - CdkDragPlaceholder, - NzPipesModule, - TaskListAddTaskInputComponent, - NzDividerModule, - SafeStringPipe, - ConvertToSubtaskModalComponent, - DateFormatterPipe, - NzDrawerModule, - TasksProgressBarComponent, - StatusFormComponent - ], - providers: [SearchByNamePipe] -}) -export class TaskListV2Module { -} diff --git a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-v2.service.ts b/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-v2.service.ts deleted file mode 100644 index 841ed83f..00000000 --- a/worklenz-frontend/src/app/administrator/modules/task-list-v2/task-list-v2.service.ts +++ /dev/null @@ -1,418 +0,0 @@ -import {Injectable} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITaskPrioritiesGetResponse} from "@interfaces/api-models/task-priorities-get-response"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {ITeamMemberViewModel} from "@interfaces/api-models/team-members-get-response"; -import {ITaskAssigneesUpdateResponse} from "@interfaces/task-assignee-update-response"; -import {ITaskLabel} from "@interfaces/task-label"; -import {ITaskListColumn} from "@interfaces/task-list-column"; -import {BehaviorSubject, Subject} from "rxjs"; -import {IGroupByOption, ITaskListContextMenuEvent, ITaskListGroup, ITaskListGroupChangeResponse} from "./interfaces"; -import {TaskListHashMapService} from "./task-list-hash-map.service"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskPhase} from "@interfaces/api-models/task-phase"; - -@Injectable({ - providedIn: 'root' -}) -export class TaskListV2Service { - private readonly colsSbj$ = new Subject(); - private readonly membersSbj$ = new Subject(); - private readonly labelsSbj$ = new Subject(); - private readonly statusesSbj$ = new Subject(); - private readonly prioritiesSbj$ = new Subject(); - private readonly contextMenuSbj$ = new Subject(); - private readonly assignMeSbj$ = new Subject(); - private readonly taskAddOrDeleteSbj$ = new BehaviorSubject(null); - private readonly refreshSbj$ = new Subject(); - private readonly groupChangeSbj$ = new Subject<{ groupId: string; taskId: string; color: string; }>(); - private readonly inviteMemberSbj$ = new Subject(); - private readonly phasesSbj$ = new Subject(); - private readonly updateGroupProgressSbj$ = new Subject<{ taskId: string; }>(); - private readonly refreshSubtasksIncludedSbj$ = new Subject(); - - public readonly HIGHLIGHT_COL_CLS = 'highlight-col'; - - public readonly COLUMN_KEYS = { - KEY: "KEY", - NAME: "NAME", - DESCRIPTION: "DESCRIPTION", - PROGRESS: "PROGRESS", - ASSIGNEES: "ASSIGNEES", - LABELS: "LABELS", - STATUS: "STATUS", - PRIORITY: "PRIORITY", - TIME_TRACKING: "TIME_TRACKING", - ESTIMATION: "ESTIMATION", - START_DATE: "START_DATE", - DUE_DATE: "DUE_DATE", - COMPLETED_DATE: "COMPLETED_DATE", - CREATED_DATE: "CREATED_DATE", - LAST_UPDATED: "LAST_UPDATED", - REPORTER: "REPORTER", - PHASE: "PHASE" - }; - - public readonly GROUP_BY_STATUS_VALUE = "status"; - public readonly GROUP_BY_PRIORITY_VALUE = "priority"; - public readonly GROUP_BY_PHASE_VALUE = "phase"; - public readonly GROUP_BY_OPTIONS: IGroupByOption[] = [ - {label: "Status", value: this.GROUP_BY_STATUS_VALUE}, - {label: "Priority", value: this.GROUP_BY_PRIORITY_VALUE}, - {label: "Phase", value: this.GROUP_BY_PHASE_VALUE} - ]; - - public groups: ITaskListGroup[] = []; - - private _projectId: string | null = null; - public _cols: ITaskListColumn[] = []; - private _members: ITeamMemberViewModel[] = []; - private _labels: ITaskLabel[] = []; - private _statuses: ITaskStatusViewModel[] = []; - private _priorities: ITaskPrioritiesGetResponse[] = []; - private _phases: ITaskPhase[] = []; - - public isSubtasksIncluded = false; - - private get _currentGroup(): IGroupByOption { - const key = localStorage.getItem("worklenz.tasklist.group_by"); - if (key) { - const g = this.GROUP_BY_OPTIONS.find(o => o.value === key); - if (g) - return g; - } - return this.GROUP_BY_OPTIONS[0]; - } - - private set _currentGroup(option) { - localStorage.setItem("worklenz.tasklist.group_by", option.value); - } - - public set columns(value) { - this._cols = value; - this.emitColsChange(); - } - - public get columns() { - return this._cols; - } - - public set members(value) { - this._members = value; - this.membersSbj$.next(); - } - - public get members() { - return this._members; - } - - public set labels(value) { - this._labels = value; - this.labelsSbj$.next(); - } - - public get labels() { - return this._labels; - } - - public set priorities(value) { - this._priorities = value; - this.prioritiesSbj$.next(); - } - - public get priorities() { - return this._priorities; - } - - public set phases(value) { - this._phases = value; - this.phasesSbj$.next(); - } - - public get phases() { - return this._phases; - } - - get onColumnsChange$() { - return this.colsSbj$.asObservable(); - } - - get onMembersChange$() { - return this.membersSbj$.asObservable(); - } - - get onLabelsChange$() { - return this.labelsSbj$.asObservable(); - } - - get onStatusesChange$() { - return this.statusesSbj$.asObservable(); - } - - get onPrioritiesChange$() { - return this.prioritiesSbj$.asObservable(); - } - - get onContextMenu$() { - return this.contextMenuSbj$.asObservable(); - } - - get onAssignMe$() { - return this.assignMeSbj$.asObservable(); - } - - get onTaskAddOrDelete$() { - return this.taskAddOrDeleteSbj$.asObservable(); - } - - get onGroupChange$() { - return this.groupChangeSbj$.asObservable(); - } - - get onRefresh$() { - return this.refreshSbj$.asObservable(); - } - - get onInviteClick$() { - return this.inviteMemberSbj$.asObservable(); - } - - get onPhaseChange$() { - return this.phasesSbj$.asObservable(); - } - - get onGroupProgressChangeDone$() { - return this.updateGroupProgressSbj$.asObservable(); - } - - set statuses(value) { - this._statuses = value; - this.statusesSbj$.next(); - } - - get statuses() { - return this._statuses; - } - - get onRefreshSubtasksIncluded() { - return this.refreshSubtasksIncludedSbj$.asObservable(); - } - - constructor( - private readonly socket: Socket, - private readonly map: TaskListHashMapService - ) { - } - - public canActive(key: string) { - return !!this.columns.find(c => c.key === key)?.pinned; - } - - public setProjectId(id: string) { - this._projectId = id; - } - - public getProjectId() { - return this._projectId; - } - - public setCurrentGroup(group: IGroupByOption) { - this._currentGroup = group; - } - - public getCurrentGroup() { - return this._currentGroup; - } - - public emitColsChange() { - this.colsSbj$.next(); - } - - public emitOnContextMenu(event: MouseEvent, task: IProjectTask) { - this.contextMenuSbj$.next({event, task}); - } - - public emitOnAssignMe(res: ITaskAssigneesUpdateResponse) { - this.assignMeSbj$.next(res); - } - - public emitRefresh() { - this.refreshSbj$.next(); - } - - public emitGroupChange(groupId: string, taskId: string, color: string) { - this.groupChangeSbj$.next({groupId, taskId, color}); - } - - public emitInviteMembers() { - this.inviteMemberSbj$.next(); - } - - public emitTaskAddOrDelete(taskId: string, isSubTask: boolean) { - this.taskAddOrDeleteSbj$.next({ - taskId: taskId, - isSubTask: isSubTask - }); - } - - public emitUpdateGroupProgress(taskId: string) { - this.updateGroupProgressSbj$.next({taskId}); - } - - public emitRefreshSubtasksIncluded() { - this.refreshSubtasksIncludedSbj$.next(); - } - - public getGroupIdByGroupedColumn(task: IProjectTask) { - const groupBy = this.getCurrentGroup().value; - if (groupBy === this.GROUP_BY_STATUS_VALUE) - return task.status as string; - - if (groupBy === this.GROUP_BY_PRIORITY_VALUE) - return task.priority as string; - - if (groupBy === this.GROUP_BY_PHASE_VALUE) - return task.phase_id as string; - - return null; - } - - /** - * Updates the task group for a given task. - * - * @param {IProjectTask} task - The task to update the group for. - * @param {boolean} insert - Add the task to the beginning of the array - * @returns {void} - */ - public updateTaskGroup(task: IProjectTask, insert = true) { - if (!task.id) return; - - /** - * Retrieves the group ID based on the grouped column of the task. - * - * @param {IProjectTask} task - The task to determine the group ID for. - * @returns {string | null} The group ID if found, or null if not applicable. - */ - const groupId = this.getGroupIdByGroupedColumn(task); - - if (groupId) { - // Delete the task from its current group - this.deleteTask(task.id); - - // Add the task to the new group - this.addTask(task, groupId, insert); - this.emitUpdateGroupProgress(task.id); - } - } - - /** - * Removes a task from the task list array, and also this performs delete from map - * @param taskId The id of the task to remove. - * @param index Task to be removed - */ - public deleteTask(taskId: string, index: number | null = null) { - // Get the group id of the task. - const groupId = this.map.getGroupId(taskId); - if (!groupId || !taskId) return; - - // Find the group that contains the task. - const group = this.groups.find(g => g.id === groupId); - if (!group) return; - - // Get the task object from the list of selected tasks. - const task = this.map.getSelectedTasks().find(t => t.id === taskId); - // If the task is a sub-task, remove it from its parent task's sub-tasks list. - if (task?.is_sub_task) { - const parentTask = group.tasks.find(t => t.id === task.parent_task_id); - if (parentTask) { - // Find the index of the sub-task in the parent task's sub-tasks list. - const index = parentTask.sub_tasks?.findIndex(t => t.id === task.id); - if (typeof index !== "undefined" && index !== -1) { - if (!parentTask.sub_tasks_count) parentTask.sub_tasks_count = 0; - parentTask.sub_tasks_count = Math.max(+parentTask.sub_tasks_count - 1, 0); - parentTask.sub_tasks?.splice(index, 1); - this.emitTaskAddOrDelete(parentTask.id as string, true); - } - } - this.socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), parentTask?.id); - this.map.remove(task); - } else { // If the task is not a sub-task, remove it from the group's task list. - const taskIndex = index ?? group.tasks.findIndex(t => t.id === taskId); - if (taskIndex !== -1) { - this.map.remove(group.tasks[taskIndex]); - group.tasks.splice(taskIndex, 1); - this.emitTaskAddOrDelete(taskId, false); - } - } - - // Deselect all tasks after removing the task. - this.map.deselectAll(); - } - - public removeSubtask(taskId: string, index: number | null = null) { - const groupId = this.map.getGroupId(taskId); - if (!groupId || !taskId) return; - - // Find the group that contains the task. - const group = this.groups.find(g => g.id === groupId); - if (!group) return; - - const taskIndex = index ?? group.tasks.findIndex(t => t.id === taskId); - if (taskIndex !== -1) { - this.map.remove(group.tasks[taskIndex]); - group.tasks.splice(taskIndex, 1); - } - - this.map.deselectAll(); - } - - /** - * Adds a new task to the specified group. - * If the task is a sub-task, it is added to the corresponding parent task's sub-task array. - * Also adds to map - * @param task The task to add - * @param groupId The ID of the group to add the task to - * @param insert Add the element to the beginning of the array - */ - public addTask(task: IProjectTask, groupId: string, insert = false) { - const group = this.groups.find(g => g.id === groupId); - if (group && task.id) { - // If the task is a sub-task - if (task.parent_task_id) { - // Find the parent task - const parentTask = group.tasks.find(t => t.id === task.parent_task_id); - if (parentTask) { - if (!parentTask.sub_tasks_count) parentTask.sub_tasks_count = 0; - parentTask.sub_tasks_count = +parentTask.sub_tasks_count + 1; - // Add the task to the parent task's sub-task array - parentTask.sub_tasks?.push(task); - - this.socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), parentTask?.id); - } - } else { // If the task is not a sub-task - // Add the task to the group's task array - if (insert) { - group.tasks.unshift(task); - } else { - group.tasks.push(task); - } - } - - this.map.add(groupId, task); - this.emitTaskAddOrDelete(task.parent_task_id as string, !!task.parent_task_id); - } - } - - public reset() { - this._cols = []; - this._members = []; - this._labels = []; - this._statuses = []; - this._priorities = []; - - this._projectId = null; - this.groups = []; - this.isSubtasksIncluded = false; - } -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/activity-log/activity-log.component.html b/worklenz-frontend/src/app/administrator/my-dashboard/activity-log/activity-log.component.html deleted file mode 100644 index f4e456fb..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/activity-log/activity-log.component.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - {{item.description}} - - - - - - - diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/activity-log/activity-log.component.scss b/worklenz-frontend/src/app/administrator/my-dashboard/activity-log/activity-log.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/activity-log/activity-log.component.spec.ts b/worklenz-frontend/src/app/administrator/my-dashboard/activity-log/activity-log.component.spec.ts deleted file mode 100644 index 599246a1..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/activity-log/activity-log.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ActivityLogComponent} from './activity-log.component'; - -describe('ActivityLogComponent', () => { - let component: ActivityLogComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ActivityLogComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ActivityLogComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/activity-log/activity-log.component.ts b/worklenz-frontend/src/app/administrator/my-dashboard/activity-log/activity-log.component.ts deleted file mode 100644 index 51c37560..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/activity-log/activity-log.component.ts +++ /dev/null @@ -1,58 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit} from '@angular/core'; -import {LogsApiService} from "@api/logs-api.service"; -import {IActivityLog} from "@interfaces/personal-overview"; - -@Component({ - selector: 'worklenz-activity-log', - templateUrl: './activity-log.component.html', - styleUrls: ['./activity-log.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ActivityLogComponent implements OnInit { - loading = false; - activityLog: IActivityLog[] = []; - private readonly key = "my-dashboard-log-active"; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: LogsApiService - ) { - } - - get active() { - if (localStorage.getItem(this.key) === null) { - this.active = true; - return true; - } - return localStorage.getItem(this.key) === "1"; - } - - set active(value: boolean) { - localStorage.setItem(this.key, value ? "1" : "0"); - } - - ngOnInit(): void { - void this.getActivityLog(); - } - - async getActivityLog() { - if (!this.active) return; - try { - this.loading = true; - const res = await this.api.getActivityLog(); - if (res.done) { - this.activityLog = res.body; - } - this.loading = false; - } catch (e) { - this.loading = false; - } - - this.cdr.markForCheck(); - } - - onActiveChange(active: boolean) { - this.active = active; - void this.getActivityLog(); - } -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/dashboard.component.html b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/dashboard.component.html deleted file mode 100644 index 9c926fd1..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/dashboard.component.html +++ /dev/null @@ -1,58 +0,0 @@ -
-
-
-
- -

Hi {{name}}, {{greetingTime}}!

-
-

Today - is {{currentDate | date:'EEEE, MMMM d, y'}}

-
-
- - - - - - -
    -
  • - - Import from template -
  • -
-
-
-
-
- -
-
-
- -
-
-
-
- -
-
- -
-
-
-
-
-
-
- - - diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/dashboard.component.scss b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/dashboard.component.scss deleted file mode 100644 index 538280de..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/dashboard.component.scss +++ /dev/null @@ -1,37 +0,0 @@ -.home-top-row { - margin-top: 24px; - margin-bottom: 24px; -} - -.home-top-action { - position: absolute; - right: 0; - bottom: 1em; -} - -.home-name-date h3, .home-name-date h4 { - font-weight: 500; - margin-bottom: 0.25em; -} - -.home-name-date h4 { - color: #1890FF; - font-size: 16px; - line-height: 24px; - font-weight: 400; -} - -.dashboard-main-card { - border-radius: 4px; - border: 1px solid #f0f0f0; - - &.tasks-card { - border: 1px solid transparent; - box-shadow: rgb(122 122 122 / 15%) 0px 5px 16px; - } -} - -.my-dashboard { - max-width: 1320px; - width: 100%; -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/dashboard.component.spec.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/dashboard.component.spec.ts deleted file mode 100644 index 883c7a62..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/dashboard.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {DashboardComponent} from './dashboard.component'; - -describe('DashboardComponent', () => { - let component: DashboardComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [DashboardComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(DashboardComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/dashboard.component.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/dashboard.component.ts deleted file mode 100644 index b4c81b50..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/dashboard.component.ts +++ /dev/null @@ -1,106 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, ViewChild} from '@angular/core'; -import {AppService} from "@services/app.service"; -import {AuthService} from "@services/auth.service"; -import {IProjectViewModel} from "@interfaces/api-models/project-view-model"; -import {ProjectFormModalComponent} from 'app/administrator/components/project-form-modal/project-form-modal.component'; -import {MyProjectsComponent} from './my-projects/my-projects.component'; -import {TaskListV2Service} from "../../modules/task-list-v2/task-list-v2.service"; -import { - ProjectTemplateImportDrawerComponent -} from "@admin/components/project-template-import-drawer/project-template-import-drawer.component"; -import {Router} from "@angular/router"; - -@Component({ - selector: 'worklenz-dashboard', - templateUrl: './dashboard.component.html', - styleUrls: ['./dashboard.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class DashboardComponent implements OnInit { - @ViewChild(ProjectFormModalComponent) projectsForm!: ProjectFormModalComponent; - @ViewChild(MyProjectsComponent) myProjects!: MyProjectsComponent; - @ViewChild(ProjectTemplateImportDrawerComponent) projectTemplateDrawer!: ProjectTemplateImportDrawerComponent; - - greetingTime!: string; - greetingTimer: number | null = null; - - loadingProjects = false; - - projects: IProjectViewModel[] = []; - - currentDate: Date = new Date(); - - get profile() { - return this.auth.getCurrentSession(); - } - - get name() { - const n = this.profile?.name; - const chunks = n?.split(' ') || []; - return chunks[0] || chunks.join(""); - } - - constructor( - private readonly app: AppService, - private readonly auth: AuthService, - private readonly listService: TaskListV2Service, - private readonly cdr: ChangeDetectorRef, - private readonly router: Router, - ) { - this.app.setTitle("Home"); - } - - ngOnInit() { - this.listService.setCurrentGroup(this.listService.GROUP_BY_OPTIONS[0]); - this.greetingTime = this.getGreetingTime(); - this.startGreetingTimer(); - } - - isOwnerOrAdmin() { - return this.profile?.owner || this.profile?.is_admin; - } - - openProjectForm(id?: string) { - this.projectsForm?.open(id, !!id); - this.cdr.markForCheck(); - } - - newProjectCreated() { - this.myProjects.getProjects(); - this.cdr.markForCheck(); - } - - getGreetingTime() { - const splitAfternoon = 12; // 24hr time to split the afternoon - const splitEvening = 17; // 24hr time to split the evening - const currentHour = new Date().getHours(); - - if (currentHour >= splitAfternoon && currentHour <= splitEvening) { - // Between 12 PM and 5PM - return 'Good afternoon'; - } else if (currentHour >= splitEvening) { - // Between 5PM and Midnight - return 'Good evening'; - } - // Between dawn and noon - return 'Good morning'; - } - - private startGreetingTimer() { - clearTimeout(this.greetingTimer as number); - clearInterval(this.greetingTimer as number); - setTimeout(() => { - this.greetingTime = this.getGreetingTime(); - }, 1000); - this.cdr.markForCheck(); - } - - openTemplateSelector() { - this.projectTemplateDrawer.open(); - this.cdr.markForCheck(); - } - - goToProjects(event: any) { - this.router.navigate([`/worklenz/projects/${event}`]); - } -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-projects/my-projects.component.html b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-projects/my-projects.component.html deleted file mode 100644 index 16100b86..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-projects/my-projects.component.html +++ /dev/null @@ -1,56 +0,0 @@ -
-
-
-

Projects ({{projects.length}})

-
-
- - - - -
-
-
- -
- - - - -
- - - - -

{{project.name}}

-
-
- - - -
- - -
- - -
- -
-
- You have not assigned to any project yet. -
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-projects/my-projects.component.scss b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-projects/my-projects.component.scss deleted file mode 100644 index 76c074ae..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-projects/my-projects.component.scss +++ /dev/null @@ -1,87 +0,0 @@ -.ant-card-bordered { - transition: 0.3s all; - border: 1px solid #e5e5e5; -} - -.ant-card-bordered:hover { - border: 1px solid #c5c5c5; -} - -tr { - border-bottom: 1px solid #F0F0F0; -} - -.ant-table table { - border-collapse: collapse !important; -} - -.ant-table-cell { - background: #fff !important; -} - -.text-grey { - color: #757B81; - font-size: 14px; - line-height: 25px; - margin-bottom: 0px !important; -} - -.no-data-img-holder { - width: 64px; - margin-top: 24px; -} - -.project-card { - width: 20%; - cursor: pointer; -} - -.font-400 { - font-weight: 400; -} - -nz-rate nz-icon { - font-size: 16px; -} - -.card-data { - padding: 24px; -} - -.projects-td { - padding: 4px 11px; - height: 44px; -} - -.card-top { - padding: 14px 24px; - border-bottom: 1px solid #f0f0f0; -} - -.card-title { - font-size: 16px; - font-weight: 500; -} - -.p-name { - cursor: pointer; - - &:hover { - & p { - color: #1890FF; - } - } -} - -.homepage-table { - overflow-y: auto; - max-height: 420px; -} - -th { - font-weight: 500; -} - -.card-data { - padding: 12px 24px; -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-projects/my-projects.component.spec.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-projects/my-projects.component.spec.ts deleted file mode 100644 index 8f37bf02..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-projects/my-projects.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {MyProjectsComponent} from './my-projects.component'; - -describe('MyProjectsComponent', () => { - let component: MyProjectsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [MyProjectsComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(MyProjectsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-projects/my-projects.component.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-projects/my-projects.component.ts deleted file mode 100644 index 42836f69..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-projects/my-projects.component.ts +++ /dev/null @@ -1,83 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit} from '@angular/core'; -import {Router} from '@angular/router'; -import {ProjectsApiService} from '@api/projects-api.service'; -import {IProjectViewModel} from '@interfaces/api-models/project-view-model'; -import {log_error} from '@shared/utils'; -import {HomePageApiService} from "@api/home-page-api.service"; - -@Component({ - selector: 'worklenz-my-projects', - templateUrl: './my-projects.component.html', - styleUrls: ['./my-projects.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class MyProjectsComponent implements OnInit { - projects: IProjectViewModel[] = []; - - loading = true; - options = ['Recent', 'Favorites']; - - private readonly myProjectsActiveFilterKey = "my-dashboard-active-projects-filter"; - - get activeFilter() { - return +(localStorage.getItem(this.myProjectsActiveFilterKey) || 0); - } - - set activeFilter(value: number) { - localStorage.setItem(this.myProjectsActiveFilterKey, value.toString()); - } - - constructor( - private readonly api: ProjectsApiService, - private readonly cdr: ChangeDetectorRef, - private readonly router: Router, - private readonly homePageApi: HomePageApiService - ) { - } - - ngOnInit() { - this.getProjects(); - } - - async getProjects() { - try { - this.loading = true; - const res = await this.homePageApi.getProjects(this.activeFilter); - if (res) { - this.projects = res.body; - } - this.loading = false; - } catch (e) { - log_error(e); - } - - this.cdr.markForCheck(); - } - - handleViewChange(index: number) { - this.activeFilter = index; - void this.getProjects(); - } - - async toggleFavorite(id?: string) { - if (!id) return; - try { - const res = await this.api.toggleFavorite(id); - if (res.done) - await this.getProjects(); - } catch (e) { - log_error(e); - } - } - - trackBy(index: number, item: IProjectViewModel) { - return item.id; - } - - selectProject(id: string) { - if (id) { - void this.router.navigate([`/worklenz/projects/${id}`]); - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-done/task-done.component.html b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-done/task-done.component.html deleted file mode 100644 index c7caa185..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-done/task-done.component.html +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-done/task-done.component.scss b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-done/task-done.component.scss deleted file mode 100644 index b361703d..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-done/task-done.component.scss +++ /dev/null @@ -1,16 +0,0 @@ -span { - color: #00000073; -} - -.green { - color: limegreen; -} - -button { - margin-left: 4px; - max-width: 20px; - overflow: hidden; - min-width: 20px; - max-height: 24px; - z-index: 9; -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-done/task-done.component.spec.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-done/task-done.component.spec.ts deleted file mode 100644 index 30d13a2f..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-done/task-done.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskDoneComponent} from './task-done.component'; - -describe('TaskDoneComponent', () => { - let component: TaskDoneComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskDoneComponent] - }); - fixture = TestBed.createComponent(TaskDoneComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-done/task-done.component.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-done/task-done.component.ts deleted file mode 100644 index a9d81d13..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-done/task-done.component.ts +++ /dev/null @@ -1,39 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from '@angular/core'; -import {IMyTask} from "@interfaces/my-tasks"; -import {HomePageApiService} from "@api/home-page-api.service"; -import {log_error} from "@shared/utils"; -import {HomepageService} from "../../../../homepage-service.service"; - -@Component({ - selector: 'worklenz-task-done', - templateUrl: './task-done.component.html', - styleUrls: ['./task-done.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskDoneComponent { - @Input() task: IMyTask | null = null; - loading = false; - - constructor( - private readonly api: HomePageApiService, - private readonly service: HomepageService, - private readonly cdr: ChangeDetectorRef, - ) { - } - - async markAsDone() { - if (!this.task?.id) return; - try { - this.loading = true; - const res = await this.api.taskMarkAsDone(this.task.id); - if (res) { - this.service.emitRemoveTaskFromList(res.body); - this.cdr.markForCheck(); - } - } catch (e) { - log_error(e) - } - this.loading = false; - this.cdr.markForCheck(); - } -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-due-date/task-due-date.component.html b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-due-date/task-due-date.component.html deleted file mode 100644 index 39c55b43..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-due-date/task-due-date.component.html +++ /dev/null @@ -1,17 +0,0 @@ -
- {{task.end_date | dateFormatter}} - - -
diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-due-date/task-due-date.component.scss b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-due-date/task-due-date.component.scss deleted file mode 100644 index 28e1ede5..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-due-date/task-due-date.component.scss +++ /dev/null @@ -1,30 +0,0 @@ -nz-date-picker { - max-width: 160px; - border-color: transparent !important; - top: 0; - bottom: 0; - left: 0; - right: 0; - - &:hover { - border-color: #d9d9d9 !important; - } -} - -.editable { - position: relative; -} - -.date-text { - display: flex; - align-items: center; - padding-left: 4px; -} - -.past-date { - color: #ff4d4f; -} - -.soon-date { - color: #52c41a; -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-due-date/task-due-date.component.spec.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-due-date/task-due-date.component.spec.ts deleted file mode 100644 index 87b17714..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-due-date/task-due-date.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskDueDateComponent} from './task-due-date.component'; - -describe('TaskDueDateComponent', () => { - let component: TaskDueDateComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskDueDateComponent] - }); - fixture = TestBed.createComponent(TaskDueDateComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-due-date/task-due-date.component.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-due-date/task-due-date.component.ts deleted file mode 100644 index 16b116d7..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-due-date/task-due-date.component.ts +++ /dev/null @@ -1,148 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, NgZone, OnDestroy, OnInit} from '@angular/core'; -import moment from "moment"; -import {IMyTask} from "@interfaces/my-tasks"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {HomepageService} from "../../../../homepage-service.service"; -import {formatGanttDate} from "@shared/utils"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-task-due-date', - templateUrl: './task-due-date.component.html', - styleUrls: ['./task-due-date.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskDueDateComponent implements OnInit, OnDestroy { - @Input() task: IMyTask | null = null; - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly homePageService: HomepageService, - private readonly auth: AuthService, - ) { - } - - ngOnInit() { - this.socket.on(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.handleResponse); - } - - private handleResponse = (response: { - id: string; - parent_task: string | null; - end_date: string; - }) => { - if (response.id === this.task?.id) { - this.task.end_date = response.end_date; - if (this.homePageService.tasksViewConfig) this.homePageService.emitGetTasksWithoutLoading(this.homePageService.tasksViewConfig); - this.cdr.markForCheck(); - } - }; - - handleEndDateChange(date: string, task: IMyTask) { - this.socket.emit( - SocketEvents.TASK_END_DATE_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - end_date: formatGanttDate(date) || null, - parent_task: task.parent_task_id, - time_zone: this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone - })); - } - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - // this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - // this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - checkForPastDate(endDate: any) { - const formattedEndDate = moment(endDate).format('YYYY-MM-DD'); - return formattedEndDate < moment().format('YYYY-MM-DD'); - } - - checkForSoonDate(endDate: any) { - const formattedEndDate = moment(endDate).format('YYYY-MM-DD'); - const tomorrow = moment().add(1, 'day').format('YYYY-MM-DD'); - return formattedEndDate === moment().format('YYYY-MM-DD') || formattedEndDate === tomorrow; - } - - changeTaskTab() { - if (!this.task?.id) return; - const taskToRemove = this.homePageService.tasksModel.tasks.findIndex(item => item.id === this.task?.id); - - if (this.homePageService.tasksViewConfig?.current_tab === 'All') { - return; - } - - if (!this.task.end_date) { - switch (this.homePageService.tasksViewConfig?.current_tab) { - case 'Today': - this.homePageService.tasksModel.tasks.splice(taskToRemove, 1) - break; - case 'Upcoming': - this.homePageService.tasksModel.tasks.splice(taskToRemove, 1) - break; - case 'Overdue': - this.homePageService.tasksModel.tasks.splice(taskToRemove, 1) - break; - } - return; - } - - if (this.task.end_date) { - const dateToCheck = new Date(this.task.end_date); - const today = new Date(); - today.setHours(0, 0, 0, 0); - - if (dateToCheck.toDateString() === today.toDateString()) { - switch (this.homePageService.tasksViewConfig?.current_tab) { - case 'Upcoming': - this.homePageService.tasksModel.tasks.splice(taskToRemove, 1) - break; - case 'Overdue': - this.homePageService.tasksModel.tasks.splice(taskToRemove, 1) - break; - case 'NoDueDate': - this.homePageService.tasksModel.tasks.splice(taskToRemove, 1) - break; - } - return; - } else if (dateToCheck > today) { - switch (this.homePageService.tasksViewConfig?.current_tab) { - case 'Today': - this.homePageService.tasksModel.tasks.splice(taskToRemove, 1) - break; - case 'Overdue': - this.homePageService.tasksModel.tasks.splice(taskToRemove, 1) - break; - case 'NoDueDate': - this.homePageService.tasksModel.tasks.splice(taskToRemove, 1) - break; - } - } else if (dateToCheck < today) { - switch (this.homePageService.tasksViewConfig?.current_tab) { - case 'Today': - this.homePageService.tasksModel.tasks.splice(taskToRemove, 1) - break; - case 'Upcoming': - this.homePageService.tasksModel.tasks.splice(taskToRemove, 1) - break; - case 'NoDueDate': - this.homePageService.tasksModel.tasks.splice(taskToRemove, 1) - break; - } - } - } - this.cdr.markForCheck(); - } -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-name/task-name.component.html b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-name/task-name.component.html deleted file mode 100644 index 66826934..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-name/task-name.component.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - {{task.name | ellipsis : 50}} - - - -{{task.name | ellipsis : 100}} - - - - -

Sub-task of {{task?.parent_task_name}}

-
diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-name/task-name.component.scss b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-name/task-name.component.scss deleted file mode 100644 index 0326eed3..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-name/task-name.component.scss +++ /dev/null @@ -1,14 +0,0 @@ -button { - position: absolute; - top: 0; - bottom: 0; - right: 0; - margin-top: auto; - margin-bottom: auto; - background: #edebf0 !important; - border: 1px solid #ededed; -} - -p { - max-width: max-content; -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-name/task-name.component.spec.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-name/task-name.component.spec.ts deleted file mode 100644 index 9c9d48b8..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-name/task-name.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskNameComponent} from './task-name.component'; - -describe('TaskNameComponent', () => { - let component: TaskNameComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskNameComponent] - }); - fixture = TestBed.createComponent(TaskNameComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-name/task-name.component.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-name/task-name.component.ts deleted file mode 100644 index 24bf3621..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-name/task-name.component.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - OnDestroy, - OnInit, - Output -} from '@angular/core'; -import {IMyTask} from "@interfaces/my-tasks"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {TaskListV2Service} from "../../../../../modules/task-list-v2/task-list-v2.service"; - -@Component({ - selector: 'worklenz-task-name', - templateUrl: './task-name.component.html', - styleUrls: ['./task-name.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskNameComponent implements OnInit, OnDestroy { - @Input() task: IMyTask | null = null; - @Output() onOpenTask = new EventEmitter(); - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly socket: Socket, - private readonly listService: TaskListV2Service - ) { - } - - ngOnInit(): void { - this.socket.on(SocketEvents.TASK_NAME_CHANGE.toString(), this.handleNameChangeResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_NAME_CHANGE.toString(), this.handleNameChangeResponse); - } - - openTask(task: IProjectTask) { - this.onOpenTask?.emit(task); - } - - private handleNameChangeResponse = (response: { id: string; parent_task: string; name: string; }) => { - if (!response || !this.task?.id) return; - if (this.task.id == response.id) { - this.task.name = response.name; - this.cdr.detectChanges(); - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-project/task-project.component.html b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-project/task-project.component.html deleted file mode 100644 index af16cad3..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-project/task-project.component.html +++ /dev/null @@ -1,8 +0,0 @@ -
-
- -
-
-

{{task.project_name | ellipsis: 20}}

-
-
diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-project/task-project.component.scss b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-project/task-project.component.scss deleted file mode 100644 index 1b8e2649..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-project/task-project.component.scss +++ /dev/null @@ -1,4 +0,0 @@ -p { - color: rgba(0, 0, 0, 0.65); - font-size: 13px; -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-project/task-project.component.spec.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-project/task-project.component.spec.ts deleted file mode 100644 index f6654253..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-project/task-project.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskProjectComponent} from './task-project.component'; - -describe('TaskProjectComponent', () => { - let component: TaskProjectComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskProjectComponent] - }); - fixture = TestBed.createComponent(TaskProjectComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-project/task-project.component.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-project/task-project.component.ts deleted file mode 100644 index e4677468..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-project/task-project.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; -import {IMyTask} from "@interfaces/my-tasks"; - -@Component({ - selector: 'worklenz-task-project', - templateUrl: './task-project.component.html', - styleUrls: ['./task-project.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskProjectComponent { - @Input() task: IMyTask | null = null; -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-status/task-status.component.html b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-status/task-status.component.html deleted file mode 100644 index 281ddadc..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-status/task-status.component.html +++ /dev/null @@ -1,13 +0,0 @@ -
- - - - - -
diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-status/task-status.component.scss b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-status/task-status.component.scss deleted file mode 100644 index 6bc7859f..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-status/task-status.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -nz-select { - max-width: 100px; -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-status/task-status.component.spec.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-status/task-status.component.spec.ts deleted file mode 100644 index 63d8b742..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-status/task-status.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskStatusComponent} from './task-status.component'; - -describe('TaskStatusComponent', () => { - let component: TaskStatusComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskStatusComponent] - }); - fixture = TestBed.createComponent(TaskStatusComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-status/task-status.component.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-status/task-status.component.ts deleted file mode 100644 index d021d138..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/task-status/task-status.component.ts +++ /dev/null @@ -1,71 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {IMyTask} from "@interfaces/my-tasks"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskListStatusChangeResponse} from "@interfaces/task-list-status-change-response"; -import {Subject} from "rxjs"; -import {HomepageService} from "../../../../homepage-service.service"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-task-status', - templateUrl: './task-status.component.html', - styleUrls: ['./task-status.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskStatusComponent implements OnInit, OnDestroy { - @Input() task: IMyTask | null = null; - - loading = false; - - statuses: ITaskStatusViewModel[] = []; - - private readonly destroy$ = new Subject(); - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly homePageService: HomepageService, - private readonly auth: AuthService, - ) { - } - - ngOnInit() { - this.socket.on(SocketEvents.TASK_STATUS_CHANGE.toString(), this.handleResponse); - if (this.task?.project_statuses) { - this.statuses = this.task.project_statuses; - } - this.cdr.markForCheck(); - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - this.socket.removeListener(SocketEvents.TASK_STATUS_CHANGE.toString(), this.handleResponse); - } - - trackById(index: number, item: ITaskStatusViewModel) { - return item.id; - } - - handleStatusChange(statusId: string, taskId?: string) { - if (!taskId) return; - this.socket.emit(SocketEvents.TASK_STATUS_CHANGE.toString(), JSON.stringify({ - task_id: taskId, - status_id: statusId, - parent_task: this.task?.parent_task_id, -team_id: this.auth.getCurrentSession()?.team_id - })); - } - - private handleResponse = (response: ITaskListStatusChangeResponse) => { - if (!this.task) return; - if (response && response.id === this.task.id) { - this.task.status_color = response.color_code.slice(0, -2); - this.task.status = response.status_id; - if (this.homePageService.tasksViewConfig) this.homePageService.emitGetTasksWithoutLoading(this.homePageService.tasksViewConfig); - this.cdr.markForCheck(); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/task-add-container/task-add-container.component.html b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/task-add-container/task-add-container.component.html deleted file mode 100644 index cba299b9..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/task-add-container/task-add-container.component.html +++ /dev/null @@ -1,95 +0,0 @@ -
- - - - - - - - - - - - - - - - - - -
- Press Tab to select a 'Due date' and a 'Project' . - Press Enter to create. -
-
- -
- - - - - - - - - - - - - - - - -
- Press Tab to select a 'Project' . - Press Enter to create. -
-
diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/task-add-container/task-add-container.component.scss b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/task-add-container/task-add-container.component.scss deleted file mode 100644 index 94af0139..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/task-add-container/task-add-container.component.scss +++ /dev/null @@ -1,24 +0,0 @@ -.add-task-container { - background: #fff; - padding-left: 4px; - padding-right: 4px; -} - -input { - max-width: 360px; - min-width: 360px; -} - -@media (max-width: 1400px) { - input { - max-width: 300px; - min-width: 300px; - } -} - -@media (max-width: 1280px) { - input { - max-width: 250px; - min-width: 250px; - } -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/task-add-container/task-add-container.component.spec.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/task-add-container/task-add-container.component.spec.ts deleted file mode 100644 index f824cb7f..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/task-add-container/task-add-container.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskAddContainerComponent} from './task-add-container.component'; - -describe('TaskAddContainerComponent', () => { - let component: TaskAddContainerComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskAddContainerComponent] - }); - fixture = TestBed.createComponent(TaskAddContainerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/task-add-container/task-add-container.component.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/task-add-container/task-add-container.component.ts deleted file mode 100644 index 1143105b..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/task-add-container/task-add-container.component.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - Input, - OnInit, - ViewChild -} from '@angular/core'; -import {log_error} from "@shared/utils"; -import {AuthService} from "@services/auth.service"; -import {HomePageApiService} from "@api/home-page-api.service"; -import {HomepageService} from "../../../../../homepage-service.service"; -import {NzSelectComponent} from "ng-zorro-antd/select"; -import {IProject} from "@interfaces/project"; -import {IHomeTaskCreateRequest} from "@interfaces/api-models/task-create-request"; -import {DEFAULT_TASK_NAME} from "@shared/constants"; -import {SocketEvents} from "@shared/socket-events"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {Socket} from "ngx-socket-io"; -import {ITaskAssigneesUpdateResponse} from "@interfaces/task-assignee-update-response"; -import {IMyTask} from "@interfaces/my-tasks"; -import {IPersonalTask} from "../../../../../intefaces"; - -@Component({ - selector: 'worklenz-task-add-container', - templateUrl: './task-add-container.component.html', - styleUrls: ['./task-add-container.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskAddContainerComponent implements OnInit { - @ViewChild('task_input') private task_input: ElementRef | undefined; - @ViewChild('due_date_selector') due_date_selector!: NzSelectComponent; - @ViewChild('project_selector') project_selector!: NzSelectComponent; - @Input() isPersonal: boolean = false; - - private session: ILocalSession | null = null; - - newTaskName: string | null = null; - selectedProjectId: string | null = null; - - taskCreateIndex = 1; - - dueDateOpened = false; - projectSelectOpened = false; - loadingProjects = false; - - dueDateOptionsList = [ - {label: 'Today', value: 'Today'}, - {label: 'Tomorrow', value: 'Tomorrow'}, - {label: 'Next Week', value: 'Next Week'}, - {label: 'Next Month', value: 'Next Month'}, - {label: 'No Due Date', value: 'No Due Date'} - ]; - - selectedDueDate = this.dueDateOptionsList[4]; - - projects: IProject[] = []; - - today = new Date(); - tomorrow = new Date().setDate(new Date().getDate() + 1); - nextWeek = new Date().setDate(new Date().getDate() + (7 - new Date().getDay() + 1)); - nextMonth = new Date().setMonth(new Date().getMonth() + 1); - - get profile() { - return this.auth.getCurrentSession(); - } - - constructor( - private readonly auth: AuthService, - private readonly cdr: ChangeDetectorRef, - private readonly api: HomePageApiService, - private readonly socket: Socket, - public readonly service: HomepageService, - ) { - } - - ngOnInit() { - this.session = this.auth.getCurrentSession(); - } - - onKeyDown(event: any) { - if (!this.newTaskName || this.newTaskName.trim() == "") return; - if (event.key == "Tab") { - if (this.isPersonal) { - this.task_input?.nativeElement.focus(); - } else { - // in calendar view - if (this.service.tasksViewConfig?.current_view === 1) { - if (this.taskCreateIndex == 1) { - this.taskCreateIndex = 3; - setTimeout(() => { - this.task_input?.nativeElement.blur(); - this.projectSelectOpened = true; - this.handleProjectOpen(); - this.project_selector.focus(); - }, 150) - } - return; - } - if (this.taskCreateIndex == 1) { - this.taskCreateIndex = 2; - setTimeout(() => { - this.task_input?.nativeElement.blur(); - this.dueDateOpened = true; - this.due_date_selector.focus(); - }, 150) - } - } - } - - if (event.key == "Enter") { - if (this.isPersonal) { - this.createPersonalTask(); - } else { - // in calendar view - if (this.service.tasksViewConfig?.current_view === 1) { - return; - } - if (this.selectedProjectId) { - this.createMainTask(this.selectedProjectId); - } - } - } - } - - async handleProjectOpen() { - const session = this.auth.getCurrentSession(); - const team_id = session?.team_id; - if (!team_id) return; - try { - this.loadingProjects = true; - const res = await this.api.getProjectsByTeam(); - if (res) { - this.projects = res.body; - } - this.loadingProjects = false; - } catch (e) { - log_error(e); - this.loadingProjects = false; - } - this.cdr.markForCheck(); - } - - taskInputFocus() { - document.getElementById('enter_text')?.classList.remove('d-none'); - document.getElementById('tab_text')?.classList.remove('d-none'); - } - - taskInputBlur() { - document.getElementById('enter_text')?.classList.add('d-none'); - document.getElementById('tab_text')?.classList.add('d-none'); - } - - dueDateFieldValidate(event: any, refEl: any): void { - refEl.blur(); - this.taskCreateIndex = 3; - setTimeout(() => { - this.dueDateOpened = false; - this.projectSelectOpened = true; - this.handleProjectOpen(); - this.project_selector.focus(); - }, 150) - } - - async createMainTask(event: string) { - try { - - const body: IHomeTaskCreateRequest = { - name: this.newTaskName || DEFAULT_TASK_NAME, - project_id: this.selectedProjectId as string, - reporter_id: this.session?.id, - team_id: this.session?.team_id - }; - - switch (this.selectedDueDate.value) { - case 'Today': - body.end_date = this.today; - break; - case 'Tomorrow': - body.end_date = new Date(this.tomorrow); - break; - case 'Next Week': - body.end_date = new Date(this.nextWeek); - break; - case 'Next Month': - body.end_date = new Date(this.nextMonth); - break; - } - - // if in calendar view - if (this.service.tasksViewConfig?.current_view === 1) { - if (this.service.tasksViewConfig.selected_date) { - body.end_date = new Date(this.service.tasksViewConfig.selected_date); - } - } - - this.socket.emit(SocketEvents.QUICK_TASK.toString(), JSON.stringify(body)); - this.socket.once(SocketEvents.QUICK_TASK.toString(), (task: IMyTask) => { - const task_assign_body = { - team_member_id: this.session?.team_member_id, - project_id: task.project_id, - task_id: task.id, - reporter_id: this.session?.id, - mode: 0, - }; - this.socket.emit(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), JSON.stringify(task_assign_body)); - this.socket.once(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), (response: ITaskAssigneesUpdateResponse) => { - this.service.emitNewTaskReceived(task); - this.cdr.markForCheck(); - }); - }); - } catch (e) { - log_error(e); - } - this.reset(); - } - - async createPersonalTask() { - if (!this.newTaskName) return; - try { - const createPersonalTaskBody: IPersonalTask = { - name: this.newTaskName, - color_code: '#000' - } - const res = await this.api.createPersonalTask(createPersonalTaskBody); - if (res) { - const personalTask: IMyTask = { - id: res.body.id, - name: res.body.name, - is_task: false, - done: false - } - this.service.emitPersonalTaskReceived(personalTask); - this.cdr.markForCheck(); - } - } catch (e) { - log_error(e) - } - this.reset(); - } - - reset() { - this.taskCreateIndex = 1; - this.newTaskName = null; - this.selectedDueDate = this.dueDateOptionsList[4]; - this.selectedProjectId = null; - this.dueDateOpened = false; - this.projectSelectOpened = false; - this.task_input?.nativeElement.focus(); - } -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/tasks-table.component.html b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/tasks-table.component.html deleted file mode 100644 index acd7d4dc..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/tasks-table.component.html +++ /dev/null @@ -1,54 +0,0 @@ - - -
- -
-
- You have not assigned to any project yet.Therefore you cannot create tasks or view tasks. -
-
- - - - - Task - Project - Status - Due date - - - - - - - - - - - - - - - - - - - -
- -
-
- No tasks to show. -
-
-
-
- - diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/tasks-table.component.scss b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/tasks-table.component.scss deleted file mode 100644 index d00b1f86..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/tasks-table.component.scss +++ /dev/null @@ -1,46 +0,0 @@ - -.tasks-td { - padding: 4px 11px; - height: 44px; -} - -.project-td { - max-width: 200px; - width: 200px; -} - -.status-td { - max-width: 100px; - width: 100px; -} - -.due-date-td { - max-width: 125px; - width: 125px; -} - -.name-td { - position: relative; - - & button { - opacity: 0; - } - - &:hover button { - background: #0a58ca !important; - } -} - -.no-data-img-holder { - max-width: 64px; - margin-top: 24px; -} - -.homepage-table { - overflow-y: auto; - max-height: 925px; -} - -th { - font-weight: 500; -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/tasks-table.component.spec.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/tasks-table.component.spec.ts deleted file mode 100644 index fadaccd0..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/tasks-table.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TasksTableComponent} from './tasks-table.component'; - -describe('TasksTableComponent', () => { - let component: TasksTableComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TasksTableComponent] - }); - fixture = TestBed.createComponent(TasksTableComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/tasks-table.component.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/tasks-table.component.ts deleted file mode 100644 index 0c02e76c..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/components/tasks-table/tasks-table.component.ts +++ /dev/null @@ -1,223 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit} from '@angular/core'; -import {log_error} from "@shared/utils"; -import {AuthService} from "@services/auth.service"; -import {IMyTask} from "@interfaces/my-tasks"; -import {HomePageApiService} from "@api/home-page-api.service"; -import {HomepageService} from "../../../../homepage-service.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {merge} from "rxjs"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {ProjectsService} from "../../../../../projects/projects.service"; -import {IProjectViewModel} from "@interfaces/api-models/project-view-model"; -import {DRAWER_ANIMATION_INTERVAL} from "@shared/constants"; -import {IProject} from "@interfaces/project"; - -@Component({ - selector: 'worklenz-tasks-table', - templateUrl: './tasks-table.component.html', - styleUrls: ['./tasks-table.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TasksTableComponent implements OnInit { - private session: ILocalSession | null = null; - protected selectedTask: IProjectTask | null = null; - - projects: IProject[] = []; - - loading = false; - showTaskModal = false; - - constructor( - private readonly auth: AuthService, - private readonly cdr: ChangeDetectorRef, - private readonly homePageApi: HomePageApiService, - private readonly projectService: ProjectsService, - private readonly taskViewService: TaskViewService, - public readonly homePageService: HomepageService - ) { - - this.homePageService.newTaskReceived - .pipe(takeUntilDestroyed()) - .subscribe(task => { - this.handleNewTaskReceived(task); - }); - - this.taskViewService.onViewBackFrom - .pipe(takeUntilDestroyed()) - .subscribe(task => { - const task_: IProjectTask = { - id: task.parent_task_id, - project_id: task.project_id, - } - this.handleTaskSelectFromView(task_); - }); - - this.taskViewService.onSelectSubTask - .pipe(takeUntilDestroyed()) - .subscribe(task => { - this.handleTaskSelectFromView(task); - }); - - this.homePageService.onGetTasks - .pipe(takeUntilDestroyed()) - .subscribe(config => { - this.getTasks(true); - }) - - this.homePageService.onGetTasksWithoutLoading - .pipe(takeUntilDestroyed()) - .subscribe(config => { - this.getTasks(false); - }) - - merge( - this.taskViewService.onAssigneesChange, - this.taskViewService.onEndDateChange, - this.taskViewService.onStatusChange - ) - .pipe(takeUntilDestroyed()) - .subscribe(() => { - void this.getTasks(false); - }); - - this.taskViewService.onDelete - .pipe(takeUntilDestroyed()) - .subscribe(value => { - if (value.parent_task_id) { - const task_: IProjectTask = { - id: value.parent_task_id, - project_id: value.project_id as string - } - this.handleTaskSelectFromView(task_); - } - void this.getTasks(false); - }); - } - - ngOnInit() { - this.session = this.auth.getCurrentSession(); - this.getProjects(); - this.getTasks(true); - } - - async getProjects() { - const team_id = this.session?.team_id; - if (!team_id) return; - try { - const res = await this.homePageApi.getProjectsByTeam(); - if (res) { - this.projects = res.body; - } - } catch (e) { - log_error(e); - } - this.cdr.markForCheck(); - } - - async getTasks(isloading: boolean) { - if (!this.homePageService.tasksViewConfig) return; - try { - this.loading = isloading; - this.homePageService.loadingTasks = true; - let config = this.homePageService.tasksViewConfig; - config.time_zone = this.session?.timezone_name ? this.session?.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone; - const res = await this.homePageApi.getMyTasks(config); - if (res) { - this.homePageService.tasksModel = res.body; - } - this.loading = false; - this.homePageService.loadingTasks = false; - } catch (e) { - log_error(e); - this.loading = false; - this.homePageService.loadingTasks = false; - } - - this.cdr.markForCheck(); - } - - private handleTaskSelectFromView(task: IProjectTask) { - this.showTaskModal = false; - setTimeout(() => { - if (task) { - this.openTask(task); - this.cdr.markForCheck(); - } - }, DRAWER_ANIMATION_INTERVAL); - this.cdr.detectChanges(); - } - - protected openTask(task: IProjectTask) { - this.selectedTask = task; - if (task.project_id) this.projectService.id = task.project_id; - this.showTaskModal = true; - this.cdr.markForCheck(); - } - - onShowChange(show: boolean) { - if (!show) { - this.selectedTask = null; - } - } - - handleNewTaskReceived(task: IMyTask): void { - if (!task) return; - const receivedTask: IMyTask = { - id: task.id, - name: task.name, - project_id: task.project_id, - status: task.status, - end_date: task.end_date, - is_task: true, - done: false, - project_color: task.project_color, - project_name: task.project_name, - team_id: this.session?.team_id, - status_color: task.status_color?.slice(0, -2), - project_statuses: task.project_statuses - } - // change tasks count on tabs - this.homePageService.tasksModel.total++; - - // add task to all tab - if (this.homePageService.tasksViewConfig?.current_tab === 'All') { - this.homePageService.tasksModel.tasks.unshift(receivedTask); - } - - // add task to no due date tab and increase count - if (!task.end_date) { - this.homePageService.tasksModel.no_due_date++; - if (this.homePageService.tasksViewConfig?.current_tab === 'NoDueDate') { - this.homePageService.tasksModel.tasks.unshift(receivedTask); - } - } - - if (task.end_date) { - const dateToCheck = new Date(task.end_date); - const today = new Date(); - today.setHours(0, 0, 0, 0); - - // add task to today tab and increase count - if (dateToCheck.toDateString() === today.toDateString()) { - this.homePageService.tasksModel.today++; - if (this.homePageService.tasksViewConfig?.current_tab === 'Today') { - this.homePageService.tasksModel.tasks.unshift(receivedTask); - } - } else if (dateToCheck > today) { - this.homePageService.tasksModel.upcoming++; - if (this.homePageService.tasksViewConfig?.current_tab === 'Upcoming') { - this.homePageService.tasksModel.tasks.unshift(receivedTask); - } - } - } - - this.cdr.markForCheck(); - } - - trackBy(index: number, item: IProjectViewModel) { - return item.id; - } - -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/my-tasks.component.html b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/my-tasks.component.html deleted file mode 100644 index 45f38f07..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/my-tasks.component.html +++ /dev/null @@ -1,277 +0,0 @@ -
-
- -

Tasks

- - - -
-
-
- - - - -
-
-
-
-
- - - - - - - - - - - - - - - -
- - - - - -
- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/my-tasks.component.scss b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/my-tasks.component.scss deleted file mode 100644 index 0acd3a3b..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/my-tasks.component.scss +++ /dev/null @@ -1,239 +0,0 @@ -.single-task-name { - font-size: 14px; - margin-bottom: 0px; -} - -.single-task-dates { - font-weight: 400; - font-size: 12px; - line-height: 15px; - color: #757B81; -} - -.single-task-project { - position: relative; - width: max-content; - margin-left: auto; - font-size: 12px; - line-height: 18px; - text-align: center; - padding-left: 8px; - padding-bottom: 0px; - padding-right: 8px; - padding-top: 0px; - font-weight: 500; -} - -#block { - filter: alpha(opacity=20); - -moz-opacity: 0.20; - opacity: 0.20; - position: absolute; - top: 0; - left: 0; - height: 100%; - width: 100%; - border-radius: 100px; -} - -.ant-table table { - border-collapse: collapse !important; -} - -.ant-table-cell { - background: #fff !important; -} - -.custom-table td, -.custom-table th { - border: inherit !important; -} - -.status-selector { - width: 12px; - height: 12px; - border-radius: 4px; - margin-top: 5px; - margin-right: 8px; - color: transparent; -} - -.ant-card-bordered { - transition: 0.3s all; - border: 1px solid #e5e5e5; -} - -.ant-card-bordered:hover { - border: 1px solid #c5c5c5; -} - -.single-project-status { - display: flex; - margin-right: 8px; -} - -.ant-segmented-item-label { - color: #585858 !important; -} - -.task-add-section { - padding-left: 17px; - padding-top: 8px; - line-height: 32px; - padding-bottom: 8px; - border-bottom: 1px solid #f0f0f0; - background-color: rgb(245 245 245 / 25%) -} - -.click-to-add-task { - cursor: pointer; - margin-left: 11px; -} - -.form-task-due-date { - max-width: 23%; - width: 20%; -} - -.form-task-project { - max-width: 30%; - width: 30%; -} - -.form-task-name { - @media(max-width: 1280px) { - max-width: 40%; - } -} - -.task-name-input-field { - width: 320px; - max-width: 320px; - border: 1px solid transparent !important; - font-weight: 500; -} - -::placeholder { - color: rgb(0 0 0 / 65%) !important; - font-weight: 500 !important; -} - -.task-name-input-field:hover { - border: 1px solid #d9d9d9; -} - -.task-name-input-field:focus { - border: 1px solid #40a9ff; -} - -.task-due-date-selector { - width: 150px; -} - -.task-project-selector { - min-width: 180px; - max-width: 180px; -} - -.task-tooltip { - position: absolute; - left: 145px; -} - -.custom-date-picker { - position: absolute; - left: 0; - right: 0; - height: 16px; - opacity: 0; -} - -#top_desc { - display: block; -} - -#top_desc_.hide { - display: none; -} - -.actions-row:has(.task-due-date-elem) .single-task-project { - margin-top: -18px; -} - -.actions-row { - td { - background-color: transparent !important; - } -} - -.no-data-img-holder { - width: 64px; - margin-top: 42px; -} - -.panel { - position: relative; - padding: 0 0; - background-color: white; - max-height: 0; - overflow: hidden; - transition: max-height 0.25s; -} - -.panel.show { - transition: max-height 0.25s; - max-height: 100%; -} - -.btn { - width: max-content; - padding-left: 16px !important; - padding-right: 32px !important; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; - cursor: pointer; -} - -.btn.active { - border-bottom-left-radius: 0px; - border-bottom-right-radius: 0px; -} - -.btn .accordion-icon { - transition: transform 0.25s; - transform: rotate(0deg); -} - -.btn.active .accordion-icon { - transition: transform 0.25s; - transform: rotate(90deg); -} - - -.card-data { - padding: 12px 24px; -} - -.custom-table { - max-height: calc(100vh - 360px); - overflow: auto; -} - -.card-top { - padding: 14px 24px; - border-bottom: 1px solid #f0f0f0; -} - -.card-title { - font-size: 16px; - font-weight: 500; -} - -@media (max-width: 1100px) { - .mob-overflow { - max-width: 475px; - overflow-x: auto; - } -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/my-tasks.component.spec.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/my-tasks.component.spec.ts deleted file mode 100644 index 25c7a631..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/my-tasks.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {MyTasksComponent} from './my-tasks.component'; - -describe('MyTasksComponent', () => { - let component: MyTasksComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [MyTasksComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(MyTasksComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/my-tasks.component.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/my-tasks.component.ts deleted file mode 100644 index 5abea5d6..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/my-tasks/my-tasks.component.ts +++ /dev/null @@ -1,118 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild} from '@angular/core'; -import { - IMyDashboardAllTasksViewModel, - IMyDashboardMyTask -} from "@interfaces/api-models/my-dashboard-all-tasks-view-model"; -import {ITaskStatusViewModel} from '@interfaces/api-models/task-status-get-response'; -import {NzSelectComponent} from 'ng-zorro-antd/select'; -import {Subscription} from "rxjs"; -import {ITaskListGroup} from "../../../modules/task-list-v2/interfaces"; -import {HomepageService} from "../../homepage-service.service"; -import {TasksTableComponent} from "./components/tasks-table/tasks-table.component"; - -@Component({ - selector: 'worklenz-my-tasks', - templateUrl: './my-tasks.component.html', - styleUrls: ['./my-tasks.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class MyTasksComponent implements OnInit, OnDestroy { - @ViewChild('due_date_selector') due_date_selector!: NzSelectComponent; - @ViewChild('project_selector') project_selector!: NzSelectComponent; - @ViewChild(TasksTableComponent) tasksTable!: TasksTableComponent; - - readonly tasksModes = [ - {label: 'assigned to me', value: 0}, - {label: 'assigned by me', value: 1} - ] - - selectedTasksMode = this.tasksModes[0].value; - - defaultTasksTab = 'All'; - - tasks: IMyDashboardMyTask[] = []; - - groups: ITaskListGroup[] = []; - - model: IMyDashboardAllTasksViewModel = {}; - - loading = false; - showTaskDrawer = false; - updating = false; - - options = ['List', 'Calendar']; - private readonly myTasksActiveFilterKey = "my-dashboard-active-filter"; - - projectId = null; - - private tvDeleteSubscription!: Subscription; - - get activeFilter() { - return +(localStorage.getItem(this.myTasksActiveFilterKey) || 0); - } - - set activeFilter(value: number) { - localStorage.setItem(this.myTasksActiveFilterKey, value.toString()); - } - - constructor( - private readonly cdr: ChangeDetectorRef, - public readonly service: HomepageService - ) { - } - - ngOnInit() { - this.service.tasksViewConfig = { - tasks_group_by: this.selectedTasksMode, - current_view: this.activeFilter, - current_tab: this.defaultTasksTab, - is_calendar_view: this.activeFilter != 0, - selected_date: this.activeFilter == 0 ? null : new Date(), - time_zone: '', - }; - } - - ngOnDestroy() { - this.tvDeleteSubscription?.unsubscribe(); - } - - selectDateChange(date: Date) { - if (!this.service.tasksViewConfig) return; - this.service.tasksViewConfig.selected_date = date; - this.service.emitGetTasks(this.service.tasksViewConfig); - } - - handleModeChange(index: number) { - if (!this.service.tasksViewConfig) return; - this.service.tasksViewConfig.tasks_group_by = index; - this.service.emitGetTasks(this.service.tasksViewConfig); - } - - handleViewChange(index: number) { - if (!this.service.tasksViewConfig) return; - this.service.tasksViewConfig.current_view = index; - this.service.tasksViewConfig.is_calendar_view = false; - this.service.tasksViewConfig.selected_date = null; - if (index === 1) { - this.service.tasksViewConfig.is_calendar_view = true; - this.service.tasksViewConfig.selected_date = new Date(); - } - this.service.tasksViewConfig.current_tab = this.defaultTasksTab; - this.activeFilter = index; - this.cdr.markForCheck(); - } - - handleTasksTabChange(tabName: string) { - if (!this.service.tasksViewConfig) return; - this.service.tasksViewConfig.current_tab = tabName; - this.service.emitGetTasks(this.service.tasksViewConfig); - this.cdr.markForCheck(); - } - - emitGetTasks() { - if (!this.service.tasksViewConfig) return; - this.tasksTable.getTasks(true); - this.cdr.markForCheck(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/personal-tasks/personal-tasks.component.html b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/personal-tasks/personal-tasks.component.html deleted file mode 100644 index 94ef98cf..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/personal-tasks/personal-tasks.component.html +++ /dev/null @@ -1,37 +0,0 @@ -
-
- -

To do list ({{homePageService.personal_tasks.length}})

-
-
- -
-
-
-
- - - - - - - - - - - - - -
- -
-
- No tasks to show. -
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/personal-tasks/personal-tasks.component.scss b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/personal-tasks/personal-tasks.component.scss deleted file mode 100644 index fa531039..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/personal-tasks/personal-tasks.component.scss +++ /dev/null @@ -1,83 +0,0 @@ -.ant-card-bordered { - transition: 0.3s all; - border: 1px solid #e5e5e5; -} - -.ant-card-bordered:hover { - border: 1px solid #c5c5c5; -} - -tr { - border-bottom: 1px solid #F0F0F0; -} - -.ant-table table { - border-collapse: collapse !important; -} - -.ant-table-cell { - background: #fff !important; -} - -.text-grey { - color: #757B81; - font-size: 14px; - line-height: 25px; - margin-bottom: 0px !important; -} - -.no-data-img-holder { - width: 64px; - margin-top: 24px; -} - -.project-card { - width: 20%; - cursor: pointer; -} - -.font-400 { - font-weight: 400; -} - -.card-data { - padding: 24px; -} - -.projects-td { - padding: 4px 11px; - height: 44px; -} - -.card-top { - padding: 14px 24px; - border-bottom: 1px solid #f0f0f0; -} - -.card-title { - font-size: 16px; - font-weight: 500; -} - -.tasks-td { - padding: 4px 11px; - height: 44px; -} - -.task-done-btn { - max-width: 20px; - width: 20px; -} - -.homepage-table { - overflow-y: auto; - max-height: 420px; -} - -th { - font-weight: 500; -} - -.card-data { - padding: 12px 24px; -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/personal-tasks/personal-tasks.component.spec.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/personal-tasks/personal-tasks.component.spec.ts deleted file mode 100644 index ae30c86a..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/personal-tasks/personal-tasks.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {PersonalTasksComponent} from './personal-tasks.component'; - -describe('PersonalTasksComponent', () => { - let component: PersonalTasksComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [PersonalTasksComponent] - }); - fixture = TestBed.createComponent(PersonalTasksComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/personal-tasks/personal-tasks.component.ts b/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/personal-tasks/personal-tasks.component.ts deleted file mode 100644 index 0293c784..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/dashboard/personal-tasks/personal-tasks.component.ts +++ /dev/null @@ -1,79 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit} from '@angular/core'; -import {HomePageApiService} from "@api/home-page-api.service"; -import {HomepageService} from "../../homepage-service.service"; -import {IMyTask} from "@interfaces/my-tasks"; -import {log_error} from "@shared/utils"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {IProjectViewModel} from "@interfaces/api-models/project-view-model"; - -@Component({ - selector: 'worklenz-personal-tasks', - templateUrl: './personal-tasks.component.html', - styleUrls: ['./personal-tasks.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class PersonalTasksComponent implements OnInit { - - loading = true; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly homePageApi: HomePageApiService, - public readonly homePageService: HomepageService - ) { - - this.homePageService.newPersonalTaskReceived - .pipe(takeUntilDestroyed()) - .subscribe(value => { - this.handlePersonalTaskReceived(value); - }) - - this.homePageService.removeTaskFromList - .pipe(takeUntilDestroyed()) - .subscribe(value => { - this.removeTaskFromList(value); - }) - } - - ngOnInit() { - this.homePageService.personal_tasks = []; - this.getPersonalTasks(true); - } - - async getPersonalTasks(isLoading: boolean) { - try { - this.loading = isLoading; - this.homePageService.loadingPersonalTasks = true; - const res = await this.homePageApi.getPersonalTasks(); - if (res) { - this.homePageService.personal_tasks = res.body; - } - this.loading = false; - this.homePageService.loadingPersonalTasks = false; - } catch (e) { - this.loading = false; - this.homePageService.loadingPersonalTasks = false; - log_error(e); - } - - this.cdr.markForCheck(); - } - - handlePersonalTaskReceived(receivedTask: IMyTask) { - if (!receivedTask) return; - this.homePageService.personal_tasks.unshift(receivedTask); - this.cdr.markForCheck(); - } - - removeTaskFromList(id: string) { - if (!id) return; - // const taskToRemove = this.homePageService.personal_tasks.findIndex(item => item.id === id); - // this.homePageService.personal_tasks.splice(taskToRemove, 1); - this.getPersonalTasks(false); - this.cdr.markForCheck(); - } - - trackBy(index: number, item: IProjectViewModel) { - return item.id; - } -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/homepage-service.service.ts b/worklenz-frontend/src/app/administrator/my-dashboard/homepage-service.service.ts deleted file mode 100644 index f7f6dc4c..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/homepage-service.service.ts +++ /dev/null @@ -1,71 +0,0 @@ -import {Injectable} from '@angular/core'; -import {IHomeTasksConfig, IHomeTasksModel} from "./intefaces"; -import {Subject} from "rxjs"; -import {IMyTask} from "@interfaces/my-tasks"; - -@Injectable({ - providedIn: 'root' -}) -export class HomepageService { - private readonly onGetTasksSbj$ = new Subject(); - private readonly onGetTasksWithoutLoadingSbj$ = new Subject(); - private readonly onNewTaskReceiveSbj$ = new Subject(); - private readonly onNewPersonalTaskReceiveSbj$ = new Subject(); - private readonly onRemoveTaskSbj$ = new Subject(); - - tasksModel: IHomeTasksModel = { - tasks: [], - total: 0, - today: 0, - upcoming: 0, - overdue: 0, - no_due_date: 0 - }; - - personal_tasks: IMyTask[] = []; - - tasksViewConfig: IHomeTasksConfig | null = null; - - loadingTasks = false; - loadingPersonalTasks = false; - - get onGetTasks() { - return this.onGetTasksSbj$.asObservable(); - } - - get onGetTasksWithoutLoading() { - return this.onGetTasksSbj$.asObservable(); - } - - get newTaskReceived() { - return this.onNewTaskReceiveSbj$.asObservable(); - } - - get newPersonalTaskReceived() { - return this.onNewPersonalTaskReceiveSbj$.asObservable(); - } - - get removeTaskFromList() { - return this.onRemoveTaskSbj$.asObservable(); - } - - public emitGetTasks(config: IHomeTasksConfig) { - this.onGetTasksSbj$.next(config); - } - - public emitGetTasksWithoutLoading(config: IHomeTasksConfig) { - this.onGetTasksSbj$.next(config); - } - - public emitNewTaskReceived(task: IMyTask) { - this.onNewTaskReceiveSbj$.next(task); - } - - public emitPersonalTaskReceived(task: IMyTask) { - this.onNewPersonalTaskReceiveSbj$.next(task); - } - - public emitRemoveTaskFromList(id: string) { - this.onRemoveTaskSbj$.next(id); - } -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/homepage.enum.ts b/worklenz-frontend/src/app/administrator/my-dashboard/homepage.enum.ts deleted file mode 100644 index 8570a6d0..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/homepage.enum.ts +++ /dev/null @@ -1,12 +0,0 @@ -export enum TaskFilter { - AssignedToMe = 'assignedToMe', - AssignedByMe = 'assignedByMe' -} - -export enum TaskTab { - AllTasks = 'allTasks', - TodayTasks = 'todayTasks', - UpcomingTasks = 'upcomingTasks', - OverdueTasks = 'overdueTasks', - NoDueDateTasks = 'noDueDateTasks' -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/intefaces.ts b/worklenz-frontend/src/app/administrator/my-dashboard/intefaces.ts deleted file mode 100644 index 8d57da90..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/intefaces.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {IMyTask} from "@interfaces/my-tasks"; - -export interface IHomeTasksConfig { - current_tab: string | null; // active tab in list view - selected_date: Date | null; // selected date in calendar view - tasks_group_by: number; // tasks assigned to me / assigned by me - current_view: number; // list view or calendar view - is_calendar_view: boolean; - time_zone: string; -} - -export interface IHomeTasksModel { - tasks: IMyTask[]; - total: number; - today: number; - upcoming: number; - overdue: number; - no_due_date: number; -} - -export interface IPersonalTask { - name: string; - color_code: '#000'; - end_date?: string; -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/my-dashboard-routing.module.ts b/worklenz-frontend/src/app/administrator/my-dashboard/my-dashboard-routing.module.ts deleted file mode 100644 index 1ab2e8bf..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/my-dashboard-routing.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {DashboardComponent} from "./dashboard/dashboard.component"; - -const routes: Routes = [ - {path: "", component: DashboardComponent} -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class MyDashboardRoutingModule { -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/my-dashboard.module.ts b/worklenz-frontend/src/app/administrator/my-dashboard/my-dashboard.module.ts deleted file mode 100644 index b089e542..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/my-dashboard.module.ts +++ /dev/null @@ -1,124 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; - -import {MyDashboardRoutingModule} from './my-dashboard-routing.module'; -import {ActivityLogComponent} from './activity-log/activity-log.component'; -import {PersonalTodoListComponent} from './personal-todo-list/personal-todo-list.component'; -import {DashboardComponent} from './dashboard/dashboard.component'; -import {FormsModule, ReactiveFormsModule} from "@angular/forms"; -import {ProjectsTasksComponent} from './projects-tasks/projects-tasks.component'; -import {NzListModule} from "ng-zorro-antd/list"; -import {NzCollapseModule} from "ng-zorro-antd/collapse"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {FromNowPipe} from "../../pipes/from-now.pipe"; -import {NzSelectModule} from "ng-zorro-antd/select"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzPageHeaderModule} from "ng-zorro-antd/page-header"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzCardModule} from "ng-zorro-antd/card"; -import {NzTableModule} from "ng-zorro-antd/table"; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzDividerModule} from "ng-zorro-antd/divider"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {DragDropModule} from "@angular/cdk/drag-drop"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import {AvatarsComponent} from "../components/avatars/avatars.component"; -import {NzSegmentedModule} from "ng-zorro-antd/segmented"; -import {MyProjectsComponent} from './dashboard/my-projects/my-projects.component'; -import {NzProgressModule} from 'ng-zorro-antd/progress'; -import {MyTasksComponent} from './dashboard/my-tasks/my-tasks.component'; -import {NzDatePickerModule} from 'ng-zorro-antd/date-picker'; -import {ProjectFormModalComponent} from "../components/project-form-modal/project-form-modal.component"; -import {NzRateModule} from 'ng-zorro-antd/rate'; -import {EllipsisPipe} from 'app/pipes/ellipsis.pipe'; -import {TaskViewModule} from "../components/task-view/task-view.module"; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; -import {NzTagModule} from "ng-zorro-antd/tag"; -import {TaskNameComponent} from './dashboard/my-tasks/components/task-name/task-name.component'; -import {TaskProjectComponent} from './dashboard/my-tasks/components/task-project/task-project.component'; -import {TaskDueDateComponent} from './dashboard/my-tasks/components/task-due-date/task-due-date.component'; -import {TaskStatusComponent} from './dashboard/my-tasks/components/task-status/task-status.component'; -import {DateFormatterPipe} from "@pipes/date-formatter.pipe"; -import {MinDateValidatorPipe} from "@pipes/min-date-validator.pipe"; -import {TaskDoneComponent} from './dashboard/my-tasks/components/task-done/task-done.component'; -import {NzTabsModule} from "ng-zorro-antd/tabs"; -import {NzCalendarModule} from "ng-zorro-antd/calendar"; -import {NzAlertModule} from "ng-zorro-antd/alert"; -import {TasksTableComponent} from './dashboard/my-tasks/components/tasks-table/tasks-table.component'; -import { - TaskAddContainerComponent -} from './dashboard/my-tasks/components/tasks-table/task-add-container/task-add-container.component'; -import {PersonalTasksComponent} from './dashboard/personal-tasks/personal-tasks.component'; -import { - ProjectTemplateImportDrawerComponent -} from "@admin/components/project-template-import-drawer/project-template-import-drawer.component"; -import {TaskListV2Module} from "../modules/task-list-v2/task-list-v2.module"; - -@NgModule({ - declarations: [ - ActivityLogComponent, - PersonalTodoListComponent, - DashboardComponent, - ProjectsTasksComponent, - MyProjectsComponent, - MyTasksComponent, - TaskNameComponent, - TaskProjectComponent, - TaskDueDateComponent, - TaskStatusComponent, - TaskDoneComponent, - TasksTableComponent, - TaskAddContainerComponent, - PersonalTasksComponent - ], - imports: [ - CommonModule, - MyDashboardRoutingModule, - ReactiveFormsModule, - FormsModule, - NzListModule, - NzCollapseModule, - FromNowPipe, - NzSelectModule, - NzSpaceModule, - NzPageHeaderModule, - NzFormModule, - NzButtonModule, - NzInputModule, - NzIconModule, - NzDropDownModule, - NzToolTipModule, - NzCardModule, - NzTableModule, - NzDrawerModule, - NzDividerModule, - NzTypographyModule, - DragDropModule, - NzBadgeModule, - AvatarsComponent, - NzSegmentedModule, - NzSkeletonModule, - NzProgressModule, - NzDatePickerModule, - ProjectFormModalComponent, - NzRateModule, - EllipsisPipe, - TaskViewModule, - SafeStringPipe, - NzTagModule, - DateFormatterPipe, - MinDateValidatorPipe, - NzTabsModule, - NzCalendarModule, - NzAlertModule, - ProjectTemplateImportDrawerComponent, - TaskListV2Module - ] -}) -export class MyDashboardModule { -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/personal-todo-list/personal-todo-list.component.html b/worklenz-frontend/src/app/administrator/my-dashboard/personal-todo-list/personal-todo-list.component.html deleted file mode 100644 index 589d1468..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/personal-todo-list/personal-todo-list.component.html +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - {{data.name}} - - - {{data.description}} - - - -
- - - - -
- - - -
-
-
- - - -
- - - - - - - - - - -
-
-
- - - - - - - - - - -
- - Task - - - - - - Description - - - - - - - - -
    -
  • -   -
  • -
-
-
-
- -
-
-
diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/personal-todo-list/personal-todo-list.component.scss b/worklenz-frontend/src/app/administrator/my-dashboard/personal-todo-list/personal-todo-list.component.scss deleted file mode 100644 index f1657dc3..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/personal-todo-list/personal-todo-list.component.scss +++ /dev/null @@ -1,18 +0,0 @@ -.completed { - text-decoration: line-through; - opacity: 0.5; -} - -.task-list-filter-button { - width: 140px; - text-align: left; - float: right; - margin-right: 10px; -} - -.todo-list-item { - max-width: 0; - overflow: hidden; - width: 100%; - text-overflow: ellipsis; -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/personal-todo-list/personal-todo-list.component.spec.ts b/worklenz-frontend/src/app/administrator/my-dashboard/personal-todo-list/personal-todo-list.component.spec.ts deleted file mode 100644 index 47908e4b..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/personal-todo-list/personal-todo-list.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {PersonalTodoListComponent} from './personal-todo-list.component'; - -describe('PersonalTodoListComponent', () => { - let component: PersonalTodoListComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [PersonalTodoListComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(PersonalTodoListComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/personal-todo-list/personal-todo-list.component.ts b/worklenz-frontend/src/app/administrator/my-dashboard/personal-todo-list/personal-todo-list.component.ts deleted file mode 100644 index b79d84b3..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/personal-todo-list/personal-todo-list.component.ts +++ /dev/null @@ -1,201 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit} from '@angular/core'; -import {ProjectsDefaultColorCodes} from "@shared/constants"; -import {ITodoListItem} from "@interfaces/todo-list-item"; -import {TodoListApiService} from "@api/todo-list-api.service"; -import {FormBuilder, FormGroup, Validators} from "@angular/forms"; -import {AppService} from "@services/app.service"; -import {CdkDragDrop} from "@angular/cdk/drag-drop"; -import {log_error} from "@shared/utils"; - -@Component({ - selector: 'worklenz-personal-todo-list', - templateUrl: './personal-todo-list.component.html', - styleUrls: ['./personal-todo-list.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class PersonalTodoListComponent implements OnInit { - form!: FormGroup; - searchForm!: FormGroup; - - colorCodes = ProjectsDefaultColorCodes; - - tasks: ITodoListItem[] = []; - - loading = false; - creating = false; - deleteMap: { [x: string]: boolean } = {}; - deletingMap: { [x: string]: boolean } = {}; - showEditDrawer = false; - selectedItem: ITodoListItem | null = null; - updating = false; - private readonly showCompletedTasksKey = "my-dashboard-show-completed-tasks"; - - constructor( - private readonly api: TodoListApiService, - private readonly app: AppService, - private readonly fb: FormBuilder, - private readonly cdr: ChangeDetectorRef - ) { - this.form = this.fb.group({ - name: [null, [Validators.required, Validators.pattern(/^(\s+\S+\s*)*(?!\s).*$/)]], - color_code: [ProjectsDefaultColorCodes[ProjectsDefaultColorCodes.length - 1], Validators.required], - description: [] - }); - this.searchForm = this.fb.group({ - search: [] - }); - this.searchForm.valueChanges.subscribe(() => { - void this.searchTasks(); - }); - } - - get name() { - return this.form.value.name || ''; - } - - get showCompleted() { - return !!localStorage.getItem(this.showCompletedTasksKey); - } - - set showCompleted(value: boolean) { - if (value) { - localStorage.setItem(this.showCompletedTasksKey, "1"); - } else { - localStorage.removeItem(this.showCompletedTasksKey); - } - } - - async ngOnInit() { - await this.get(); - } - - async searchTasks() { - await this.get(); - } - - async get(ignoreLoading = false) { - try { - if (!ignoreLoading) - this.loading = true; - const res = await this.api.get(this.searchForm.value.search, this.showCompleted || null); - if (res.done) { - this.tasks = res.body; - } - this.loading = false; - } catch (e) { - log_error(e); - this.loading = false; - } - - this.cdr.markForCheck(); - } - - async create() { - if (this.form.invalid) { - this.app.displayErrorsOf(this.form); - return; - } - - try { - this.creating = true; - const res = await this.api.create(this.form.value); - if (res.done) { - await this.get(true); - const color = this.form.value.color_code; - this.form.reset({color_code: color}); - } - this.creating = false; - } catch (e) { - log_error(e); - this.creating = false; - } - this.cdr.markForCheck(); - } - - async delete(id?: string) { - if (!id) return; - try { - this.deleteMap[id] = true; - const res = await this.api.delete(id); - if (res.done) { - await this.get(true); - } else { - this.deleteMap[id] = false; - } - } catch (e) { - log_error(e); - this.deleteMap[id] = false; - } - this.cdr.markForCheck(); - } - - async toggleDone(id?: string, done?: boolean) { - if (!id) return; - try { - const res = await this.api.updateStatus(id, {done: !!done}); - if (res.done) { - if (done && !this.showCompleted) { - setTimeout(() => { - this.deletingMap[id] = true; - setTimeout(() => { - delete this.deletingMap[id]; - this.get(true); - }, 200); - }, 250); - } else { - await this.get(true); - } - } - } catch (e) { - log_error(e); - } - } - - identify(_index: number, item: ITodoListItem) { - return item.id; - } - - closeModal() { - this.showEditDrawer = false; - } - - editItem(item: ITodoListItem) { - this.selectedItem = {...item}; - this.showEditDrawer = true; - } - - async update() { - if (!this.selectedItem || !this.selectedItem.id) return; - try { - this.updating = true; - const res = await this.api.update(this.selectedItem.id, this.selectedItem); - if (res.done) { - await this.get(true); - this.closeModal(); - this.selectedItem = null; - } - this.updating = false; - } catch (e) { - log_error(e); - this.updating = false; - } - - this.cdr.markForCheck(); - } - - async onTaskDrop($event: CdkDragDrop) { - const {currentIndex, previousIndex} = $event; - await this.updateIndex(currentIndex, previousIndex); - } - - private async updateIndex(currentIndex: number, previousIndex: number) { - try { - const res = await this.api.updateIndex(previousIndex, currentIndex); - if (res.done) { - this.get(true); - } - } catch (e) { - log_error(e); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/projects-tasks/projects-tasks.component.html b/worklenz-frontend/src/app/administrator/my-dashboard/projects-tasks/projects-tasks.component.html deleted file mode 100644 index 0d88cc27..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/projects-tasks/projects-tasks.component.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - {{data.name}} - {{data.project_name}} - - - Start date: {{(data.start_date | date: "mediumDate") || "-"}} - End date: {{(data.end_date | date: "mediumDate") || "-"}} - - - - - - - - - - - - - - - - - - - {{title}} - - - - - - -
- - - - - - -
-
-
diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/projects-tasks/projects-tasks.component.scss b/worklenz-frontend/src/app/administrator/my-dashboard/projects-tasks/projects-tasks.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/projects-tasks/projects-tasks.component.spec.ts b/worklenz-frontend/src/app/administrator/my-dashboard/projects-tasks/projects-tasks.component.spec.ts deleted file mode 100644 index 69bea3af..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/projects-tasks/projects-tasks.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ProjectsTasksComponent} from './projects-tasks.component'; - -describe('ProjectsTasksComponent', () => { - let component: ProjectsTasksComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ProjectsTasksComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ProjectsTasksComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/my-dashboard/projects-tasks/projects-tasks.component.ts b/worklenz-frontend/src/app/administrator/my-dashboard/projects-tasks/projects-tasks.component.ts deleted file mode 100644 index d64f166b..00000000 --- a/worklenz-frontend/src/app/administrator/my-dashboard/projects-tasks/projects-tasks.component.ts +++ /dev/null @@ -1,118 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener} from '@angular/core'; -import {AvatarNamesMap, DEFAULT_PAGE_SIZE} from "@shared/constants"; -import { - IMyDashboardAllTasksViewModel, - IMyDashboardProjectTask -} from "@interfaces/api-models/my-dashboard-all-tasks-view-model"; -import {ProjectsApiService} from "@api/projects-api.service"; -import {IPaginationComponent} from "@interfaces/pagination-component"; -import {FormBuilder, FormGroup} from "@angular/forms"; -import {NzTableQueryParams} from "ng-zorro-antd/table"; -import {EventTaskCreatedOrUpdate} from "@shared/events"; -import {Router} from "@angular/router"; -import {UtilsService} from "@services/utils.service"; -import {log_error} from "@shared/utils"; - -@Component({ - selector: 'worklenz-projects-tasks', - templateUrl: './projects-tasks.component.html', - styleUrls: ['./projects-tasks.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectsTasksComponent implements IPaginationComponent { - searchForm!: FormGroup; - tasks: IMyDashboardProjectTask[] = []; - - model: IMyDashboardAllTasksViewModel = {}; - - loading = false; - - total = 0; - pageSize = DEFAULT_PAGE_SIZE; - pageIndex = 1; - paginationSizes = [5, 10, 15, 20, 50, 100]; - sortField: string | null = null; - sortOrder: string | null = null; - // Changing this order should also reflect to projects-controller:getAllTasks - options = ['Today', 'Upcoming', 'Overdue']; - private readonly activeFilterKey = "my-dashboard-active-filter"; - - constructor( - private readonly fb: FormBuilder, - private readonly api: ProjectsApiService, - private readonly router: Router, - private readonly utilsService: UtilsService, - private readonly cdr: ChangeDetectorRef - ) { - this.searchForm = this.fb.group({ - search: [] - }); - - this.searchForm.valueChanges.subscribe(async () => { - await this.searchTasks(); - }); - } - - get title() { - const index = this.activeFilter; - if (index === 0) return `My Tasks (${this.total})`; - if (index === 1) return `Upcoming (${this.total})`; - if (index === 2) return `Overdue (${this.total})`; - return "Tasks"; - } - - get activeFilter() { - return +(localStorage.getItem(this.activeFilterKey) || 0); - } - - set activeFilter(value: number) { - localStorage.setItem(this.activeFilterKey, value.toString()); - } - - @HostListener(`document:${EventTaskCreatedOrUpdate}`) - async get() { - try { - this.loading = true; - const res = await this.api.getAllTasks( - this.pageIndex, this.pageSize, this.sortField, this.sortOrder, this.searchForm.value.search, this.activeFilter); - if (res.done) { - this.model = res.body; - this.total = this.model.total || 0; - - this.utilsService.handleLastIndex(this.total, this.model.data?.length || 0, this.pageIndex, - index => { - this.pageIndex = index; - this.get(); - }); - } - this.loading = false; - } catch (e) { - log_error(e); - this.loading = false; - } - - this.cdr.markForCheck(); - } - - async searchTasks() { - await this.get(); - } - - async onQueryParamsChange(params: NzTableQueryParams) { - const {pageSize, pageIndex, sort} = params; - this.pageIndex = pageIndex; - this.pageSize = pageSize; - - const currentSort = sort.find(item => item.value !== null); - - this.sortField = (currentSort && currentSort.key) || null; - this.sortOrder = (currentSort && currentSort.value) || null; - - await this.get(); - } - - goToProject(id?: string) { - if (!id) return; - void this.router.navigate([`/worklenz/projects/${id}`]); - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/all-tasks-attachments/all-tasks-attachments.component.html b/worklenz-frontend/src/app/administrator/projects/all-tasks-attachments/all-tasks-attachments.component.html deleted file mode 100644 index b72ad7c5..00000000 --- a/worklenz-frontend/src/app/administrator/projects/all-tasks-attachments/all-tasks-attachments.component.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - Name - Attached Task - Size - Uploaded by - Uploaded at - - - - - - - {{data.name}} - - - [{{data.task_key}}] {{data.task_name}} - Unassigned - - {{data.size}} - {{data.uploader_name}} - - {{data.created_at | fromNow}} - - -
- - - - -
- - - -
-
-
- - - - - -

Coming soon!

-

Switch between list view and thumbnail view.

-
-
- - - - - All attachments to tasks in this project will appear here. - - - - -
-
- -
- There are no attachments in the project. -
-
- - diff --git a/worklenz-frontend/src/app/administrator/projects/all-tasks-attachments/all-tasks-attachments.component.scss b/worklenz-frontend/src/app/administrator/projects/all-tasks-attachments/all-tasks-attachments.component.scss deleted file mode 100644 index 3972174a..00000000 --- a/worklenz-frontend/src/app/administrator/projects/all-tasks-attachments/all-tasks-attachments.component.scss +++ /dev/null @@ -1,9 +0,0 @@ -.file-icon { - width: 25px; - display: flex; -} - -.no-data-img-holder { - width: 100px; - margin-top: 42px; -} diff --git a/worklenz-frontend/src/app/administrator/projects/all-tasks-attachments/all-tasks-attachments.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/all-tasks-attachments/all-tasks-attachments.component.spec.ts deleted file mode 100644 index fea6acb7..00000000 --- a/worklenz-frontend/src/app/administrator/projects/all-tasks-attachments/all-tasks-attachments.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {AllTasksAttachmentsComponent} from './all-tasks-attachments.component'; - -describe('AllTasksAttachmentsComponent', () => { - let component: AllTasksAttachmentsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [AllTasksAttachmentsComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(AllTasksAttachmentsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/all-tasks-attachments/all-tasks-attachments.component.ts b/worklenz-frontend/src/app/administrator/projects/all-tasks-attachments/all-tasks-attachments.component.ts deleted file mode 100644 index fa527af1..00000000 --- a/worklenz-frontend/src/app/administrator/projects/all-tasks-attachments/all-tasks-attachments.component.ts +++ /dev/null @@ -1,139 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, NgZone, OnInit} from '@angular/core'; -import {AttachmentsApiService} from "@api/attachments-api.service"; -import { - IProjectAttachmentsViewModel, - ITaskAttachmentViewModel -} from "@interfaces/api-models/task-attachment-view-model"; -import {ActivatedRoute} from "@angular/router"; -import {getFileIcon, log_error} from "@shared/utils"; -import {HttpClient} from "@angular/common/http"; -import {NzTableQueryParams} from "ng-zorro-antd/table"; -import {DEFAULT_PAGE_SIZE} from "@shared/constants"; - -@Component({ - selector: 'worklenz-all-tasks-attachments', - templateUrl: './all-tasks-attachments.component.html', - styleUrls: ['./all-tasks-attachments.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class AllTasksAttachmentsComponent implements OnInit { - projectId: string | null = null; - selectedTaskId: string | null = null; - - total = 0; - pageSize = DEFAULT_PAGE_SIZE; - pageIndex = 1; - paginationSizes = [5, 10, 15, 20, 50, 100]; - - attachments: IProjectAttachmentsViewModel = {}; - - loading = true; - showTaskDrawer = false; - downloading = false; - - options = [ - {label: '', value: 'List', icon: 'bars'}, - {label: '', value: 'Kanban', icon: 'appstore'} - ]; - - constructor( - private readonly api: AttachmentsApiService, - private readonly route: ActivatedRoute, - private readonly cdr: ChangeDetectorRef, - private readonly http: HttpClient, - private readonly ngZone: NgZone - ) { - this.projectId = this.route.snapshot.paramMap.get("id"); - } - - ngOnInit(): void { - void this.get(); - } - - async get() { - if (!this.projectId) return; - try { - this.loading = true; - const res = await this.api.getProjectAttachment(this.projectId, this.pageIndex, this.pageSize); - if (res.done) { - this.attachments = res.body; - this.total = this.attachments.total || 0; - } - this.loading = false; - } catch (e) { - this.loading = false; - log_error(e); - } - - this.cdr.markForCheck(); - } - - async onQueryParamsChange(params: NzTableQueryParams) { - const {pageSize, pageIndex, sort} = params; - this.pageIndex = pageIndex; - this.pageSize = pageSize; - await this.get(); - } - - async download(id?: string, name?: string) { - if (!id || !name) return; - try { - this.downloading = true; - const res = await this.api.download(id, name); - if (res && res.done) { - this.ngZone.runOutsideAngular(() => { - const link = document.createElement('a'); - link.href = res.body; - link.download = name; - link.click(); - link.remove(); - }); - } - } catch (e) { - log_error(e); - } - this.downloading = false; - } - - getFileIcon(type?: string) { - return getFileIcon(type); - } - - async delete(id?: string) { - if (!id) return; - // Continue to delete from the server for previous tasks - try { - const res = await this.api.deleteTaskAttachment(id); - if (res.done) { - this.get(); - } - } catch (e) { - log_error(e); - } - - this.cdr.markForCheck(); - } - - open(url?: string) { - if (!url) return; - this.ngZone.runOutsideAngular(() => { - const a = document.createElement("a"); - a.href = url; - a.target = "_blank"; - a.style.display = "none"; - a.click(); - }); - } - - onCreateOrUpdate() { - this.showTaskDrawer = false; - this.selectedTaskId = null; - } - - onShowChange(show: boolean) { - if (!show) { - this.selectedTaskId = null; - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/context-menu/context-menu.component.html b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/context-menu/context-menu.component.html deleted file mode 100644 index 69f74d37..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/context-menu/context-menu.component.html +++ /dev/null @@ -1,28 +0,0 @@ - -
    - -
  • - - Remove from member -
  • - - -
  • -
      -
    • - - - {{item?.name || null}} - -
    • -
    -
  • -
-
- - - Move to - diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/context-menu/context-menu.component.scss b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/context-menu/context-menu.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/context-menu/context-menu.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/context-menu/context-menu.component.spec.ts deleted file mode 100644 index 0837cb70..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/context-menu/context-menu.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ContextMenuComponent } from './context-menu.component'; - -describe('ContextMenuComponent', () => { - let component: ContextMenuComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ContextMenuComponent] - }); - fixture = TestBed.createComponent(ContextMenuComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/context-menu/context-menu.component.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/context-menu/context-menu.component.ts deleted file mode 100644 index 28a50eb1..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/context-menu/context-menu.component.ts +++ /dev/null @@ -1,149 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, ViewChild} from '@angular/core'; -import {ITaskListContextMenuEvent, ITaskListGroup} from "../../../../../modules/task-list-v2/interfaces"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {Subject, takeUntil} from "rxjs"; -import {NzContextMenuService, NzDropdownMenuComponent} from "ng-zorro-antd/dropdown"; -import {Socket} from "ngx-socket-io"; -import {WlTasksService} from "../../services/wl-tasks.service"; -import {WlTasksHashMapService} from "../../services/wl-tasks-hash-map.service"; -import {SocketEvents} from "@shared/socket-events"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {smallId} from "@shared/utils"; -import {AuthService} from "@services/auth.service"; -import {ITaskAssigneesUpdateResponse} from "@interfaces/task-assignee-update-response"; - -@Component({ - selector: 'worklenz-wl-context-menu', - templateUrl: './context-menu.component.html', - styleUrls: ['./context-menu.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class WLContextMenuComponent { - @ViewChild('contextMenuDropdown', {static: false}) contextMenuDropdown!: NzDropdownMenuComponent; - @Input({required: true}) projectId: string | null = null; - @Input({required: true}) teamMemberId: string | null = null; - @Input() groups: ITaskListGroup[] = []; - - protected removing = false; - - selectedTask: IProjectTask | null = null; - - protected readonly id = smallId(4); - - private readonly _session: ILocalSession | null = null; - - get profile() { - return this.auth.getCurrentSession(); - } - - private readonly destroy$ = new Subject(); - - constructor( - private readonly contextMenuService: NzContextMenuService, - private readonly service: WlTasksService, - private readonly map: WlTasksHashMapService, - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly auth: AuthService, - ) { - this.service.onContextMenu$ - .pipe(takeUntil(this.destroy$)) - .subscribe(value => { - this.onContextMenu(value); - }); - this._session = this.auth.getCurrentSession(); - } - - private onContextMenu(value: ITaskListContextMenuEvent) { - this.selectedTask = value.task; - this.map.deselectAll(); - this.map.selectTask(value.task); - this.cdr.detectChanges(); - this.contextMenuService.create(value.event, this.contextMenuDropdown); - } - - removeMember() { - if (!this._session || !this.selectedTask || !this.teamMemberId) return - const body = { - team_member_id: this.teamMemberId, - project_id: this.projectId, - task_id: this.selectedTask.id, - reporter_id: this._session.id, - mode: 1, - parent_task: this.selectedTask.parent_task_id - }; - this.socket.emit(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), JSON.stringify(body)); - this.socket.once(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), () => { - this.service.emitRemoveMembersTask(body.task_id as string); - }) - } - - handleMemberChangeResponse = (response: ITaskAssigneesUpdateResponse) => { - try { - if (response && response.id) { - this.service.deleteTask(response.id) - this.cdr.markForCheck(); - } - } catch (e) { - // ignore - } - } - - changeGroup(toGroupId: string) { - if (!this.selectedTask) return; - const groupBy = this.service.getCurrentGroup(); - if (groupBy.value === this.service.GROUP_BY_STATUS_VALUE) { - this.handleStatusChange(toGroupId, this.selectedTask.id); - } else if (groupBy.value === this.service.GROUP_BY_PRIORITY_VALUE) { - this.handlePriorityChange(toGroupId, this.selectedTask.id); - } else if (groupBy.value === this.service.GROUP_BY_PHASE_VALUE) { - this.handlePhaseChange(toGroupId, this.selectedTask.id); - } - } - - handleStatusChange(statusId: string, taskId?: string) { - if (!taskId) return; - this.socket.emit(SocketEvents.TASK_STATUS_CHANGE.toString(), JSON.stringify({ - task_id: taskId, - status_id: statusId, -team_id: this.auth.getCurrentSession()?.team_id - })); - if (this.service.getCurrentGroup().value === this.service.GROUP_BY_STATUS_VALUE && this.selectedTask) { - this.service.updateTaskGroup(this.selectedTask, false); - if (this.service.isSubtasksIncluded) { - this.service.emitRefreshSubtasksIncluded(); - } - } - this.cdr.markForCheck(); - } - - handlePriorityChange(priorityId: string, taskId?: string) { - if (!taskId) return; - this.socket.emit(SocketEvents.TASK_PRIORITY_CHANGE.toString(), JSON.stringify({ - task_id: taskId, - priority_id: priorityId - })); - if (this.service.getCurrentGroup().value === this.service.GROUP_BY_PRIORITY_VALUE && this.selectedTask) { - this.service.updateTaskGroup(this.selectedTask, false); - if (this.service.isSubtasksIncluded) { - this.service.emitRefreshSubtasksIncluded(); - } - } - this.cdr.markForCheck(); - } - - handlePhaseChange(phaseId: string, taskId?: string) { - if (!taskId) return; - this.socket.emit(SocketEvents.TASK_PHASE_CHANGE.toString(), { - task_id: taskId, - phase_id: phaseId - }); - if (this.service.getCurrentGroup().value === this.service.GROUP_BY_PHASE_VALUE && this.selectedTask) { - this.service.updateTaskGroup(this.selectedTask, false); - if (this.service.isSubtasksIncluded) { - this.service.emitRefreshSubtasksIncluded(); - } - } - this.cdr.markForCheck(); - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/end-date/end-date.component.html b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/end-date/end-date.component.html deleted file mode 100644 index db9a6085..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/end-date/end-date.component.html +++ /dev/null @@ -1,17 +0,0 @@ -
- {{task.end_date | dateFormatter}} - - - -
diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/end-date/end-date.component.scss b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/end-date/end-date.component.scss deleted file mode 100644 index 1ffe7e49..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/end-date/end-date.component.scss +++ /dev/null @@ -1,36 +0,0 @@ -nz-date-picker { - max-width: 150px; - border-color: transparent !important; - top: 0; - bottom: 0; - left: 0; - right: 0; - - &:hover { - border-color: #d9d9d9 !important; - } -} - -.editable { - position: relative; -} - -.date-text { - display: flex; - align-items: center; - padding-left: 15px; -} - -.past-date { - color: #ff4d4f; -} - -.soon-date { - color: #52c41a; -} - -.def-g-height { - max-height: 32px; - min-height: 32px; - line-height: 32px; -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/end-date/end-date.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/end-date/end-date.component.spec.ts deleted file mode 100644 index a7e2121c..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/end-date/end-date.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { EndDateComponent } from './end-date.component'; - -describe('EndDateComponent', () => { - let component: EndDateComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [EndDateComponent] - }); - fixture = TestBed.createComponent(EndDateComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/end-date/end-date.component.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/end-date/end-date.component.ts deleted file mode 100644 index f1270975..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/end-date/end-date.component.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - HostBinding, - Input, - NgZone, - OnDestroy, - OnInit, - Renderer2 -} from '@angular/core'; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import moment from "moment/moment"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {WlTasksService} from "../../services/wl-tasks.service"; -import {formatGanttDate} from "@shared/utils"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-wl-end-date', - templateUrl: './end-date.component.html', - styleUrls: ['./end-date.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class WLEndDateComponent implements OnInit, OnDestroy { - @Input() task: IProjectTask = {}; - @HostBinding("class") cls = "flex-row task-due-date"; - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly service: WlTasksService, - private readonly renderer: Renderer2, - private readonly auth: AuthService, - ) { - } - - ngOnInit() { - this.socket.on(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.handleResponse); - } - - private handleResponse = (response: { - id: string; - parent_task: string | null; - end_date: string; - }) => { - if (response.id === this.task.id && this.task.end_date !== response.end_date) { - this.task.end_date = response.end_date; - this.cdr.markForCheck(); - } - }; - - handleEndDateChange(date: string, task: IProjectTask) { - this.socket.emit( - SocketEvents.TASK_END_DATE_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - end_date: formatGanttDate(date) || null, - parent_task: task.parent_task_id, - time_zone: this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone - })); - } - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - checkForPastDate(endDate: any) { - const formattedEndDate = moment(endDate).format('YYYY-MM-DD'); - return formattedEndDate < moment().format('YYYY-MM-DD'); - } - - checkForSoonDate(endDate: any) { - const formattedEndDate = moment(endDate).format('YYYY-MM-DD'); - const tomorrow = moment().add(1, 'day').format('YYYY-MM-DD'); - return formattedEndDate === moment().format('YYYY-MM-DD') || formattedEndDate === tomorrow; - } - -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-task-add-container/member-task-add-container.component.html b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-task-add-container/member-task-add-container.component.html deleted file mode 100644 index 2bd6c591..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-task-add-container/member-task-add-container.component.html +++ /dev/null @@ -1,21 +0,0 @@ - -
- -
-
- - {{label}} -
-
diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-task-add-container/member-task-add-container.component.scss b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-task-add-container/member-task-add-container.component.scss deleted file mode 100644 index e5663a78..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-task-add-container/member-task-add-container.component.scss +++ /dev/null @@ -1,14 +0,0 @@ -.input-icon { - font-size: 11px; -} - -.task-name { - border: 1px solid transparent; - - &:hover { - cursor: text; - background: #fff; - border: 1px solid #bfbfbf; - border-radius: 4px; - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-task-add-container/member-task-add-container.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-task-add-container/member-task-add-container.component.spec.ts deleted file mode 100644 index 7c01efbf..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-task-add-container/member-task-add-container.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { MemberTaskAddContainerComponent } from './member-task-add-container.component'; - -describe('MemberTaskAddContainerComponent', () => { - let component: MemberTaskAddContainerComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [MemberTaskAddContainerComponent] - }); - fixture = TestBed.createComponent(MemberTaskAddContainerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-task-add-container/member-task-add-container.component.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-task-add-container/member-task-add-container.component.ts deleted file mode 100644 index 9809f6d9..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-task-add-container/member-task-add-container.component.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { - ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - Input, - NgZone, - OnInit, - Output, - ViewChild -} from '@angular/core'; -import {FormBuilder, FormGroup, Validators} from "@angular/forms"; -import {smallId} from "@shared/utils"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {Socket} from "ngx-socket-io"; -import {AuthService} from "@services/auth.service"; -import {ITaskCreateRequest} from "@interfaces/api-models/task-create-request"; -import {SocketEvents} from "@shared/socket-events"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {DRAWER_ANIMATION_INTERVAL} from "@shared/constants"; -import {WlTasksService} from "../../services/wl-tasks.service"; -import {WlTasksHashMapService} from "../../services/wl-tasks-hash-map.service"; -import {ITaskAssigneesUpdateResponse} from "@interfaces/task-assignee-update-response"; -import {IWLMember} from "@interfaces/workload"; - -@Component({ - selector: 'worklenz-member-task-add-container', - templateUrl: './member-task-add-container.component.html', - styleUrls: ['./member-task-add-container.component.scss'] -}) -export class MemberTaskAddContainerComponent implements OnInit{ - @ViewChild('taskInput', {static: false}) taskInput!: ElementRef; - readonly form!: FormGroup; - - @Input({required: true}) teamMember: IWLMember | null = null; - @Input() projectId: string | null = null; - @Input() groupId: string | null = null; - @Input() label = "Add Task"; - - @Output() focusChange: EventEmitter = new EventEmitter(); - - taskInputVisible = false; - creating = false; - private session: ILocalSession | null = null; - - protected readonly id = smallId(4); - - private readonly _session: ILocalSession | null = null; - - get profile() { - return this.auth.getCurrentSession(); - } - - constructor( - private readonly socket: Socket, - private readonly auth: AuthService, - private readonly fb: FormBuilder, - private readonly service: WlTasksService, - private readonly ngZone: NgZone, - private readonly cdr: ChangeDetectorRef, - private readonly map: WlTasksHashMapService - ) { - this.form = this.fb.group({ - name: [null, [Validators.required, Validators.pattern(/^(\s+\S+\s*)*(?!\s).*$/)]] - }); - this._session = this.auth.getCurrentSession(); - } - - ngOnInit() { - this.session = this.auth.getCurrentSession(); - } - - focusTaskInput() { - this.taskInputVisible = true; - this.focusChange.emit(this.taskInputVisible); - - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.taskInput?.nativeElement.focus(); - this.taskInput?.nativeElement.select(); - }, 100); // wait for the animation end - }); - } - - addTaskInputBlur() { - this.taskInputVisible = false; - this.focusChange.emit(this.taskInputVisible); - } - - async onInputBlur() { - if (this.isValidInput()) { - await this.addInstantTask(); - return; - } - - this.addTaskInputBlur(); - } - - private createRequest() { - if (!this.projectId || !this._session) return null; - - const sess = this._session; - const body: ITaskCreateRequest = { - name: this.form.value.name, - project_id: this.projectId, - reporter_id: sess.id, - team_id: sess.team_id - }; - - const groupBy = this.service.getCurrentGroup(); - - if (groupBy.value === this.service.GROUP_BY_STATUS_VALUE) { - body.status_id = this.groupId || undefined; - } else if (groupBy.value === this.service.GROUP_BY_PRIORITY_VALUE) { - body.priority_id = this.groupId || undefined; - } else if (groupBy.value === this.service.GROUP_BY_PHASE_VALUE) { - body.phase_id = this.groupId || undefined; - } - - return body; - } - - private isValidInput() { - return this.form.valid && this.form.value.name.trim().length; - } - - async addInstantTask() { - if (this.creating) return; - if (!this.projectId || !this._session) return; - - if (this.isValidInput()) { - try { - const req = this.createRequest(); - if (!req) return; - - this.creating = true; - - this.socket.emit(SocketEvents.QUICK_TASK.toString(), JSON.stringify(req)); - this.socket.once(SocketEvents.QUICK_TASK.toString(), (task: IProjectTask) => { - const task_assign_body = { - team_member_id: this.teamMember?.team_member_id, - project_id: task.project_id, - task_id: task.id, - reporter_id: this.session?.id, - mode: 0, - }; - this.socket.emit(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), JSON.stringify(task_assign_body)); - this.socket.once(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), (response: ITaskAssigneesUpdateResponse) => { - this.creating = false; - this.onNewTaskReceived(task); - this.cdr.markForCheck(); - }); - - }); - - } catch (e) { - this.creating = false; - } - - this.cdr.markForCheck(); - } - } - - public reset(scroll = true) { - this.creating = false; - - this.form.controls["name"].setValue(null); - this.taskInputVisible = true; - - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.taskInput?.nativeElement.focus(); - if (scroll) - window.scrollTo(0, document.body.scrollHeight); - }, DRAWER_ANIMATION_INTERVAL); // wait for the animation end - }); - - this.cdr.markForCheck(); - } - - private onNewTaskReceived(task: IProjectTask) { - if (this.groupId && task.id) { - if (this.map.has(task.id)) return; - this.service.addTask(task, this.groupId); - this.service.emitRefreshMembers(); - this.reset(false); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/member-tasks-drawer.component.html b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/member-tasks-drawer.component.html deleted file mode 100644 index f053f3e2..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/member-tasks-drawer.component.html +++ /dev/null @@ -1,104 +0,0 @@ - -
- - - - - - - - - - - - - - - -
    -
  • - {{item.label}} -
  • -
-
-
- - - - -
-
- - {{group.name}} ({{group.tasks.length}}) -
-
- - - - -
- No tasks available -
-
- - -
-
-
-
-
-
-
-
-
- - - - {{teamMember?.name}} - - - - - - - - - diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/member-tasks-drawer.component.scss b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/member-tasks-drawer.component.scss deleted file mode 100644 index a62efa84..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/member-tasks-drawer.component.scss +++ /dev/null @@ -1,40 +0,0 @@ -.group-heading { - max-width: max-content; - width: max-content; - padding: 6px 16px 6px 6px; - border-top-right-radius: 4px; - border-top-left-radius: 4px; -} - -.p-skel { - padding-left: 24px; -} - -.tasks-container { - max-width: 880px; - overflow-x: auto; -} - -.new-task-input { - padding-left: 4px; - padding-top: 4px; - padding-bottom: 4px; - border-bottom: 1px solid #f0f0f0; - border-right: 1px solid #f0f0f0; - border-left: 1px solid #f0f0f0; - max-width: 880px; - overflow-x: auto; - position: sticky; - left: 0; -} - -.tasks-empty-placeholder { - width: 100%; - height: 42px; - background: #fafafa; - border-left: 1px solid #f0f0f0; - border-right: 1px solid #f0f0f0; - left: 0; - position: sticky; -} - diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/member-tasks-drawer.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/member-tasks-drawer.component.spec.ts deleted file mode 100644 index 39109fd2..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/member-tasks-drawer.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { MemberTasksDrawerComponent } from './member-tasks-drawer.component'; - -describe('MemberTasksDrawerComponent', () => { - let component: MemberTasksDrawerComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [MemberTasksDrawerComponent] - }); - fixture = TestBed.createComponent(MemberTasksDrawerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/member-tasks-drawer.component.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/member-tasks-drawer.component.ts deleted file mode 100644 index abbd0590..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/member-tasks-drawer.component.ts +++ /dev/null @@ -1,309 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - NgZone, - Output, - Renderer2 -} from '@angular/core'; -import {IWLMember, IWLTaskListGroup, IWLTasksConfig} from "@interfaces/workload"; -import {deepClone} from "@shared/utils"; -import {ProjectWorkloadApiService} from "@api/project-workload-api.service"; -import {AvatarNamesMap} from "@shared/constants"; -import {Socket} from "ngx-socket-io"; -import {WlTasksHashMapService} from "../../services/wl-tasks-hash-map.service"; -import {UtilsService} from "@services/utils.service"; -import {WlTasksService} from "../../services/wl-tasks.service"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {IGroupByOption, ITaskListGroup} from "../../../../../modules/task-list-v2/interfaces"; -import {TaskStatusesApiService} from "@api/task-statuses-api.service"; -import {TaskPrioritiesService} from "@api/task-priorities.service"; -import {TaskPhasesApiService} from "@api/task-phases-api.service"; -import {ITaskStatusCategory} from "@interfaces/task-status-category"; -import {Promise} from "@rx-angular/cdk/zone-less/browser"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; - -@Component({ - selector: 'worklenz-member-tasks-drawer', - templateUrl: './member-tasks-drawer.component.html', - styleUrls: ['./member-tasks-drawer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class MemberTasksDrawerComponent { - private _show = false; - get show(): boolean { - return this._show; - } - - @Input() set show(value: boolean) { - if (value === this._show) return; - this._show = value; - } - - @Input({required: true}) teamMember: IWLMember | null = null; - @Input({required: true}) projectId: string | null = null; - @Input({required: true}) activeTab: number = 0; - @Output() showChange: EventEmitter = new EventEmitter(); - @Output() onOpenTask = new EventEmitter(); - - private readonly DRAWER_CLOSE_TIME = 100; - - readonly BODY_STYLE = { - padding: 0, - overflowX: 'hidden', - overflowY: 'auto' - }; - - loading = false; - protected loadingGroups = false; - protected loadingStatuses = false; - protected loadingPriorities = false; - protected loadingPhases = false; - protected loadingCategories = false; - - protected groupIds: string[] = []; - - protected selectedTask: IProjectTask | null = null; - protected categories: ITaskStatusCategory[] = []; - - protected get groups() { - return this.service.groups; - } - - get selectedGroup() { - return this.service.getCurrentGroup(); - } - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: ProjectWorkloadApiService, - private readonly ngZone: NgZone, - private readonly map: WlTasksHashMapService, - private readonly socket: Socket, - private readonly renderer: Renderer2, - public readonly service: WlTasksService, - public readonly utils: UtilsService, - private readonly statusesApi: TaskStatusesApiService, - private readonly prioritiesApi: TaskPrioritiesService, - private readonly phasesApi: TaskPhasesApiService, - private readonly taskViewService: TaskViewService - ) { - this.service.setCurrentGroup(this.service.GROUP_BY_OPTIONS[0]); - - this.taskViewService.onSingleMemberChange.pipe(takeUntilDestroyed()) - .subscribe(async (teamMemberId: string) => { - if (teamMemberId === this.teamMember?.team_member_id) { - await this.getGroups(false); - } - this.cdr.markForCheck(); - }); - - this.taskViewService.onDelete - .pipe(takeUntilDestroyed()) - .subscribe(async task => { - this.init(); - }) - - this.service.onRemoveMembersTask.pipe(takeUntilDestroyed()).subscribe( (taskId: string) => { - this.service.deleteTask(taskId); - }) - - } - - tabChange(index: number) { - this.activeTab = index; - switch (index) { - case 0: - this.service.emitUpdateOverviewCharts(); - break; - case 1: - this.service.currentTab = ""; - void this.getGroups(true); - break; - case 2: - this.service.currentTab = "start_date_null" - void this.getGroups(true); - break; - case 3: - this.service.currentTab = "end_date_null" - void this.getGroups(true); - break; - case 4: - this.service.currentTab = "start_end_dates_null" - void this.getGroups(true); - break; - } - } - - private init() { - this.service.isSubtasksIncluded = true; - void Promise.all([ - this.tabChange(this.activeTab), - // this.getGroups(true), - this.getStatuses(), - this.getPriorities(), - this.getCategories(), - this.getPhases() - ]) - ; - } - - private getConf(parentTaskId?: string): IWLTasksConfig { - const config: IWLTasksConfig = { - id: this.projectId as string, - members: this.teamMember ? this.teamMember.team_member_id as string : '', - archived: false, - group: this.service.getCurrentGroup().value, - isSubtasksInclude: false, - dateChecker: this.service.currentTab - }; - - if (parentTaskId) - config.parent_task = parentTaskId; - - return config; - } - - private async getGroups(loading = true) { - if (!this.projectId || !this.teamMember) return; - try { - this.map.deselectAll(); - this.loadingGroups = loading; - const config = this.getConf(); - config.isSubtasksInclude = this.service.isSubtasksIncluded; - const res = await this.api.getTasksByMember(config) as IServerResponse; - if (res.done) { - const groups = deepClone(res.body); - this.groupIds = groups.map((g: IWLTaskListGroup) => g.id); - this.mapTasks(groups); - this.service.groups = groups; - } - this.loadingGroups = false; - } catch (e) { - this.loadingGroups = false; - } - this.cdr.markForCheck(); - } - - private mapTasks(groups: IWLTaskListGroup[]) { - for (const group of groups) { - this.map.registerGroup(group); - for (const task of group.tasks) { - if (task.start_date) task.start_date = new Date(task.start_date) as any; - if (task.end_date) task.end_date = new Date(task.end_date) as any; - } - } - } - - private async getStatuses() { - if (!this.projectId) return; - try { - this.loadingStatuses = true; - const res = await this.statusesApi.get(this.projectId); - if (res.done) - this.service.statuses = res.body; - this.loadingStatuses = false; - } catch (e) { - this.loadingStatuses = false; - } - } - - private async getPriorities() { - try { - this.loadingPriorities = true; - const res = await this.prioritiesApi.get(); - if (res.done) - this.service.priorities = res.body; - this.loadingPriorities = false; - } catch (e) { - this.loadingPriorities = false; - } - } - - private async getCategories() { - try { - this.loadingCategories = true; - const res = await this.statusesApi.getCategories(); - if (res.done) - this.categories = res.body; - this.loadingCategories = false; - } catch (e) { - this.loadingCategories = false; - } - } - - private async getPhases() { - if (!this.projectId) return; - try { - const res = await this.phasesApi.get(this.projectId); - if (res.done) - this.service.phases = res.body; - } catch (e) { - } - } - - handleCancel() { - if (this._show) { - this._show = false; - this.showChange.emit(this._show); - } - } - - onVisibilityChange(visible: boolean) { - if (visible) { - setTimeout(() => this.init(), this.DRAWER_CLOSE_TIME); - } - } - - getColor(name?: string) { - return AvatarNamesMap[name?.charAt(0).toUpperCase() || 'A']; - } - - protected trackById(index: number, item: any) { - return item.id; - } - - protected openTask(task: IProjectTask) { - this.onOpenTask.emit(task) - this.cdr.markForCheck(); - } - - async toggleCollapse(group: IWLTaskListGroup | IProjectTask) { - if (this.isTaskListGroup(group)) { - group.isExpand = !group.isExpand; - } - this.cdr.detectChanges(); - } - - isTaskListGroup(group: ITaskListGroup | IProjectTask): group is ITaskListGroup { - return (group as ITaskListGroup).tasks !== undefined; - } - - private toggleFocusCls(focused: boolean, element: HTMLElement) { - if (focused) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - } - - changeGroup(item: IGroupByOption) { - this.service.setCurrentGroup(item); - this.init(); - } - - protected handleFocusChange(focused: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - this.toggleFocusCls(focused, element); - }); - } - - unuseFunc(e: any, row: any, group: any) { - return; - } - -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/overview-tab/overview-tab.component.html b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/overview-tab/overview-tab.component.html deleted file mode 100644 index 42ae3501..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/overview-tab/overview-tab.component.html +++ /dev/null @@ -1,169 +0,0 @@ -
-
-
- - -
-
-

Tasks By Status

-
-
-
-
- -
-
- -
- No tasks to show. -
-
-
-
- - -
-
-
    -
  • - -
  • -
-
-
-
-
-
-
- - -
-
-

Tasks By Priority

-
-
-
-
- -
-
- -
- No tasks to show. -
-
-
-
- - -
-
-
    -
  • - -
  • -
-
-
-
-
-
-
- - -
-
-

Tasks By Phase

-
-
-
-
- -
-
- -
- No tasks have been assigned to phases. -
-
-
-
- - -
-
-
    -
  • - -
  • -
-
-
-
-
-
-
- - -
-
-

Tasks By Dates

-
-
-
-
- -
-
- -
- No tasks to show. -
-
-
-
- - -
-
-
    -
  • - -
  • -
-
-
-
-
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/overview-tab/overview-tab.component.scss b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/overview-tab/overview-tab.component.scss deleted file mode 100644 index be663b9e..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/overview-tab/overview-tab.component.scss +++ /dev/null @@ -1,15 +0,0 @@ -.chart-details { - height: 220px; - max-height: 220px; - overflow-y: auto; -} - -.no-data-img-holder { - width: 65px; -} - -.charts-holder { - max-width: 880px; - margin-left: auto; - margin-right: auto; -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/overview-tab/overview-tab.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/overview-tab/overview-tab.component.spec.ts deleted file mode 100644 index 13748697..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/overview-tab/overview-tab.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { OverviewTabComponent } from './overview-tab.component'; - -describe('OverviewTabComponent', () => { - let component: OverviewTabComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [OverviewTabComponent] - }); - fixture = TestBed.createComponent(OverviewTabComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/overview-tab/overview-tab.component.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/overview-tab/overview-tab.component.ts deleted file mode 100644 index 278565a4..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/overview-tab/overview-tab.component.ts +++ /dev/null @@ -1,191 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, ViewChild} from '@angular/core'; -import {log_error} from "@shared/utils"; -import {ProjectWorkloadApiService} from "@api/project-workload-api.service"; -import {IWLMember, IWLMemberOverview, IWLMemberOverviewResponse} from "@interfaces/workload"; -import {BaseChartDirective} from "ng2-charts"; -import {ChartConfiguration} from "chart.js"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {WlTasksService} from "../../../services/wl-tasks.service"; -import {of} from "rxjs"; - -@Component({ - selector: 'worklenz-overview-tab', - templateUrl: './overview-tab.component.html', - styleUrls: ['./overview-tab.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class OverviewTabComponent { - @Input({required: true}) teamMember: IWLMember | null = null; - @Input({required: true}) projectId: string | null = null; - - @ViewChild(BaseChartDirective) statusChart: BaseChartDirective | undefined; - @ViewChild(BaseChartDirective) priorityChart: BaseChartDirective | undefined; - @ViewChild(BaseChartDirective) phaseChart: BaseChartDirective | undefined; - @ViewChild(BaseChartDirective) datesChart: BaseChartDirective | undefined; - - memberTasksStatusOverview: IWLMemberOverview[] = []; - memberTasksPriorityOverview: IWLMemberOverview[] = []; - memberTasksPhaseOverview: IWLMemberOverview[] = []; - memberTasksDatesOverview: IWLMemberOverview[] = []; - - loading = false; - isStatusChartEmpty = false; - isPriorityChartEmpty = false; - isPhaseChartEmpty = false; - isDatesChartEmpty = false; - - statusColors: string[] = []; - priorityColors: string[] = []; - phaseColors: string[] = []; - datesColors: string[] = []; - - statusChartData: ChartConfiguration<'doughnut'>['data'] = { - labels: [], - datasets: [{ - label: 'Tasks', - data: [], - backgroundColor: this.statusColors, - hoverOffset: 2 - }] - }; - - priorityChartData: ChartConfiguration<'doughnut'>['data'] = { - labels: [], - datasets: [{ - label: 'Tasks', - data: [], - backgroundColor: this.priorityColors, - hoverOffset: 2 - }] - }; - - phaseChartData: ChartConfiguration<'doughnut'>['data'] = { - labels: [], - datasets: [{ - label: 'Tasks', - data: [], - backgroundColor: this.phaseColors, - hoverOffset: 2 - }] - }; - - datesChartData: ChartConfiguration<'doughnut'>['data'] = { - labels: [], - datasets: [{ - label: 'Tasks', - data: [], - backgroundColor: this.datesColors, - hoverOffset: 2 - }] - }; - - chartOptions: ChartConfiguration<'doughnut'>['options'] = { - plugins: { - datalabels: { - display: false - } - }, - responsive: false - } - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: ProjectWorkloadApiService, - public readonly service: WlTasksService, - ) { - this.service.updateOverviewCharts - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.init(); - }) - } - - init() { - void this.getMemberOverviewData(); - } - - private async getMemberOverviewData() { - if (!this.projectId || !this.teamMember) return; - try { - this.loading = true; - this.clearCharts(); - const res = await this.api.getMemberOverview(this.projectId, this.teamMember.team_member_id); - if (res.done) { - this.memberTasksStatusOverview = res.body.by_status; - this.memberTasksPriorityOverview = res.body.by_priority; - this.memberTasksPhaseOverview = res.body.by_phase; - this.memberTasksDatesOverview = res.body.by_dates; - this.drawCharts(res.body); - } - this.loading = false; - this.cdr.markForCheck(); - } catch (e) { - this.loading = false; - log_error(e) - this.cdr.markForCheck(); - } - } - - private drawCharts(data: IWLMemberOverviewResponse) { - if (data.by_status) { - for (const item of data.by_status) { - this.statusChartData.labels?.push(item.label); - this.statusChartData.datasets[0].data.push(item.tasks_count || 0); - this.statusColors.push(item.color_code as string); - } - this.statusChart?.update(); - if (this.statusChartData.datasets[0].data.every(value => value === 0)) - this.isStatusChartEmpty = true; - } - - if (data.by_priority) { - for (const item of data.by_priority) { - this.priorityChartData.labels?.push(item.label); - this.priorityChartData.datasets[0].data.push(item.tasks_count || 0); - this.priorityColors.push(item.color_code as string); - } - this.priorityChart?.update(); - if (this.priorityChartData.datasets[0].data.every(value => value === 0)) - this.isPriorityChartEmpty = true; - } - - if (data.by_phase) { - for (const item of data.by_phase) { - this.phaseChartData.labels?.push(item.label); - this.phaseChartData.datasets[0].data.push(item.tasks_count || 0); - this.phaseColors.push(item.color_code as string); - } - this.phaseChart?.update(); - if (this.phaseChartData.datasets[0].data.every(value => value === 0)) - this.isPhaseChartEmpty = true; - } - - if (data.by_dates) { - for (const item of data.by_dates) { - this.datesChartData.labels?.push(item.label); - this.datesChartData.datasets[0].data.push(item.tasks_count || 0); - this.datesColors.push(item.color_code as string); - } - this.datesChart?.update(); - if (this.datesChartData.datasets[0].data.every(value => value === 0)) - this.isDatesChartEmpty = true; - } - this.cdr.markForCheck(); - } - - clearCharts() { - this.statusChartData.datasets[0].data = []; - this.priorityChartData.datasets[0].data = []; - this.phaseChartData.datasets[0].data = []; - this.datesChartData.datasets[0].data = []; - - this.statusChartData.labels = []; - this.priorityChartData.labels = []; - this.phaseChartData.labels = []; - this.datesChartData.labels = []; - - this.cdr.markForCheck(); - } - - protected readonly of = of; -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/phase/phase.component.html b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/phase/phase.component.html deleted file mode 100644 index 80d87dda..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/phase/phase.component.html +++ /dev/null @@ -1,20 +0,0 @@ -
- - - - - -
diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/phase/phase.component.scss b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/phase/phase.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/phase/phase.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/phase/phase.component.spec.ts deleted file mode 100644 index 0a565fcb..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/phase/phase.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { WLPhaseComponent } from './phase.component'; - -describe('PhaseComponent', () => { - let component: WLPhaseComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [WLPhaseComponent] - }); - fixture = TestBed.createComponent(WLPhaseComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/phase/phase.component.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/phase/phase.component.ts deleted file mode 100644 index 5e35245b..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/phase/phase.component.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostBinding, - Input, - NgZone, - OnDestroy, - OnInit, - Renderer2 -} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ALPHA_CHANNEL} from "@shared/constants"; -import {ITaskPhase} from "@interfaces/api-models/task-phase"; -import {Socket} from "ngx-socket-io"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {ITaskPhaseChangeResponse} from "@interfaces/task-phase-change-response"; -import {WlTasksService} from "../../services/wl-tasks.service"; - -@Component({ - selector: 'worklenz-wl-phase', - templateUrl: './phase.component.html', - styleUrls: ['./phase.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class WLPhaseComponent implements OnInit, OnDestroy { - @Input({required: true}) task: IProjectTask = {}; - @HostBinding("class") cls = "flex-row task-phase"; - - readonly PHASE_COLOR = "#a9a9a9" + ALPHA_CHANNEL; - readonly PLACEHOLDER_COLOR = 'rgba(0, 0, 0, 0.85) !important'; - - phases: ITaskPhase[] = []; - - loading = false; - - constructor( - private readonly service: WlTasksService, - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly element: ElementRef, - private readonly renderer: Renderer2 - ) { - this.service.onPhaseChange$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.updatePhases(); - this.cdr.markForCheck(); - }); - } - - ngOnInit() { - this.updatePhases(); - this.socket.on(SocketEvents.TASK_PHASE_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_PHASE_CHANGE.toString(), this.handleResponse); - } - - private isGroupByPhase() { - return this.service.getCurrentGroup().value === this.service.GROUP_BY_PHASE_VALUE; - } - - trackById(index: number, item: ITaskStatusViewModel) { - return item.id; - } - - handleChange(phaseId: string, taskId?: string) { - if (!taskId) return; - this.socket.emit(SocketEvents.TASK_PHASE_CHANGE.toString(), { - task_id: taskId, - phase_id: phaseId, - parent_task: this.task.parent_task_id - }); - } - - private handleResponse = (response: ITaskPhaseChangeResponse) => { - if (response && response.task_id === this.task.id) { - this.task.phase_color = response.color_code || undefined; - this.task.phase_id = response.id; - - if (this.isGroupByPhase()) { - this.service.updateTaskGroup(this.task, false); - if (this.service.isSubtasksIncluded) { - this.service.emitRefreshSubtasksIncluded(); - } - } - this.cdr.markForCheck(); - } - } - - private updatePhases() { - this.phases = this.service.phases; - } - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - handleOpen(open: boolean) { - this.toggleHighlightCls(open, this.element.nativeElement); - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/priority/priority.component.html b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/priority/priority.component.html deleted file mode 100644 index e7adb32e..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/priority/priority.component.html +++ /dev/null @@ -1,15 +0,0 @@ -
- - - - - -
diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/priority/priority.component.scss b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/priority/priority.component.scss deleted file mode 100644 index dd984827..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/priority/priority.component.scss +++ /dev/null @@ -1,5 +0,0 @@ -nz-select { - max-width: 96px; - width: 100%; - text-overflow: ellipsis; -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/priority/priority.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/priority/priority.component.spec.ts deleted file mode 100644 index a4ae6a21..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/priority/priority.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { WLPriorityComponent } from './priority.component'; - -describe('PriorityComponent', () => { - let component: WLPriorityComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [WLPriorityComponent] - }); - fixture = TestBed.createComponent(WLPriorityComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/priority/priority.component.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/priority/priority.component.ts deleted file mode 100644 index 9ad9e7dd..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/priority/priority.component.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostBinding, - Input, - NgZone, - OnDestroy, - OnInit, - Renderer2 -} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITaskPrioritiesGetResponse} from "@interfaces/api-models/task-priorities-get-response"; -import {Socket} from "ngx-socket-io"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {filter} from "rxjs"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {WlTasksService} from "../../services/wl-tasks.service"; - -@Component({ - selector: 'worklenz-wl-priority', - templateUrl: './priority.component.html', - styleUrls: ['./priority.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class WLPriorityComponent implements OnInit, OnDestroy { - @Input({required: true}) task: IProjectTask = {}; - @HostBinding("class") cls = "flex-row task-priority"; - - priorities: ITaskPrioritiesGetResponse[] = []; - - loading = false; - - constructor( - private readonly service: WlTasksService, - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly element: ElementRef, - private readonly renderer: Renderer2 - ) { - this.service.onPrioritiesChange$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.updatePriorities(); - this.cdr.markForCheck(); - }); - } - - ngOnInit() { - this.updatePriorities(); - this.socket.on(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.priorities = []; - this.socket.removeListener(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.handleResponse); - } - - private isGroupByPriority() { - return this.service.getCurrentGroup().value === this.service.GROUP_BY_PRIORITY_VALUE; - } - - trackById(index: number, item: ITaskStatusViewModel) { - return item.id; - } - - handlePriorityChange(priorityId: string, data: IProjectTask) { - this.socket.emit(SocketEvents.TASK_PRIORITY_CHANGE.toString(), JSON.stringify({ - task_id: data.id, - priority_id: priorityId, - parent_task: this.task.parent_task_id - })); - } - - private handleResponse = (response: { - priority_id: string | undefined; - name: string | undefined; id: string; parent_task: string; color_code: string; - }) => { - if (response && response.id === this.task.id) { - this.task.priority_color = response.color_code; - this.task.priority = response.priority_id; - - if (this.isGroupByPriority()) { - this.service.updateTaskGroup(this.task, false); - if (this.service.isSubtasksIncluded) { - this.service.emitRefreshSubtasksIncluded(); - } - } - this.cdr.markForCheck(); - } - } - - private updatePriorities() { - this.priorities = this.service.priorities; - } - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - handleOpen(open: boolean) { - this.toggleHighlightCls(open, this.element.nativeElement); - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/start-date/start-date.component.html b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/start-date/start-date.component.html deleted file mode 100644 index 1c125973..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/start-date/start-date.component.html +++ /dev/null @@ -1,17 +0,0 @@ -
- - - - {{task.start_date | dateFormatter}} - - -
diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/start-date/start-date.component.scss b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/start-date/start-date.component.scss deleted file mode 100644 index 17785765..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/start-date/start-date.component.scss +++ /dev/null @@ -1,28 +0,0 @@ -nz-date-picker { - max-width: 150px; - border-color: transparent !important; - top: 0; - bottom: 0; - left: 0; - right: 0; - - &:hover { - border-color: #d9d9d9 !important; - } -} - -.editable { - position: relative; -} - -.date-text { - display: flex; - align-items: center; - padding-left: 15px; -} - -.def-g-height { - max-height: 32px; - min-height: 32px; - line-height: 32px; -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/start-date/start-date.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/start-date/start-date.component.spec.ts deleted file mode 100644 index f6df4a45..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/start-date/start-date.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { StartDateComponent } from './start-date.component'; - -describe('StartDateComponent', () => { - let component: StartDateComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [StartDateComponent] - }); - fixture = TestBed.createComponent(StartDateComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/start-date/start-date.component.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/start-date/start-date.component.ts deleted file mode 100644 index 96d72c8f..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/start-date/start-date.component.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - HostBinding, - Input, - NgZone, - OnInit, - Renderer2 -} from '@angular/core'; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {WlTasksService} from "../../services/wl-tasks.service"; -import {formatGanttDate} from "@shared/utils"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-wl-start-date', - templateUrl: './start-date.component.html', - styleUrls: ['./start-date.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class WLStartDateComponent implements OnInit { - @Input({required: true}) task: IProjectTask | null = null; - @HostBinding("class") cls = "flex-row task-due-date"; - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly service: WlTasksService, - private readonly renderer: Renderer2, - private readonly auth: AuthService, - ) { - } - - ngOnInit() { - this.socket.on(SocketEvents.TASK_START_DATE_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_START_DATE_CHANGE.toString(), this.handleResponse); - } - - private handleResponse = (response: { - id: string; - parent_task: string | null; - start_date: string; - }) => { - if(!this.task) return; - if (response.id === this.task.id && this.task.start_date !== response.start_date) { - this.task.start_date = response.start_date; - this.cdr.markForCheck(); - } - }; - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - handleStartDateChange(date: string, task: IProjectTask) { - this.socket.emit( - SocketEvents.TASK_START_DATE_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - start_date: formatGanttDate(date) || null, - parent_task: task.parent_task_id, - time_zone: this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone - })); - } - -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/status/status.component.html b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/status/status.component.html deleted file mode 100644 index 3610373e..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/status/status.component.html +++ /dev/null @@ -1,14 +0,0 @@ -
- - - - - -
diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/status/status.component.scss b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/status/status.component.scss deleted file mode 100644 index dd984827..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/status/status.component.scss +++ /dev/null @@ -1,5 +0,0 @@ -nz-select { - max-width: 96px; - width: 100%; - text-overflow: ellipsis; -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/status/status.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/status/status.component.spec.ts deleted file mode 100644 index 1fcecc44..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/status/status.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { StatusComponent } from './status.component'; - -describe('StatusComponent', () => { - let component: StatusComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [StatusComponent] - }); - fixture = TestBed.createComponent(StatusComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/status/status.component.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/status/status.component.ts deleted file mode 100644 index fe0fb72d..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/status/status.component.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostBinding, - Input, - NgZone, - OnDestroy, - OnInit, - Renderer2 -} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {Socket} from "ngx-socket-io"; -import {WlTasksService} from "../../services/wl-tasks.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {filter} from "rxjs"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskListStatusChangeResponse} from "@interfaces/task-list-status-change-response"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-wl-status', - templateUrl: './status.component.html', - styleUrls: ['./status.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class WLStatusComponent implements OnInit, OnDestroy { - @Input({required: true}) task: IProjectTask = {}; - @HostBinding("class") cls = "flex-row task-status"; - - statuses: ITaskStatusViewModel[] = []; - loading = false; - - constructor( - private readonly service: WlTasksService, - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly element: ElementRef, - private readonly renderer: Renderer2, - private readonly auth: AuthService, - ) { - this.service.onStatusesChange$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.updateStatuses(); - this.cdr.markForCheck(); - }); - } - - ngOnInit() { - this.updateStatuses(); - this.socket.on(SocketEvents.TASK_STATUS_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_STATUS_CHANGE.toString(), this.handleResponse); - } - - private getTaskProgress(taskId: string) { - this.socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), taskId); - } - - private isGroupByStatus() { - return this.service.getCurrentGroup().value === this.service.GROUP_BY_STATUS_VALUE; - } - - trackById(index: number, item: ITaskStatusViewModel) { - return item.id; - } - - handleStatusChange(statusId: string, taskId?: string) { - if (!taskId) return; - - this.socket.emit(SocketEvents.TASK_STATUS_CHANGE.toString(), JSON.stringify({ - task_id: taskId, - status_id: statusId, - parent_task: this.task.parent_task_id, -team_id: this.auth.getCurrentSession()?.team_id - })); - - this.getTaskProgress(taskId); - } - - private handleResponse = (response: ITaskListStatusChangeResponse) => { - if (response && response.id === this.task.id) { - this.task.status_color = response.color_code; - this.task.complete_ratio = +response.complete_ratio || 0; - this.task.status = response.status_id; - this.task.status_category = response.statusCategory; - - if (this.isGroupByStatus()) { - this.service.updateTaskGroup(this.task, false); - if (this.service.isSubtasksIncluded) { - this.service.emitRefreshSubtasksIncluded(); - } - } - this.cdr.markForCheck(); - } - } - - private updateStatuses() { - this.statuses = this.service.statuses; - } - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - handleOpen(open: boolean) { - this.toggleHighlightCls(open, this.element.nativeElement); - } - -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-header/task-list-header.component.html b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-header/task-list-header.component.html deleted file mode 100644 index 6dee69f9..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-header/task-list-header.component.html +++ /dev/null @@ -1,36 +0,0 @@ - - -
- -
- - -
Task
- - - -
Status
-
- - - -
Priority
-
- - - -
- {{phaseLabel | ellipsis : 10}} -
-
- - - -
Start Date
-
- - - -
Due Date
-
diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-header/task-list-header.component.scss b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-header/task-list-header.component.scss deleted file mode 100644 index 8c01e7a9..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-header/task-list-header.component.scss +++ /dev/null @@ -1,60 +0,0 @@ -.flex-row { - padding: 4px 11px; - background-color: #fafafa; - border-top: 1px solid #f0f0f0; - border-bottom: 1px solid #f0f0f0; - border-right: 1px solid #f0f0f0; - display: flex; - align-items: center; - flex-direction: row; -} - -.task-check { - text-align: center; - padding: 8px 6px 8px 6px !important; - border-left: 1px solid #f0f0f0; - position: sticky; - left: 0px; - z-index: 1; -} - -.task-name { - width: 450px; - min-width: 450px; - position: sticky; - left: 29px; - z-index: 1; - - nz-filter-trigger { - margin-left: auto; - } - - &.left-0 { - left: 47px; - } - -} - -.task-status { - width: 120px; - min-width: 120px; -} - -.task-phase { - width: 150px; - min-width: 150px; -} - -.task-priority { - width: 120px; - min-width: 120px; -} - -.task-start-date, .task-due-date { - width: 150px; - min-width: 150px; -} - -.visibility-hidden { - visibility: hidden; -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-header/task-list-header.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-header/task-list-header.component.spec.ts deleted file mode 100644 index d6f4108c..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-header/task-list-header.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskListHeaderComponent } from './task-list-header.component'; - -describe('TaskListHeaderComponent', () => { - let component: TaskListHeaderComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskListHeaderComponent] - }); - fixture = TestBed.createComponent(TaskListHeaderComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-header/task-list-header.component.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-header/task-list-header.component.ts deleted file mode 100644 index 253fd41f..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-header/task-list-header.component.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - HostBinding, - Input, - OnInit, - Output -} from '@angular/core'; -import {TaskListV2Service} from "../../../../../modules/task-list-v2/task-list-v2.service"; -import {TaskListHashMapService} from "../../../../../modules/task-list-v2/task-list-hash-map.service"; -import {ProjectPhasesService} from "@services/project-phases.service"; -import {AuthService} from "@services/auth.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {merge} from "rxjs"; - -@Component({ - selector: 'worklenz-task-list-header', - templateUrl: './task-list-header.component.html', - styleUrls: ['./task-list-header.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListHeaderComponent { - @HostBinding("class") headerCls = "d-flex header mb-0 flex-row"; - - @Input() groupId!: string; - @Output() selectChange = new EventEmitter(); - - checked = false; - indeterminate = false; - - get phaseLabel() { - return this.phasesService.label; - } - - constructor( - public readonly service: TaskListV2Service, - private readonly map: TaskListHashMapService, - private readonly cdr: ChangeDetectorRef, - private readonly phasesService: ProjectPhasesService, - public readonly auth: AuthService - ) { - this.map.onDeselectAll$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.checked = false; - this.indeterminate = false; - this.cdr.markForCheck(); - }); - - this.phasesService.onLabelChange - .pipe(takeUntilDestroyed()) - .subscribe(value => { - this.cdr.markForCheck(); - }); - - merge(this.map.onSelect$, this.map.onDeselect$) - .pipe(takeUntilDestroyed()) - .subscribe(() => { - if (this.map.isAllDeselected(this.groupId)) { - this.checked = false; - this.indeterminate = false; - } else if (this.map.isAllSelected(this.groupId)) { - this.checked = true; - this.indeterminate = false; - } else { - this.indeterminate = true; - } - - this.cdr.markForCheck(); - }); - } - - onAllChecked(checked: boolean) { - this.selectChange?.emit(checked); - } - -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-row/task-list-row.component.html b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-row/task-list-row.component.html deleted file mode 100644 index d2d8cfd0..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-row/task-list-row.component.html +++ /dev/null @@ -1,104 +0,0 @@ -
- -
- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- -
-
-
- - - - {{ task.name }}   -
-
- - -
- - - {{task.sub_tasks_count}} - - - -
-
-
- - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-row/task-list-row.component.scss b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-row/task-list-row.component.scss deleted file mode 100644 index c33e6079..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-row/task-list-row.component.scss +++ /dev/null @@ -1,232 +0,0 @@ -.hidden-arrow { - display: none !important; -} - -.dropdown-highlight:hover { - background-color: rgba(208, 238, 250, 0.33); - border: #5587f5 1px solid; - border-radius: 3px; -} - -.plus-icon { - display: none; - position: absolute; - right: 0; - z-index: 1; - top: 0; - bottom: 0; - height: 100%; -} - -.expanded { - transform: rotate(-90deg); -} - -.sub-tasks-arrow { - position: relative; - cursor: pointer; - left: 3px; - width: 16px; - padding: 2px; - border: 1px solid transparent; - z-index: 1; - - .sub-arrow { - width: 10px; - height: 10px; - color: #191919; - margin-left: -2px; - } -} - -.task-name-text { - border: 1px solid transparent; - padding-left: 2px; - border-radius: 4px; - - &:hover { - border: 1px solid #d9d9d9; - } -} - -.task-name { - border: 1px solid transparent; - - &:hover { - cursor: text; - background: #fff; - border-radius: 4px; - } -} - -.highlight-col { - border: 1px solid #1890ff !important; - - nz-date-picker { - box-shadow: none; - } -} - -.editable { - .add-button { - visibility: hidden; - } - - &:hover { - .add-button { - visibility: visible; - } - } -} - -.ant-popover { - width: 500px; -} - -.flex-table { - display: flex; -} - -.rows { - .flex-row { - padding: 3px 12px; - border-bottom: 1px solid #f0f0f0; - border-right: 1px solid #f0f0f0; - background: white; - display: flex; - align-items: center; - max-height: 50px; - border-radius: 0px; - } - - &:hover .flex-row { - background: #f8f7f9; - } -} - -.subtask { - .flex-row { - background: #fcfcfc; - } -} - -.task-check { - text-align: center; - padding: 8px 6px 8px 6px !important; - border-left: 1px solid #f0f0f0; - position: sticky; - z-index: 1; - left: 0px; -} - -.task-arrow { - width: 20px; - min-width: 20px; - padding: 8px 11px 8px 2px !important; - border-right: none !important; - position: sticky; - z-index: 1; - left: 29px; - - &.highlight-col { - border-top: 1px solid #188fff !important; - border-left: 1px solid #188fff !important; - border-bottom: 1px solid #188fff !important; - } -} - -.task-name { - width: 450px; - min-width: 450px; - position: sticky; - z-index: 1; - border-radius: 0px; - padding-right: 25px; - left: 29px; - - &.highlight-col { - border: 1px solid #188fff !important; - } - -} - -.task-status { - width: 120px; - min-width: 120px; -} - -.task-phase { - width: 150px; - min-width: 150px; -} - -.task-priority { - width: 120px; - min-width: 120px; -} - -.task-start-date, .task-due-date { - width: 150px; - min-width: 150px; -} - -.task-due-date { - padding: 0px 0px !important; - - .editable { - align-items: center; - display: flex; - } -} - -.task-name-text { - width: 100%; - -webkit-line-clamp: 1; - display: -webkit-box; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; -} - -.inner-icon-cont { - width: max-content; - display: flex; - justify-content: flex-end; - align-items: center; - column-gap: 4px; -} - -.name-input { - padding: 5px 12px; - border-left: 1px solid transparent; -} - -.double-arrow { - line-height: 16px; - border: none; - cursor: pointer; -} - -.task-placeholder { - width: 100%; - height: 42px; - border: 1px dashed #d9d9d9; - background: #fafafa; -} - -.task-open-btn { - opacity: 0; - position: absolute; - right: 0; - top: 4px; - background: whitesmoke; - transition: 0.25s all; -} - -.task-name { - &:hover { - & .task-open-btn { - opacity: 1; - } - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-row/task-list-row.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-row/task-list-row.component.spec.ts deleted file mode 100644 index 1e8ef5d7..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-row/task-list-row.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { WLTaskListRowComponent } from './task-list-row.component'; - -describe('TaskListRowComponent', () => { - let component: WLTaskListRowComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [WLTaskListRowComponent] - }); - fixture = TestBed.createComponent(WLTaskListRowComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-row/task-list-row.component.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-row/task-list-row.component.ts deleted file mode 100644 index a9a6c68e..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-list-row/task-list-row.component.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, EventEmitter, HostBinding, HostListener, - Input, - NgZone, - OnDestroy, - OnInit, Output, - Renderer2 -} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {Socket} from "ngx-socket-io"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; -import {UtilsService} from "@services/utils.service"; -import {WlTasksService} from "../../services/wl-tasks.service"; -import {WlTasksHashMapService} from "../../services/wl-tasks-hash-map.service"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskListStatusChangeResponse} from "@interfaces/task-list-status-change-response"; -import {filter, merge} from "rxjs"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; - -@Component({ - selector: 'worklenz-wl-task-list-row', - templateUrl: './task-list-row.component.html', - styleUrls: ['./task-list-row.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class WLTaskListRowComponent implements OnInit, OnDestroy { - @Input({required: true}) task!: IProjectTask; - @Output() onShowSubTasks = new EventEmitter(); - @Output() onOpenTask = new EventEmitter(); - @HostBinding("class") cls = "position-relative task-row"; - - private readonly highlight = 'highlight-col'; - - protected editId: string | null = null; - - protected selected = false; - - protected readonly Number = Number; - - public get id() { - return this.task.id; - } - - constructor( - private readonly element: ElementRef, - private readonly renderer: Renderer2, - public readonly service: WlTasksService, - private readonly cdr: ChangeDetectorRef, - private readonly socket: Socket, - private readonly map: WlTasksHashMapService, - private readonly ngZone: NgZone, - private readonly view: TaskViewService, - public readonly utils: UtilsService - ) { - merge( - this.map.onSelect$.pipe( - filter(value => value.id === this.id), - filter(() => !this.selected) - ), - this.map.onDeselect$.pipe( - filter(value => value.id === this.id), - filter(() => this.selected) - ), - this.map.onDeselectAll$.pipe( - filter(() => this.selected) - ) - ).pipe( - takeUntilDestroyed() - ).subscribe(value => { - this.selected = !this.selected; - this.toggleSelection(); - this.markForCheck(); - }); - } - - private toggleSelection() { - this.ngZone.runOutsideAngular(() => { - const cls = "selected"; - const ele = this.element.nativeElement; - - if (this.selected) { - this.renderer.addClass(ele, cls); - } else { - this.renderer.removeClass(ele, cls); - } - }); - } - - ngOnInit() { - this.registerSocketEvents(); - } - - ngOnDestroy() { - this.unregisterSocketEvents(); - } - - private registerSocketEvents() { - this.socket.on(SocketEvents.TASK_NAME_CHANGE.toString(), this.handleNameChangeResponse); - } - - private unregisterSocketEvents() { - this.socket.removeListener(SocketEvents.TASK_NAME_CHANGE.toString(), this.handleNameChangeResponse); - } - - @HostListener("contextmenu", ["$event"]) - private onContextMenu(event: MouseEvent) { - this.service.emitOnContextMenu(event, this.task); - } - - focus(tr: HTMLDivElement) { - setTimeout(() => { - const element = tr.querySelector("input"); - element?.focus(); - }); - } - - onCheckChange(checked: boolean) { - if (checked) { - this.map.selectTask(this.task); - } else { - this.map.deselectTask(this.task); - } - - this.toggleSelection(); - } - - openSubTasks() { - this.onShowSubTasks?.emit(this.task); - } - - openTask(task: IProjectTask) { - this.onOpenTask?.emit(task); - } - - selectCol(element: HTMLDivElement) { - if (element.classList.contains(this.highlight)) return; - element.classList.add(this.highlight); - } - - deselectCol(element: HTMLDivElement) { - element.classList.remove(this.highlight); - this.editId = null; - } - - handleNameChange(data?: IProjectTask) { - if (!data) return; - this.socket.emit(SocketEvents.TASK_NAME_CHANGE.toString(), JSON.stringify({ - task_id: data.id, - name: data.name, - parent_task: this.task.parent_task_id - })); - this.editId = null; - } - - onTaskNameClick(event: MouseEvent, tr1: HTMLDivElement, task: IProjectTask) { - event.stopPropagation(); - this.focus(tr1); - this.editId = task.id || null; - } - - public markForCheck() { - this.cdr.markForCheck(); - } - - public detectChanges() { - this.cdr.detectChanges(); - } - - private handleNameChangeResponse = (response: { id: string; parent_task: string; name: string; }) => { - if (!response) return; - if (this.id !== response.id) return; - - if (this.task && this.task.name != response.name) { - this.task.name = response.name; - this.markForCheck(); - } - }; - - private handleCompletedAt = (response: ITaskListStatusChangeResponse) => { - if (!response.id) return; - if (this.id !== response.id) return; - this.task.completed_at = response.completed_at; - } - -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-name/task-name.component.html b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-name/task-name.component.html deleted file mode 100644 index a214a45c..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-name/task-name.component.html +++ /dev/null @@ -1,15 +0,0 @@ -
-
-
- {{task.task_name}} -
-
-
- -
- -
diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-name/task-name.component.scss b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-name/task-name.component.scss deleted file mode 100644 index c74ef393..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-name/task-name.component.scss +++ /dev/null @@ -1,47 +0,0 @@ -input { - padding-left: 4px; - padding-right: 0px; -} - -.border-input { - border: 1px solid transparent; - border-radius: 4px; - transition: 0.25s all; - padding-left: 4px; - max-width: 260px; - - &:hover { - border: 1px solid #d9d9d9; - } -} - -.task-name-block, .name-holder, .border-input { - max-height: 32px; - min-height: 32px; - line-height: 32px; -} - -.ellipsis { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - - -.task-open-btn { - opacity: 0; - position: absolute; - right: 0; - top: 0; - background: whitesmoke; - transition: 0.25s all; -} - -.task-name-block { - position: relative; - &:hover { - & .task-open-btn { - opacity: 1; - } - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-name/task-name.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-name/task-name.component.spec.ts deleted file mode 100644 index 0ffb2281..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-name/task-name.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskNameComponent } from './task-name.component'; - -describe('TaskNameComponent', () => { - let component: TaskNameComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskNameComponent] - }); - fixture = TestBed.createComponent(TaskNameComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-name/task-name.component.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-name/task-name.component.ts deleted file mode 100644 index e0199de1..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/components/task-name/task-name.component.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - Input, - OnDestroy, - OnInit, - Output, - ViewChild -} from '@angular/core'; -import {IWLMemberTask} from "@interfaces/workload"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; - -@Component({ - selector: 'worklenz-task-name', - templateUrl: './task-name.component.html', - styleUrls: ['./task-name.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskNameComponent implements OnInit, OnDestroy { - @ViewChild('input') input!: ElementRef; - @Input({required: true}) task: IWLMemberTask | null = null; - @Output() openTask = new EventEmitter(); - - initialTaskName: string | null = null; - - isEditing = false; - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - ) { - } - - ngOnInit() { - if (this.task?.task_name) this.initialTaskName = this.task.task_name; - this.socket.on(SocketEvents.TASK_NAME_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_NAME_CHANGE.toString(), this.handleResponse); - } - - enableEdit() { - this.isEditing = true; - setTimeout(() => { - if (this.input) { - this.input.nativeElement.focus(); - } - }, 250); - } - - validateName() { - if (this.task && this.task.task_name.trim() === '') { - this.task.task_name = this.initialTaskName as string; - this.isEditing = false; - this.cdr.markForCheck(); - return; - } else { - this.changeName(); - } - } - - changeName() { - if (!this.task) return; - this.socket.emit( - SocketEvents.TASK_NAME_CHANGE.toString(), JSON.stringify({ - task_id: this.task.task_id, - name: this.task.task_name, - parent_task: null, - })); - this.isEditing = false; - } - - private handleResponse = (response: { id: string; parent_task: string; name: string; }) => { - if (!response) return; - if (this.task?.task_id !== response.id) return; - - if (this.task && this.task.task_name != response.name) { - this.task.task_name = response.name; - this.cdr.markForCheck(); - } - }; - -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/services/wl-tasks-hash-map.service.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/services/wl-tasks-hash-map.service.ts deleted file mode 100644 index 8041f987..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/services/wl-tasks-hash-map.service.ts +++ /dev/null @@ -1,173 +0,0 @@ -import {Injectable} from '@angular/core'; -import {Subject} from "rxjs"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {IWLTaskListGroup} from "@interfaces/workload"; - -@Injectable({ - providedIn: 'root' -}) -export class WlTasksHashMapService { - private readonly _selectSbj$ = new Subject(); - private readonly _deselectSbj$ = new Subject(); - private readonly _deselectAllSbj$ = new Subject(); - - /** Map */ - private readonly _groupTaskMap = new Map(); - /** Map */ - private readonly _taskGroupIdsMap = new Map(); - /** Map */ - private readonly _selectedTasksMap = new Map(); - /** Map */ - private readonly _allTasksMap = new Map(); - /** Map(); - - private _selectedCount = 0; - - public get tasks() { - return this._allTasksMap; - } - - public get onSelect$() { - return this._selectSbj$.asObservable(); - } - - public get onDeselect$() { - return this._deselectSbj$.asObservable(); - } - - public get onDeselectAll$() { - return this._deselectAllSbj$.asObservable(); - } - - public reset() { - this._groupTaskMap.clear(); - this._taskGroupIdsMap.clear(); - this._selectedTasksMap.clear(); - this._allTasksMap.clear(); - this._subTasksMap.clear(); - this._selectedCount = 0; - } - - public registerGroup(group: IWLTaskListGroup) { - for (const task of group.tasks) { - this.add(group.id, task); - } - } - - public add(groupId: string, task: IProjectTask) { - if (!task.id) return; - this.updateGroupTaskMap(groupId, task.id); - this._taskGroupIdsMap.set(task.id, groupId); - this._allTasksMap.set(task.id, task); - - if (task.parent_task_id) { - this.updateSubtasksMap(task.parent_task_id, task) - } - } - - public addGroupTask(groupId: string, task: IProjectTask) { - if (!task.id) return; - this._taskGroupIdsMap.set(task.id, groupId); - } - - public has(taskId: string) { - return this._allTasksMap.has(taskId); - } - - public remove(task: IProjectTask) { - if (!task.id) return; - this.deselectTask(task); - this._taskGroupIdsMap.get(task.id); - this._allTasksMap.delete(task.id); - } - - private updateGroupTaskMap(groupId: string, taskId: string, selected?: boolean) { - const map = this._groupTaskMap.get(groupId); - if (map) { - if (typeof selected === "boolean") { - map[taskId] = selected; - } else { - delete map[taskId]; - } - - this._groupTaskMap.set(groupId, map); - } else { - this._groupTaskMap.set(groupId, {[taskId]: selected || false}); - } - } - - private updateSubtasksMap(parentTaskId: string, task: IProjectTask, selected?: boolean) { - const subtasks = this._subTasksMap.get(parentTaskId) || []; - const isParentTaskAvailable = subtasks.some((subtask) => subtask.id === task.id); - - // Only push the subtask if the parent task is not available - if (!isParentTaskAvailable) { - subtasks.push(task); - this._subTasksMap.set(parentTaskId, subtasks); - } - - } - - public selectTask(task: IProjectTask) { - if (this._selectedTasksMap.get(task.id as string)) return; - - this._selectedTasksMap.set(task.id as string, task); - this._selectedCount++; - - this.updateGroupTaskMap( - this._taskGroupIdsMap.get(task.id as string) as string, - task.id as string, - true - ); - - this._selectSbj$.next(task); - - } - - public deselectTask(task: IProjectTask) { - if (this._selectedTasksMap.has(task.id as string)) { - this._selectedTasksMap.delete(task.id as string); - this._selectedCount--; - - this.updateGroupTaskMap( - this._taskGroupIdsMap.get(task.id as string) as string, - task.id as string, - false - ); - - this._deselectSbj$.next(task); - } - } - - private deselectLocalGroups() { - for (const [groupId, task] of this._groupTaskMap) { - for (const taskId in task) { - this.updateGroupTaskMap(groupId, taskId, false); - } - } - } - - public deselectAll() { - if (!this._selectedTasksMap.size) return; - - this.deselectLocalGroups(); - this._selectedTasksMap.clear(); - this._selectedCount = 0; - - this._deselectAllSbj$.next(); - } - - public getGroupId(taskId: string) { - return this._taskGroupIdsMap.get(taskId); - } - - public getSelectedTasks() { - const tasks = []; - for (const [, task] of this._selectedTasksMap.entries()) { - tasks.push(task); - } - return tasks; - } - -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/services/wl-tasks.service.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/services/wl-tasks.service.ts deleted file mode 100644 index d32d40b6..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/services/wl-tasks.service.ts +++ /dev/null @@ -1,264 +0,0 @@ -import {Injectable} from '@angular/core'; -import {BehaviorSubject, Subject} from "rxjs"; -import { - IGroupByOption, - ITaskListContextMenuEvent, - ITaskListGroupChangeResponse -} from "../../../../modules/task-list-v2/interfaces"; -import {ITeamMemberViewModel} from "@interfaces/api-models/team-members-get-response"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {ITaskPrioritiesGetResponse} from "@interfaces/api-models/task-priorities-get-response"; -import {ITaskPhase} from "@interfaces/api-models/task-phase"; -import {Socket} from "ngx-socket-io"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {SocketEvents} from "@shared/socket-events"; -import {IWLTaskListGroup} from "@interfaces/workload"; -import {WlTasksHashMapService} from "./wl-tasks-hash-map.service"; - -@Injectable({ - providedIn: 'root' -}) -export class WlTasksService { - private readonly membersSbj$ = new Subject(); - private readonly statusesSbj$ = new Subject(); - private readonly prioritiesSbj$ = new Subject(); - private readonly contextMenuSbj$ = new Subject(); - private readonly taskAddOrDeleteSbj$ = new BehaviorSubject(null); - private readonly phasesSbj$ = new Subject(); - private readonly updateOverviewChartsSbj$ = new Subject(); - private readonly removeMembersTaskSbj$ = new Subject(); - private readonly refreshOnlyMembersSbj$ = new Subject(); - private readonly refreshSubtasksIncludedSbj$ = new Subject(); - - public readonly HIGHLIGHT_COL_CLS = 'highlight-col'; - - public readonly GROUP_BY_STATUS_VALUE = "status"; - public readonly GROUP_BY_PRIORITY_VALUE = "priority"; - public readonly GROUP_BY_PHASE_VALUE = "phase"; - public readonly GROUP_BY_OPTIONS: IGroupByOption[] = [ - {label: "Status", value: this.GROUP_BY_STATUS_VALUE}, - {label: "Priority", value: this.GROUP_BY_PRIORITY_VALUE}, - {label: "Phase", value: this.GROUP_BY_PHASE_VALUE} - ]; - - public groups: IWLTaskListGroup[] = []; - - private _members: ITeamMemberViewModel[] = []; - private _statuses: ITaskStatusViewModel[] = []; - private _priorities: ITaskPrioritiesGetResponse[] = []; - private _phases: ITaskPhase[] = []; - - public isSubtasksIncluded = false; - - public currentTab: "" | "end_date_null" | "start_date_null" | "start_end_dates_null" = ""; - - private get _currentGroup(): IGroupByOption { - const key = localStorage.getItem("worklenz.wltasklist.group_by"); - if (key) { - const g = this.GROUP_BY_OPTIONS.find(o => o.value === key); - if (g) - return g; - } - return this.GROUP_BY_OPTIONS[0]; - } - - private set _currentGroup(option) { - localStorage.setItem("worklenz.wltasklist.group_by", option.value); - } - - public set members(value) { - this._members = value; - this.membersSbj$.next(); - } - - public get members() { - return this._members; - } - - public set priorities(value) { - this._priorities = value; - this.prioritiesSbj$.next(); - } - - public get priorities() { - return this._priorities; - } - - public set phases(value) { - this._phases = value; - this.phasesSbj$.next(); - } - - public get phases() { - return this._phases; - } - - get onStatusesChange$() { - return this.statusesSbj$.asObservable(); - } - - get onPrioritiesChange$() { - return this.prioritiesSbj$.asObservable(); - } - - get onContextMenu$() { - return this.contextMenuSbj$.asObservable(); - } - - get onPhaseChange$() { - return this.phasesSbj$.asObservable(); - } - - set statuses(value) { - this._statuses = value; - this.statusesSbj$.next(); - } - - get statuses() { - return this._statuses; - } - - get updateOverviewCharts() { - return this.updateOverviewChartsSbj$.asObservable(); - } - - get onRemoveMembersTask() { - return this.removeMembersTaskSbj$.asObservable(); - } - - constructor( - private readonly socket: Socket, - private readonly map: WlTasksHashMapService - ) { - } - - public setCurrentGroup(group: IGroupByOption) { - this._currentGroup = group; - } - - public getCurrentGroup() { - return this._currentGroup; - } - - public onRefreshMembers() { - return this.refreshOnlyMembersSbj$.asObservable(); - } - - public emitRefreshMembers() { - this.refreshOnlyMembersSbj$.next(); - } - - public emitRefreshSubtasksIncluded() { - this.refreshSubtasksIncludedSbj$.next(); - } - - - public emitOnContextMenu(event: MouseEvent, task: IProjectTask) { - this.contextMenuSbj$.next({event, task}); - } - - public emitTaskAddOrDelete(taskId: string, isSubTask: boolean) { - this.taskAddOrDeleteSbj$.next({ - taskId: taskId, - isSubTask: isSubTask - }); - } - - public emitUpdateOverviewCharts() { - this.updateOverviewChartsSbj$.next(); - } - - public emitRemoveMembersTask(taskId: string) { - this.removeMembersTaskSbj$.next(taskId); - } - - public getGroupIdByGroupedColumn(task: IProjectTask) { - const groupBy = this.getCurrentGroup().value; - if (groupBy === this.GROUP_BY_STATUS_VALUE) - return task.status as string; - - if (groupBy === this.GROUP_BY_PRIORITY_VALUE) - return task.priority as string; - - if (groupBy === this.GROUP_BY_PHASE_VALUE) - return task.phase_id as string; - - return null; - } - - public updateTaskGroup(task: IProjectTask, insert = true) { - if (!task.id) return; - const groupId = this.getGroupIdByGroupedColumn(task); - - if (groupId) { - // Delete the task from its current group - this.deleteTask(task.id); - - // Add the task to the new group - this.addTask(task, groupId, insert); - } - } - - public deleteTask(taskId: string, index: number | null = null) { - // Get the group id of the task. - const groupId = this.map.getGroupId(taskId); - if (!groupId || !taskId) return; - - // Find the group that contains the task. - const group = this.groups.find(g => g.id === groupId); - if (!group) return; - - // Get the task object from the list of selected tasks. - const task = this.map.getSelectedTasks().find(t => t.id === taskId); - - // If the task is a sub-task, remove it from its parent task's sub-tasks list. - if (task?.is_sub_task) { - const parentTask = group.tasks.find(t => t.id === task.parent_task_id); - if (parentTask) { - // Find the index of the sub-task in the parent task's sub-tasks list. - const index = parentTask.sub_tasks?.findIndex(t => t.id === task.id); - if (typeof index !== "undefined" && index !== -1) { - if (!parentTask.sub_tasks_count) parentTask.sub_tasks_count = 0; - parentTask.sub_tasks_count = Math.max(+parentTask.sub_tasks_count - 1, 0); - parentTask.sub_tasks?.splice(index, 1); - this.emitTaskAddOrDelete(parentTask.id as string, true); - } - } - this.socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), parentTask?.id); - this.map.remove(task); - } else { // If the task is not a sub-task, remove it from the group's task list. - const taskIndex = index ?? group.tasks.findIndex(t => t.id === taskId); - if (taskIndex !== -1) { - this.map.remove(group.tasks[taskIndex]); - group.tasks.splice(taskIndex, 1); - this.emitTaskAddOrDelete(taskId, false); - } - } - - // Deselect all tasks after removing the task. - this.map.deselectAll(); - } - - public addTask(task: IProjectTask, groupId: string, insert = false) { - const group = this.groups.find(g => g.id === groupId); - if (group && task.id) { - // Add the task to the group's task array - if (insert) { - group.tasks.unshift(task); - } else { - group.tasks.push(task); - } - this.map.add(groupId, task); - this.emitTaskAddOrDelete(task.parent_task_id as string, !!task.parent_task_id); - } - } - - public reset() { - this._members = []; - this._statuses = []; - this._priorities = []; - - this.groups = []; - this.isSubtasksIncluded = false; - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/workload-gaant-chart-v2.component.html b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/workload-gaant-chart-v2.component.html deleted file mode 100644 index 34ff6d2b..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/workload-gaant-chart-v2.component.html +++ /dev/null @@ -1,248 +0,0 @@ - -
- -
-
- -
- No tasks have been assigned to project members. - Assign tasks to members. -
-
-
-
-
-
-
-
-
-
- Member -
-
-
-
-
-
-
- -
-
- {{member.name}} -
-
- - No start date or end date assigned - - - - - {{member.tasks_start_date | date : "MMM dd, YY"}} - - - - - {{member.tasks_end_date | date : "MMM dd, YY"}} - - - - -
-
-
-
-
-
-
-
-
-
-
-
- -
- - Total tasks - ({{member.tasks_stats.total}}) -
-
- - Tasks without Start date - ({{member.tasks_stats.null_start_dates}}) -
-
- - Tasks without End date - ({{member.tasks_stats.null_end_dates}}) -
-
- - Tasks without Start and End dates - ({{member.tasks_stats.null_start_end_dates}}) -
-
-
-
-
-
- -
-
- {{member.name}} -
-
- - No tasks assigned yet - -
-
-
- -
- - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
-
-
-
-
- {{m.month}} -
-
-
-
-
-
-
-
- {{d.name}} -
-
- {{d.day}} -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - - - - - - - - - - - - - -
-
-
-
-
-
-
- From {{customToolTipStartDate | date : 'MMM dd, YYYY'}} - to {{customToolTipEndDate | date : 'MMM dd, YYYY'}} - No start & end date -
-
- - - - - - diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/workload-gaant-chart-v2.component.scss b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/workload-gaant-chart-v2.component.scss deleted file mode 100644 index ab7336ab..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/workload-gaant-chart-v2.component.scss +++ /dev/null @@ -1,316 +0,0 @@ -.gannt { - border-top: 1px solid #f0f0f0; - border-bottom: 1px solid #f0f0f0; -} - -.top, .top-left-placeholder { - max-height: 70px; - min-height: 70px; - position: sticky; - top: 0; - z-index: 1; - background: white; -} - -.top-left-placeholder { - width: 100%; - right: 0; - box-shadow: 0px 1px #f0f0f0; -} - -.fixed-left-column, .fixed-right-column { - overflow-y: auto; - min-height: calc(100vh - 220px); - max-height: calc(100vh - 220px); - will-change: transform; - transition: scroll-behavior 0.125s ease; -} - -.fixed-left-column { - min-width: 500px; - max-width: 500px; - overflow-x: scroll; - box-shadow: 10px 0px 8px -8px #00000026; - border-left: 1px solid #f0f0f0; - z-index: 9; -} - -.member-details { - max-width: 270px; - min-width: 270px; -} - -.scroll-animation { - scroll-behavior: smooth; -} - -.fixed-right-column { - position: relative; - overflow-x: scroll; - box-shadow: 1px 0px #f0f0f0; -} - -.single-member, .day-boundary-inner, .single-member-task, .h-default { - min-height: 60px; - max-height: 60px; -} - -.single-member-task { - box-shadow: 1px 1px #f0f0f0; -} - -.single-member { - border-bottom: 1px solid #f0f0f0; - padding-left: 7px; - transition: 0.25s all; - - &.active { - background: #f8f7f9; - } -} - -.m-t-name { - min-width: 270px; - max-width: 270px; -} - -.m-t-start-date, .m-t-end-date { - min-width: 100px; - max-width: 100px; -} - -.task-bar { - background: #d9d9d9; - position: absolute; - min-height: 22px; - max-height: 22px; - border-radius: 4px; - overflow: hidden; - z-index: 0; - cursor: move; - transition: 0.125s all; -} - -.duration-indicator { - position: absolute; - border-radius: 4px; - top: 10px; - bottom: 10px; - overflow: hidden; - z-index: 0; - transition: 0.125s all; - border: 1px solid; -} - -.day-boundary { - position: relative; - box-shadow: -1px 1px #f0f0f0; -} - -.month-boundary { - box-shadow: -1px 0px #f0f0f0; - padding-left: 14px; - background: #e6f7ff; -} - - -.resize-handle { - position: absolute; - width: 5px; - height: 100%; - cursor: col-resize; - background-color: #ccc; -} - -.left-handle { - left: 0; -} - -.right-handle { - right: 0; -} - -.active-hover-bg { - cursor: pointer; -} - -table { - position: absolute; - top: 0; - height: 100% -} - -.weekend { - background: linear-gradient(-45deg, rgb(230 230 230 / 50%) 12.5%, transparent 12.5%, transparent 50%, rgb(230 230 230 / 50%) 50%, rgb(230 230 230 / 50%) 62.5%, transparent 62.5%, transparent) 0% 0%/5px 5px; -} - -.middle { - position: relative; -} - -.day-cells { - position: absolute; - top: 0; - bottom: 0; - height: 100%; - z-index: -1; -} - -.inner-day-cell { - box-shadow: -1px 1px #f0f0f0; - height: 100%; -} - -.bar-top { - padding-top: 11px; -} - -.name-ellipsis { - max-width: 200px; - line-height: 14px; -} - -.ellipsis { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.stat-indicator, .indicator-bars { - border-radius: 4px; -} - -.indicator-bars { - min-width: 185px; - max-width: 185px; -} - -.stat-indicator, .indicator-bars, .bar-no-start-date, .bar-no-end-date, .bar-no-start-end-date, .bar-have-start-end-date { - min-height: 26px; - max-height: 26px; -} - -.bar-no-start-date { - background: rgb(248 169 169 / 55%); - transition: 0.2s all; - - &:hover { - background: rgb(248 146 146 / 85%); - } -} - -.bar-no-end-date { - background: rgb(249 160 160 / 75%); - transition: 0.2s all; - - &:hover { - background: rgb(248 146 146 / 90%); - } -} - -.bar-no-start-end-date { - background: rgb(247 167 167 / 90%); - transition: 0.2s all; - - &:hover { - background: rgb(248 146 146); - } -} - -.bar-have-start-end-date { - background: #f0f0f0; - transition: 0.2s all; -} - -.m-duration-text { - color: rgba(0, 0, 0, 0.65); - font-size: 12px; -} - -.today { - position: relative; - - &::after { - position: absolute; - content: ""; - left: 0; - top: 0; - bottom: 0; - width: 2px; - z-index: -1; - background: #188ff9a6; - } -} - -.today-bg { - background: #188ff9a6; - - &::after { - display: none; - } - - & div { - color: white; - } -} - -.highlighter { - position: absolute; - top: 25px; - bottom: 0; - z-index: -1; -} - -.d-name { - font-size: 13px -} - -.d-day { - line-height: 25px -} - -.month-name { - min-height: 25px; - max-height: 25px -} - -.hover-light-blue { - transition: 0.25s all; - - &:hover { - background-color: #f8f7f9; - } -} - - -.custom-tooltip-element { - position: absolute; - opacity: 0; - scale: 0; - z-index: 999; - padding: 6px 8px; - color: #fff; - text-align: left; - text-decoration: none; - word-wrap: break-word; - background-color: #4a4a4a; - border-radius: 4px; - box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); - transition: 0.25s scale; -} - -.no-data-img-holder { - max-width: 100px; -} - -.navigator { - transition: 0.25s all; - &:hover { - color: #1890ff; - } -} - -.th-text { - color: #000000d9; - font-weight: 500; -} diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/workload-gaant-chart-v2.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/workload-gaant-chart-v2.component.spec.ts deleted file mode 100644 index abc6e0fe..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/workload-gaant-chart-v2.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { WorkloadGaantChartV2Component } from './workload-gaant-chart-v2.component'; - -describe('WorkloadGaantChartV2Component', () => { - let component: WorkloadGaantChartV2Component; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [WorkloadGaantChartV2Component] - }); - fixture = TestBed.createComponent(WorkloadGaantChartV2Component); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/workload-gaant-chart-v2.component.ts b/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/workload-gaant-chart-v2.component.ts deleted file mode 100644 index 16043cd6..00000000 --- a/worklenz-frontend/src/app/administrator/projects/components-v2/workload-gaant-chart-v2/workload-gaant-chart-v2.component.ts +++ /dev/null @@ -1,338 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - NgZone, - OnDestroy, - OnInit, - QueryList, - ViewChild, - ViewChildren -} from '@angular/core'; -import {ISingleMonth, IWLMember} from "@interfaces/workload"; -import {log_error} from "@shared/utils"; -import {AvatarNamesMap, DRAWER_ANIMATION_INTERVAL, GANNT_COLUMN_WIDTH} from "@shared/constants"; -import {ActivatedRoute} from "@angular/router"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {ProjectWorkloadApiService} from "@api/project-workload-api.service"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {WlTasksService} from "./services/wl-tasks.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; -import {AuthService} from "@services/auth.service"; -import {TaskTimerService} from "@admin/components/task-timer/task-timer.service"; - -@Component({ - selector: 'worklenz-workload-gaant-chart-v2', - templateUrl: './workload-gaant-chart-v2.component.html', - styleUrls: ['./workload-gaant-chart-v2.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) - -export class WorkloadGaantChartV2Component implements OnInit, OnDestroy { - @ViewChild('scroller') scroller?: ElementRef; - @ViewChild('fixed_right_column') fixed_right_column?: ElementRef; - @ViewChild('fixed_left_column') fixed_left_column?: ElementRef; - @ViewChild('custom_tooltip_element') custom_tooltip_element?: ElementRef; - @ViewChildren('duration_indicator') duration_indicators?: QueryList; - - protected readonly GANNT_COLUMN_WIDTH = 30; - protected showTaskModal = false; - protected showMemberModal = false; - protected selectedTaskId: string | null = null; - protected selectedTeamMember: IWLMember | null = null; - - initialScroll = 0; - numberOfDays: number = 0; - activeTab = 0; - - projectId: string | null = null; - chartStart: string | null = null; - chartEnd: string | null = null; - customToolTipStartDate: string | null = null; - customToolTipEndDate: string | null = null; - customToolTipLeft: number | null = null; - customToolTipTop: number | null = null; - - months: ISingleMonth[] = [] - workloadMembers: IWLMember[] = []; - selectedTask: IProjectTask | null = null; - - loading = false; - isResized = false; - - constructor( - private readonly api: ProjectWorkloadApiService, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly socket: Socket, - private route: ActivatedRoute, - private readonly service: WlTasksService, - private readonly taskViewService: TaskViewService, - private readonly auth: AuthService, - private readonly timerService: TaskTimerService - ) { - this.projectId = this.route.snapshot.paramMap.get("id"); - - this.taskViewService.onViewBackFrom - .pipe(takeUntilDestroyed()) - .subscribe(task => { - const task_: IProjectTask = { - id: task.parent_task_id, - project_id: task.project_id, - } - this.handleTaskSelectFromView(task_); - }); - - this.taskViewService.onDelete - .pipe(takeUntilDestroyed()) - .subscribe(async task => { - if (task.parent_task_id) { - const task_: IProjectTask = { - id: task.parent_task_id, - project_id: this.projectId as string - } - this.handleTaskSelectFromView(task_); - } - await this.refresh(false); - }) - - this.service.onRemoveMembersTask.pipe(takeUntilDestroyed()).subscribe(async(taskId: string) => { - await this.refresh(false); - }) - - this.timerService.onSubmitOrUpdate.pipe(takeUntilDestroyed()).subscribe(async() => { - await this.refresh(false); - }) - - this.service.onRefreshMembers().pipe(takeUntilDestroyed()).subscribe( - async() => { - await this.getMembers(false); - } - ) - this.taskViewService.onSingleMemberChange.pipe(takeUntilDestroyed()) - .subscribe((teamMemberId: string) => { - void this.init(false); - }); - - } - - async ngOnInit() { - await this.init(true); - this.socket.on(SocketEvents.TASK_START_DATE_CHANGE.toString(), this.refresh); - this.socket.on(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.refresh); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_START_DATE_CHANGE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.refresh); - } - - refresh = async (response: any) => { - await this.init(false); - } - - async init(isLoading: boolean) { - await this.createChart(isLoading); - await this.getMembers(isLoading); - } - - async createChart(isLoading: boolean) { - if (!this.projectId || !this.auth.getCurrentSession()?.timezone_name) return; - try { - this.loading = isLoading; - const res = await this.api.getGanntDates(this.projectId, this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name as string : Intl.DateTimeFormat().resolvedOptions().timeZone); - if (res.done) { - this.months = res.body.date_data; - this.numberOfDays = res.body.width; - this.initialScroll = res.body.scroll_by; - this.chartStart = res.body.chart_start; - this.chartEnd = res.body.chart_end; - } - this.cdr.markForCheck(); - } catch (e) { - log_error(e); - this.cdr.markForCheck(); - } - } - - async getMembers(scroll: boolean) { - if (!this.projectId || !this.auth.getCurrentSession()?.timezone_name) return; - try { - const res = await this.api.getWorkloadMembers(this.projectId, this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name as string : Intl.DateTimeFormat().resolvedOptions().timeZone); - if (res.done) { - this.workloadMembers = res.body; - await this.initScrollHandler(scroll); - this.cdr.markForCheck(); - } - } catch (e) { - log_error(e); - this.loading = false; - this.cdr.markForCheck(); - } - } - - getColor(name?: string) { - return AvatarNamesMap[name?.charAt(0).toUpperCase() || 'A']; - } - - async initScrollHandler(needed: boolean) { - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - if (this.fixed_right_column && needed) { - this.fixed_right_column.nativeElement.scrollLeft = this.initialScroll - (2 * GANNT_COLUMN_WIDTH); - this.scrollListner(); - } - }, 100) - }); - this.loading = false; - this.cdr.markForCheck(); - } - - afterViewScrollHandler(fromLeft: number) { - this.ngZone.runOutsideAngular(() => { - if (this.fixed_right_column) { - this.fixed_right_column.nativeElement.classList.add('scroll-animation'); - this.fixed_right_column.nativeElement.scrollLeft = fromLeft - (2 * GANNT_COLUMN_WIDTH); - setTimeout(() => { - if (this.fixed_right_column) { - this.fixed_right_column.nativeElement.classList.remove('scroll-animation'); - } - }, 125); - } - }); - this.cdr.markForCheck(); - } - - scrollListner() { - this.ngZone.runOutsideAngular(() => { - this.fixed_left_column?.nativeElement.addEventListener('scroll', () => { - if (this.fixed_right_column) this.fixed_right_column.nativeElement.scrollTop = this.fixed_left_column?.nativeElement.scrollTop; - }); - this.fixed_right_column?.nativeElement.addEventListener('scroll', () => { - if (this.fixed_left_column) this.fixed_left_column.nativeElement.scrollTop = this.fixed_right_column?.nativeElement.scrollTop; - }); - }) - } - - private handleTaskSelectFromView(task: IProjectTask) { - this.showTaskModal = false; - setTimeout(() => { - if (task) { - this.openTask(task); - } - }, DRAWER_ANIMATION_INTERVAL); - this.cdr.detectChanges(); - } - - protected openTask(task: IProjectTask) { - this.selectedTask = task; - this.showTaskModal = true; - this.cdr.markForCheck(); - } - - onShowChange(show: boolean) { - if (!show) { - this.selectedTask = null; - } - } - - protected openMember(member: IWLMember, tab: number) { - this.selectedTeamMember = member; - this.activeTab = tab; - this.showMemberModal = true; - this.cdr.markForCheck(); - } - - hoverInIndicator(elem: HTMLDivElement, highlighter: HTMLDivElement, colorCode: string, event: MouseEvent, member: IWLMember) { - this.ngZone.runOutsideAngular(() => { - this.setIndicatorHoverStyle(elem, colorCode); - this.setHighlighterStyle(highlighter, elem, colorCode); - this.setCustomTooltipStyle(event, member); - this.cdr.detectChanges(); - }); - } - - hoverOutIndicator(elem: HTMLDivElement, highlighter: HTMLDivElement) { - this.ngZone.runOutsideAngular(() => { - this.removeIndicatorHoverStyle(elem); - this.removeHighlighterStyle(highlighter); - this.removeCustomTooltipStyle(); - this.cdr.detectChanges(); - }); - } - - hoverInMember(index: number, highlighter: HTMLDivElement, colorCode: string) { - this.ngZone.runOutsideAngular(() => { - if (this.duration_indicators) { - const elem: HTMLDivElement = this.duration_indicators.toArray()[index].nativeElement; - this.setIndicatorHoverStyle(elem, colorCode); - this.setHighlighterStyle(highlighter, elem, colorCode); - this.cdr.detectChanges(); - } - }); - } - - hoverOutMember(index: number, highlighter: HTMLDivElement) { - this.ngZone.runOutsideAngular(() => { - if (this.duration_indicators) { - const elem: HTMLDivElement = this.duration_indicators.toArray()[index].nativeElement; - this.removeIndicatorHoverStyle(elem); - this.removeHighlighterStyle(highlighter); - this.cdr.detectChanges(); - } - }); - } - - setIndicatorHoverStyle(elem: HTMLDivElement, colorCode: string) { - elem.style.backgroundColor = colorCode + "69"; - elem.style.borderColor = "transparent"; - elem.style.top = '7px'; - elem.style.bottom = '7px'; - } - - removeIndicatorHoverStyle(elem: HTMLDivElement) { - elem.style.backgroundColor = '#d9e3ee'; - elem.style.borderColor = "#adb5bd"; - elem.style.top = '10px'; - elem.style.bottom = '10px'; - } - - setHighlighterStyle(highlighter: HTMLDivElement, elem: HTMLDivElement, colorCode: string) { - highlighter.style.backgroundColor = colorCode + "70"; - highlighter.style.left = elem.style.left; - highlighter.style.width = elem.style.width; - } - - removeHighlighterStyle(highlighter: HTMLDivElement) { - highlighter.style.backgroundColor = "transparent"; - highlighter.style.left = '0px'; - highlighter.style.width = '0px'; - } - - setCustomTooltipStyle(event: MouseEvent, member: IWLMember) { - this.customToolTipLeft = event.clientX + 15; - this.customToolTipTop = event.clientY; - this.customToolTipStartDate = member.tasks_start_date; - this.customToolTipEndDate = member.tasks_end_date; - if (this.custom_tooltip_element) { - this.custom_tooltip_element.nativeElement.style.opacity = 1; - this.custom_tooltip_element.nativeElement.style.scale = 1; - } - this.cdr.detectChanges(); - } - - removeCustomTooltipStyle() { - this.customToolTipLeft = null; - this.customToolTipTop = null; - this.customToolTipStartDate = null; - this.customToolTipEndDate = null; - if (this.custom_tooltip_element) { - this.custom_tooltip_element.nativeElement.style.opacity = 0; - this.custom_tooltip_element.nativeElement.style.scale = 0; - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/components/project-stats/project-stats.component.html b/worklenz-frontend/src/app/administrator/projects/project-insights/components/project-stats/project-stats.component.html deleted file mode 100644 index 1560d2c3..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/components/project-stats/project-stats.component.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - -
- -
-
- -
Completed tasks
-

{{overviewData.completed_tasks_count || 0}}

-
-
-
-
- - - - - -
- -
-
- -
Incomplete tasks
-

{{overviewData.todo_tasks_count || 0}}

-
-
-
-
- - - - - -
- -
-
- -
Overdue tasks -
-

{{overviewData.overdue_count || 0}}

-
-
-
-
- - - - - -
- - -
-
- -
{{checkOverLoggedOrNot(overviewData) ? 'Over' : 'Under'}} logged hours - -
-

- {{checkOverLoggedOrNot(overviewData) ? '(' + (overviewData.overlogged_hours || 0) + ')' : overviewData.overlogged_hours}} -

- - - - - - - - - - -
Total Estimation:  {{overviewData.total_estimated_hours_string || 'Oh 0m'}}
Total Logged Hours:  {{overviewData.total_logged_hours_string || 'Oh 0m'}}
-
-
-
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/components/project-stats/project-stats.component.scss b/worklenz-frontend/src/app/administrator/projects/project-insights/components/project-stats/project-stats.component.scss deleted file mode 100644 index 1053b124..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/components/project-stats/project-stats.component.scss +++ /dev/null @@ -1,20 +0,0 @@ -.fs-16 { - font-size: 16px; -} - -.fs-26 { - font-size: 36px; -} - -nz-card:hover { - border-color: #d3d3d3; - box-shadow: 0 0 0 1px #f1f1f1, 0 2px 10px 0 rgba(109, 110, 111, 0.08); -} - -.fs-50 { - font-size: 50px; -} - -.icon-img-holder { - width: 80%; -} \ No newline at end of file diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/components/project-stats/project-stats.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/components/project-stats/project-stats.component.spec.ts deleted file mode 100644 index b4a2d586..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/components/project-stats/project-stats.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ProjectStatsComponent} from './project-stats.component'; - -describe('ProjectStatsComponent', () => { - let component: ProjectStatsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ProjectStatsComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ProjectStatsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/components/project-stats/project-stats.component.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/components/project-stats/project-stats.component.ts deleted file mode 100644 index 63031579..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/components/project-stats/project-stats.component.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Input, - OnChanges, - OnInit, - SimpleChanges -} from '@angular/core'; -import {IProjectInsightsGetRequest} from "@interfaces/api-models/project-insights"; -import {log_error} from "@shared/utils"; -import {ProjectInsightsService} from "@api/project-insights.service"; -import {ActivatedRoute} from "@angular/router"; - -@Component({ - selector: 'worklenz-project-stats', - templateUrl: './project-stats.component.html', - styleUrls: ['./project-stats.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectStatsComponent implements OnInit, OnChanges { - @Input() archived = false; - - private readonly includeArchivedTasks = "include-archived-tasks"; - - loading = false; - overviewData: IProjectInsightsGetRequest = {}; - projectId: string = ''; - - constructor( - private api: ProjectInsightsService, - private route: ActivatedRoute, - private readonly cdr: ChangeDetectorRef, - ) { - this.projectId = this.route.snapshot.paramMap.get('id') || ''; - } - - ngOnInit() { - // this.getProjectOverviewData(); - } - - ngOnChanges(changes: SimpleChanges) { - this.getProjectOverviewData(); - } - - get archivedTasksChoice() { - return localStorage.getItem(this.includeArchivedTasks) === 'true'; - } - - async getProjectOverviewData() { - try { - this.loading = true; - const res = await this.api.getProjectOverviewData(this.projectId, this.archivedTasksChoice); - if (res.done) { - this.overviewData = res.body; - } - this.loading = false; - this.cdr.markForCheck(); - } catch (e) { - this.loading = false; - log_error(e); - this.cdr.markForCheck(); - } - } - - checkOverLoggedOrNot(overviewData: IProjectInsightsGetRequest) { - if (overviewData.total_estimated_hours === null || overviewData.total_logged_hours === null) return false; - return (overviewData.total_estimated_hours || 0) < (overviewData.total_logged_hours || 0); - } - -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights.component.html b/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights.component.html deleted file mode 100644 index 5c2310f7..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights.component.html +++ /dev/null @@ -1,17 +0,0 @@ - - -
- - - - -
-
- - - - diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights.component.scss b/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights.component.scss deleted file mode 100644 index ce021806..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights.component.scss +++ /dev/null @@ -1,25 +0,0 @@ -.archived-toggler { - padding: 7px 15px; - font-size: 14px; - border-radius: 4px; - background: whitesmoke; -} - -.archived-toggler label { - position: relative; -} - -.archived-toggler label::after { - position: absolute; - content: ""; - right: -5px; - top: -1px; - width: 8px; - height: 8px; - border-radius: 10px; - background: #f5222d; -} - -.archived-toggler.archived label::after { - background: #52c41a; -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights.component.spec.ts deleted file mode 100644 index a8137435..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ProjectInsightsComponent} from './project-insights.component'; - -describe('ProjectInsightsComponent', () => { - let component: ProjectInsightsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ProjectInsightsComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ProjectInsightsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights.component.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights.component.ts deleted file mode 100644 index c3458ec2..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights.component.ts +++ /dev/null @@ -1,73 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, ViewChild} from '@angular/core'; -import { - ProjectInsightsMemberOverviewComponent -} from './project-members/project-insights-member-overview/project-insights-member-overview.component'; -import {ProjectOverviewComponent} from './project-overview/project-overview.component'; -import {TaskInsightsComponent} from './task-insights/task-insights.component'; - -enum IInsightModes { - 'overview', 'members', 'tasks' -} - -@Component({ - selector: 'worklenz-project-insights', - templateUrl: './project-insights.component.html', - styleUrls: ['./project-insights.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectInsightsComponent { - @ViewChild(ProjectOverviewComponent) projectOverviewComponent: ProjectOverviewComponent | undefined; - @ViewChild(TaskInsightsComponent) taskInsightsComponent: TaskInsightsComponent | undefined; - @ViewChild(ProjectInsightsMemberOverviewComponent) projectInsightsMemberOverviewComponent: ProjectInsightsMemberOverviewComponent | undefined; - - @Input() projectName: string | null = null; - private readonly includeArchivedTasks = "include-archived-tasks"; - - options = ['Overview', 'Members', 'Tasks']; - selectedMode: number = IInsightModes.overview; - modes = IInsightModes; - isLoading = false; - includeArchived = false; - - constructor( - private readonly cdr: ChangeDetectorRef, - ) { - this.checkArchivedChoice(); - } - - get archived() { - return localStorage.getItem(this.includeArchivedTasks) === 'true'; - } - - checkArchivedChoice() { - if (localStorage.getItem(this.includeArchivedTasks) === null) { - localStorage.setItem(this.includeArchivedTasks, 'false'); - } - this.includeArchived = this.archived; - this.cdr.markForCheck(); - } - - handleIndexChange(e: number): void { - this.selectedMode = e; - } - - exportPdf() { - if (this.selectedMode == 0) { - this.projectOverviewComponent?.exportOverview(this.projectName); - this.cdr.markForCheck(); - } - if (this.selectedMode == 1) { - this.projectInsightsMemberOverviewComponent?.exportMembersInsight(this.projectName); - this.cdr.markForCheck(); - } - if (this.selectedMode == 2) { - this.taskInsightsComponent?.exportTaskInsights(this.projectName); - this.cdr.markForCheck(); - } - } - - archivedChoiceChanged(event: any) { - localStorage.setItem(this.includeArchivedTasks, event); - this.cdr.markForCheck(); - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/last-updated-tasks/last-updated-tasks.component.html b/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/last-updated-tasks/last-updated-tasks.component.html deleted file mode 100644 index 01e54ecf..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/last-updated-tasks/last-updated-tasks.component.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - Name - Status - Due Date - Last Updated - - - - - {{data.name}} - - {{data.status}} - - - {{(data.end_date | date: 'mediumDate') || '-'}} - - {{formatEndDate(data.updated_at)}} - - - - - - - -
-
- -
- No tasks have been updated recently in the project. -
- -
diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/last-updated-tasks/last-updated-tasks.component.scss b/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/last-updated-tasks/last-updated-tasks.component.scss deleted file mode 100644 index 7f3a1f9e..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/last-updated-tasks/last-updated-tasks.component.scss +++ /dev/null @@ -1,5 +0,0 @@ - -.no-data-img-holder { - width: 64px; - margin-top: 42px; -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/last-updated-tasks/last-updated-tasks.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/last-updated-tasks/last-updated-tasks.component.spec.ts deleted file mode 100644 index 2babf8bd..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/last-updated-tasks/last-updated-tasks.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {LastUpdatedTasksComponent} from './last-updated-tasks.component'; - -describe('LastUpdatedTasksComponent', () => { - let component: LastUpdatedTasksComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [LastUpdatedTasksComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(LastUpdatedTasksComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/last-updated-tasks/last-updated-tasks.component.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/last-updated-tasks/last-updated-tasks.component.ts deleted file mode 100644 index fac4e04b..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/last-updated-tasks/last-updated-tasks.component.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Input, - OnChanges, - OnInit, - SimpleChanges -} from '@angular/core'; -import {ProjectInsightsService} from "@api/project-insights.service"; -import {ActivatedRoute} from "@angular/router"; -import {log_error} from "@shared/utils"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {formatDistance} from "date-fns"; -import {UtilsService} from "@services/utils.service"; -import {IInsightTasks} from "@interfaces/api-models/project-insights"; - -@Component({ - selector: 'worklenz-last-updated-tasks', - templateUrl: './last-updated-tasks.component.html', - styleUrls: ['./last-updated-tasks.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class LastUpdatedTasksComponent implements OnInit, OnChanges { - @Input() archived = false; - - private readonly includeArchivedTasks = "include-archived-tasks"; - - loadingStatuses = false; - loadingLastUpdatedTasks = false; - - showTaskDrawer = false; - - projectId: string = ''; - selectedTaskId: string | null = null; - - lastUpdatedTasks: IInsightTasks[] = []; - - constructor( - private api: ProjectInsightsService, - public utils: UtilsService, - private socket: Socket, - private route: ActivatedRoute, - private readonly cdr: ChangeDetectorRef, - ) { - this.projectId = this.route.snapshot.paramMap.get('id') || ''; - } - - ngOnInit() { - // this.getLastUpdatedTasks(); - } - - ngOnChanges(changes: SimpleChanges) { - this.getLastUpdatedTasks(); - } - - get archivedTasksChoice() { - return localStorage.getItem(this.includeArchivedTasks) === 'true'; - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_STATUS_CHANGE.toString()); - } - - async getLastUpdatedTasks() { - try { - this.loadingLastUpdatedTasks = true; - const res = await this.api.getLastUpdatedTasks(this.projectId, this.archivedTasksChoice); - if (res.done) { - this.lastUpdatedTasks = res.body; - } - this.loadingLastUpdatedTasks = false; - this.cdr.markForCheck(); - } catch (e) { - this.loadingLastUpdatedTasks = false; - log_error(e); - this.cdr.markForCheck(); - } - } - - onCreateOrUpdate() { - this.getLastUpdatedTasks(); - this.showTaskDrawer = false; - this.selectedTaskId = null; - this.cdr.markForCheck(); - } - - formatEndDate(updated_at: any) { - return formatDistance(new Date(updated_at), new Date(), {addSuffix: true}) - } - - trackById(index: number, item: any) { - return item.id; - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/priority-breakdown/priority-breakdown.component.html b/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/priority-breakdown/priority-breakdown.component.html deleted file mode 100644 index c0b2c58d..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/priority-breakdown/priority-breakdown.component.html +++ /dev/null @@ -1,14 +0,0 @@ - - - -
- - -
diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/priority-breakdown/priority-breakdown.component.scss b/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/priority-breakdown/priority-breakdown.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/priority-breakdown/priority-breakdown.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/priority-breakdown/priority-breakdown.component.spec.ts deleted file mode 100644 index 40d37058..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/priority-breakdown/priority-breakdown.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {PriorityBreakdownComponent} from './priority-breakdown.component'; - -describe('PriorityBreakdownComponent', () => { - let component: PriorityBreakdownComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [PriorityBreakdownComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(PriorityBreakdownComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/priority-breakdown/priority-breakdown.component.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/priority-breakdown/priority-breakdown.component.ts deleted file mode 100644 index 8bcee865..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/priority-breakdown/priority-breakdown.component.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - Input, - OnChanges, - SimpleChanges, - ViewChild -} from '@angular/core'; -import {ProjectInsightsService} from "@api/project-insights.service"; -import {ActivatedRoute} from "@angular/router"; -import {log_error} from "@shared/utils"; -import {BaseChartDirective} from "ng2-charts"; -import {ChartConfiguration} from "chart.js"; - -@Component({ - selector: 'worklenz-priority-breakdown', - templateUrl: './priority-breakdown.component.html', - styleUrls: ['./priority-breakdown.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class PriorityBreakdownComponent implements OnChanges { - @ViewChild(BaseChartDirective) barChart: BaseChartDirective | undefined; - @ViewChild('statusOverviewChart') statusOverviewChart!: ElementRef; - @Input() archived = false; - - private readonly includeArchivedTasks = "include-archived-tasks"; - - loading = false; - projectId = ''; - - priorityStats: any[] = []; - priorityColors: string[] = [] - options: any = {}; - - barChartPlugins = []; - - barChartData: ChartConfiguration<'bar'>['data'] = { - labels: [], - datasets: [{data: [], label: 'Tasks', backgroundColor: this.priorityColors}] - }; - - barChartOptions: ChartConfiguration<'bar'>['options'] = { - responsive: false, - plugins: { - datalabels: { - display: false - } - }, - scales: { - y: { - title: { - display: true, - text: 'Task Count', - align: "end", - font: { - family: 'Helvetica' - } - } - }, - x: { - title: { - display: true, - text: 'Priority', - align: "end", - font: { - family: 'Helvetica' - } - } - } - } - }; - - constructor( - private api: ProjectInsightsService, - private route: ActivatedRoute, - private readonly cdr: ChangeDetectorRef, - ) { - this.projectId = this.route.snapshot.paramMap.get('id') || ''; - } - - get chartWidth() { - const windowWidth = window.innerWidth; - if (windowWidth > 1400) { - return 580; - } else if (windowWidth < 1400 && windowWidth > 1200) { - return 460; - } - return 350; - } - - ngOnChanges(changes: SimpleChanges) { - this.getPriorityBreakdown(); - } - - get archivedTasksChoice() { - return localStorage.getItem(this.includeArchivedTasks) === 'true'; - } - - async getPriorityBreakdown() { - try { - this.loading = true; - this.barChartData.datasets[0].data = []; - this.barChartData.labels = []; - const res = await this.api.getPriorityOverview(this.projectId, this.archivedTasksChoice); - if (res.done) { - this.priorityStats = res.body; - this.loadChart(this.priorityStats); - } - this.loading = false; - this.cdr.markForCheck(); - } catch (e) { - this.loading = false; - log_error(e); - this.cdr.markForCheck(); - } - } - - async loadChart(seriesData: any) { - this.barChartData.datasets[0].data = []; - this.barChartData.labels = []; - - for (const item of seriesData) { - this.barChartData.labels?.push(item.name); - this.barChartData.datasets[0].data.push(item.data[0] || 0); - this.priorityColors.push(item.color); - } - this.barChart?.update(); - this.cdr.markForCheck(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/project-deadline/project-deadline.component.html b/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/project-deadline/project-deadline.component.html deleted file mode 100644 index 21aaf6eb..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/project-deadline/project-deadline.component.html +++ /dev/null @@ -1,101 +0,0 @@ - - - - Project Deadline - - ({{getProjectEndDate(deadlineStats.project_end_date)}}) - - - - - - - - - - -
- -
-
- -
Overdue tasks (hours) - - -
-

{{deadlineStats.deadline_logged_hours_string}}

-
-
-
-
- - - - - -
- -
-
- -
Overdue tasks - - -
-

{{deadlineStats.deadline_tasks_count || 0}}

-
-
-
-
-
- - - - - Name - Status - End Date - - - - - - {{data.name}} - - - {{data.status}} - - - {{(data.end_date | date: 'mediumDate') || '-'}} - - - - - -
- - - -
-
- -
-
All tasks completed on time.
-
- -
- - diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/project-deadline/project-deadline.component.scss b/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/project-deadline/project-deadline.component.scss deleted file mode 100644 index 50179ca8..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/project-deadline/project-deadline.component.scss +++ /dev/null @@ -1,20 +0,0 @@ -.fs-16 { - font-size: 16px; -} - -.fs-26 { - font-size: 26px; -} - -.fs-50 { - font-size: 50px; -} - -.icon-img-holder { - width: 90%; -} - -.no-data-img-holder { - width: 64px; - margin-top: 42px; -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/project-deadline/project-deadline.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/project-deadline/project-deadline.component.spec.ts deleted file mode 100644 index 503f92d5..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/project-deadline/project-deadline.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ProjectDeadlineComponent} from './project-deadline.component'; - -describe('ProjectDeadlineComponent', () => { - let component: ProjectDeadlineComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ProjectDeadlineComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ProjectDeadlineComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/project-deadline/project-deadline.component.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/project-deadline/project-deadline.component.ts deleted file mode 100644 index 299ab347..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/project-deadline/project-deadline.component.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Input, - OnChanges, - OnInit, - SimpleChanges, - ViewChild -} from '@angular/core'; -import {ProjectInsightsService} from "@api/project-insights.service"; -import {ActivatedRoute, Router} from "@angular/router"; -import {log_error} from "@shared/utils"; -import {IDeadlineTaskStats} from "@interfaces/api-models/project-insights"; -import {format} from "date-fns"; -import {Socket} from "ngx-socket-io"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {UtilsService} from "@services/utils.service"; -import {TaskStatusesApiService} from "@api/task-statuses-api.service"; -import {ProjectFormModalComponent} from "../../../../components/project-form-modal/project-form-modal.component"; - -@Component({ - selector: 'worklenz-project-deadline', - templateUrl: './project-deadline.component.html', - styleUrls: ['./project-deadline.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectDeadlineComponent implements OnInit, OnChanges { - @ViewChild(ProjectFormModalComponent) projectsForm!: ProjectFormModalComponent; - @Input() archived = false; - - private readonly includeArchivedTasks = "include-archived-tasks"; - - loading = false; - loadingStatuses = false; - showTaskDrawer = false; - - deadlineStats: IDeadlineTaskStats = {}; - taskStatuses: ITaskStatusViewModel[] = []; - - selectedTaskId: string | null = null; - - projectId: string = ''; - - constructor( - private socket: Socket, - public utils: UtilsService, - private api: ProjectInsightsService, - private statusesApi: TaskStatusesApiService, - private route: ActivatedRoute, - private router: Router, - private readonly cdr: ChangeDetectorRef, - ) { - this.projectId = this.route.snapshot.paramMap.get('id') || ''; - } - - ngOnInit() { - // void this.get(); - } - - ngOnChanges(changes: SimpleChanges) { - this.get(); - } - - get archivedTasksChoice() { - return localStorage.getItem(this.includeArchivedTasks) === 'true'; - } - - async get() { - try { - this.loading = true; - const res = await this.api.getProjectDeadlineStats(this.projectId, this.archivedTasksChoice); - if (res.done) { - this.deadlineStats = res.body; - } - this.loading = false; - this.cdr.markForCheck(); - } catch (e) { - this.loading = false; - log_error(e); - this.cdr.markForCheck(); - } - } - - trackById(index: number, item: any) { - return item.id; - } - - getProjectEndDate(project_end_date: string = '') { - return project_end_date ? format(new Date(project_end_date), 'yyyy-MM-dd') : '' - } - - back() { - this.router.navigate(['/worklenz/projects']); - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/status-overview/status-overview.component.html b/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/status-overview/status-overview.component.html deleted file mode 100644 index ac04e43c..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/status-overview/status-overview.component.html +++ /dev/null @@ -1,35 +0,0 @@ - - - -
-
- - -
-
-
    -
  • - -

    {{item.name | ellipsis : 20}} ({{item.y}})

    -
  • -
-
-
- - - -
-
- -
- No tasks to show. -
-
diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/status-overview/status-overview.component.scss b/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/status-overview/status-overview.component.scss deleted file mode 100644 index 2fee8fe0..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/status-overview/status-overview.component.scss +++ /dev/null @@ -1,7 +0,0 @@ -.chart-details { - max-height: 300px; - overflow-y: auto; -} -.no-data-img-holder { - width: 65px; -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/status-overview/status-overview.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/status-overview/status-overview.component.spec.ts deleted file mode 100644 index df8c1bfa..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/status-overview/status-overview.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {StatusOverviewComponent} from './status-overview.component'; - -describe('StatusOverviewComponent', () => { - let component: StatusOverviewComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [StatusOverviewComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(StatusOverviewComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/status-overview/status-overview.component.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/status-overview/status-overview.component.ts deleted file mode 100644 index 37684954..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-insights/status-overview/status-overview.component.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { - ChangeDetectionStrategy, ChangeDetectorRef, - Component, - Input, - OnChanges, - SimpleChanges, - ViewChild -} from '@angular/core'; -import {ProjectInsightsService} from "@api/project-insights.service"; -import {log_error} from "@shared/utils"; -import {ActivatedRoute} from "@angular/router"; -import {BaseChartDirective} from "ng2-charts"; -import {ChartConfiguration} from "chart.js"; - -declare let require: any; - -@Component({ - selector: 'worklenz-status-overview', - templateUrl: './status-overview.component.html', - styleUrls: ['./status-overview.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class StatusOverviewComponent implements OnChanges { - @Input() archived = false; - - @ViewChild(BaseChartDirective) chart: BaseChartDirective | undefined; - private readonly includeArchivedTasks = "include-archived-tasks"; - - loading = false; - isChartEmpty = false; - - projectId = ''; - statusCounts: any = []; - options: any = {}; - statusColors: string[] = []; - - chartPlugins = []; - - chartData: ChartConfiguration<'doughnut'>['data'] = { - labels: [], - datasets: [{ - label: 'Tasks', - data: [], - backgroundColor: this.statusColors, - hoverOffset: 2 - }] - }; - - chartOptions: ChartConfiguration<'doughnut'>['options'] = { - plugins: { - datalabels: { - display: false - } - }, - responsive: false, - } - - constructor( - private api: ProjectInsightsService, - private route: ActivatedRoute, - private readonly cdr: ChangeDetectorRef, - ) { - this.projectId = this.route.snapshot.paramMap.get('id') || ''; - } - - get chartWidth() { - const windowWidth = window.innerWidth; - if (windowWidth > 1400) { - return 350; - } else if (windowWidth < 1400 && windowWidth > 1200) { - return 275; - } - return 220; - } - - ngOnChanges(changes: SimpleChanges) { - this.getStatusCounts() - } - - get archivedTasksChoice() { - return localStorage.getItem(this.includeArchivedTasks) === 'true'; - } - - async getStatusCounts() { - try { - this.loading = true; - this.chartData.datasets[0].data = []; - this.chartData.labels = []; - const res = await this.api.getTaskStatusCounts(this.projectId, this.archivedTasksChoice); - if (res.done) { - this.statusCounts = res.body; - await this.loadChart(this.statusCounts); - } - this.loading = false; - this.cdr.markForCheck(); - } catch (e) { - this.loading = false; - log_error(e); - this.cdr.markForCheck(); - } - } - - async loadChart(seriesData: any) { - this.chartData.datasets[0].data = []; - this.chartData.labels = []; - - for (const item of seriesData) { - this.chartData.labels?.push(item.name); - this.chartData.datasets[0].data.push(item.y || 0); - this.statusColors.push(item.color); - } - this.chart?.update(); - - if (this.chartData.datasets[0].data.every(value => value === 0)) - this.isChartEmpty = true; - - this.cdr.markForCheck(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-stats/member-stats.component.html b/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-stats/member-stats.component.html deleted file mode 100644 index 41cfc09d..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-stats/member-stats.component.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - -
- -
-
- -
Project Members
-

{{memberStats.total_members_count}}

-
-
-
-
- - - - - -
- -
- -
- -
Assignees with overdue tasks
-

{{memberStats.overdue_members}}

-
-
-
-
- - - - - -
- -
-
- -
Unassigned Members
-

{{memberStats.unassigned_members}}

-
-
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-stats/member-stats.component.scss b/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-stats/member-stats.component.scss deleted file mode 100644 index 40e8e4e1..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-stats/member-stats.component.scss +++ /dev/null @@ -1,20 +0,0 @@ -.fs-16 { - font-size: 16px; -} - -.fs-26 { - font-size: 36px; -} - -.fs-50 { - font-size: 50px; -} - -nz-card:hover { - border-color: #d3d3d3; - box-shadow: 0 0 0 1px #f1f1f1, 0 2px 10px 0 rgba(109, 110, 111, 0.08); -} - -.icon-img-holder { - width: 70%; -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-stats/member-stats.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-stats/member-stats.component.spec.ts deleted file mode 100644 index f6017e04..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-stats/member-stats.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {MemberStatsComponent} from './member-stats.component'; - -describe('MemberStatsComponent', () => { - let component: MemberStatsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [MemberStatsComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(MemberStatsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-stats/member-stats.component.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-stats/member-stats.component.ts deleted file mode 100644 index e8cb84c0..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-stats/member-stats.component.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Input, - OnChanges, - OnInit, - SimpleChanges -} from '@angular/core'; -import {ProjectInsightsService} from "@api/project-insights.service"; -import {IProjectMemberStats} from "@interfaces/api-models/project-insights"; -import {ActivatedRoute} from "@angular/router"; - -@Component({ - selector: 'worklenz-member-stats', - templateUrl: './member-stats.component.html', - styleUrls: ['./member-stats.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class MemberStatsComponent implements OnInit, OnChanges { - @Input() archived = false; - - private readonly includeArchivedTasks = "include-archived-tasks"; - - memberStats: IProjectMemberStats = {}; - projectId: string = ''; - - loading = true; - - constructor( - private api: ProjectInsightsService, - private route: ActivatedRoute, - private readonly cdr: ChangeDetectorRef - ) { - this.projectId = this.route.snapshot.paramMap.get('id') || ''; - } - - ngOnInit() { - // void this.getProjectMemberInsights(); - } - - ngOnChanges(changes: SimpleChanges) { - this.getProjectMemberInsights(); - } - - get archivedTasksChoice() { - return localStorage.getItem(this.includeArchivedTasks) === 'true'; - } - - async getProjectMemberInsights() { - try { - this.loading = true; - const res = await this.api.getMemberInsightAStats(this.projectId, this.archivedTasksChoice); - if (res.done) { - this.memberStats = res.body; - } - this.loading = false; - this.cdr.markForCheck(); - } catch (e) { - this.loading = false; - this.cdr.markForCheck(); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-tasks/member-tasks.component.html b/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-tasks/member-tasks.component.html deleted file mode 100644 index 7c24c3db..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-tasks/member-tasks.component.html +++ /dev/null @@ -1,104 +0,0 @@ - -
-
- -
-
-
-
-
Name
-
Task Count
-
Contribution
-
Completed
-
Incomplete
-
Overdue
-
Progress
-
- -
-
- -
- The project does not have any members yet. -
- -
-
-
- - - - -
-
-
- - -
-
{{member.name}}
-
{{member.task_count || 0}}
-
- -
-
{{member.done_task_count || 0}}
-
{{member.pending_task_count || 0}}
-
{{member.overdue_task_count || 0}}
-
- -
- - -
- -
-
-
-
-
Name
-
Status
-
Due Date
-
Days Overdue
-
Completed Date
-
Total Allocation
-
Over Logged Time
-
-
-
- - -
-
- - {{task.name}} - -
-
- {{task.status}} -
-
- {{task.end_date | date: 'MMM dd, yyyy'}} -
-
{{task.days_overdue}}
-
{{(task.completed_at | date: 'MMM dd, yyyy') || 'N/A'}}
-
{{task.total_minutes}}
-
{{task.overlogged_time}}
-
-
-
-
-
-
-
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-tasks/member-tasks.component.scss b/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-tasks/member-tasks.component.scss deleted file mode 100644 index 2784a636..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-tasks/member-tasks.component.scss +++ /dev/null @@ -1,136 +0,0 @@ -.table { - //font-size: 12px; -} - -.table, .td { - text-align: left; -} - -.table-header { - font-weight: 500; -} - -.table-row { - display: block; - white-space: nowrap; - overflow: hidden; - border-bottom: #e3e3e3 1px solid; -} - -.table-row:last-child { - border: none -} - -.td { - display: inline-block; - padding: 12px 10px; -} - -.td:nth-child(1) { - width: calc(1vw); -} - -.td:nth-child(2) { - width: calc(1vw); -} - -.td:nth-child(3) { - width: calc(11vw); -} - -.td:nth-child(4) { - width: calc(9vw); - text-align: center; -} - -.td:nth-child(5) { - width: 17%; - text-align: center; -} - -.td:nth-child(6) { - width: 9%; - text-align: center; -} - -.td:nth-child(7) { - width: 9%; - text-align: center; -} - -.td:nth-child(8) { - width: 9%; - text-align: center; -} - -.td:nth-child(9) { - width: 19%; - text-align: center; -} - -.clickable { - cursor: pointer; -} - -//.clickable:hover { -// background-color: #e7e7e7; -//} - -.child-row { - padding: 5px 50px 20px; -} - -.overdue-tasks-present { - color: #ff4400; -} - -nz-card:hover { - border-color: #d3d3d3; - box-shadow: 0 0 0 1px #f1f1f1, 0 2px 10px 0 rgba(109, 110, 111, 0.08); -} - -.child-td { - display: inline-block; - padding: 6px 10px; -} - -.child-td:nth-child(1) { - width: 3%; -} - -.child-td:nth-child(2) { - width: 25%; -} - -.child-td:nth-child(3) { - width: 10%; -} - -.child-td:nth-child(4) { - width: 12%; -} - -.child-td:nth-child(5) { - width: 8%; -} - -.child-td:nth-child(6) { - width: 15%; -} - -.child-td:nth-child(7) { - width: 10%; -} - -.child-td:nth-child(8) { - width: 13%; -} - -.expanded-panel { - background-color: #f0f2f5; -} - -.no-data-img-holder { - width: 100px; - margin-top: 42px; -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-tasks/member-tasks.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-tasks/member-tasks.component.spec.ts deleted file mode 100644 index 6b1ebfe6..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-tasks/member-tasks.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {MemberTasksComponent} from './member-tasks.component'; - -describe('MemberTasksComponent', () => { - let component: MemberTasksComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [MemberTasksComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(MemberTasksComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-tasks/member-tasks.component.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-tasks/member-tasks.component.ts deleted file mode 100644 index a7be4457..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/member-tasks/member-tasks.component.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - HostListener, - Input, - OnInit, - SimpleChanges -} from '@angular/core'; -import {ITeamMemberOverviewGetResponse} from "@interfaces/api-models/team-members-get-response"; -import {IProjectsOverviewGetResponse} from "@interfaces/api-models/projects-get-response"; -import {ActivatedRoute} from "@angular/router"; -import {ProjectsApiService} from "@api/projects-api.service"; -import {EventTaskCreatedOrUpdate} from "@shared/events"; -import {animate, state, style, transition, trigger} from "@angular/animations"; -import {ProjectInsightsService} from "@api/project-insights.service"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {Socket} from "ngx-socket-io"; -import {UtilsService} from "@services/utils.service"; - -@Component({ - selector: 'worklenz-member-tasks', - templateUrl: './member-tasks.component.html', - styleUrls: ['./member-tasks.component.scss'], - animations: [ - trigger('detailExpand', [ - state('collapsed', style({height: '0px', minHeight: '0'})), - state('expanded', style({height: '*'})), - transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')), - ]), - ], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class MemberTasksComponent implements OnInit { - @Input() archived = false; - - private readonly includeArchivedTasks = "include-archived-tasks"; - - projectMembers: ITeamMemberOverviewGetResponse[] = []; - - projectId: string = ''; - projectOverview: IProjectsOverviewGetResponse = {}; - taskStatuses: ITaskStatusViewModel[] = []; - - completed = 0; - pending = 0; - - loading = true; - loadingTasks = true; - loadingStatuses = false; - - expanded: { [key: string]: boolean } = {}; - - constructor( - private route: ActivatedRoute, - private socket: Socket, - public utils: UtilsService, - private projectsApiService: ProjectsApiService, - private projectInsightsService: ProjectInsightsService, - private readonly cdr: ChangeDetectorRef - ) { - this.projectId = this.route.snapshot.paramMap.get('id') || ''; - } - - ngOnInit(): void { - // this.init(); - } - - ngOnChanges(changes: SimpleChanges) { - this.init(); - this.expanded = {}; - } - - get archivedTasksChoice() { - return localStorage.getItem(this.includeArchivedTasks) === 'true'; - } - - @HostListener(`document:${EventTaskCreatedOrUpdate}`) - init() { - this.getProjectOverview(); - this.getProjectOverviewMembers(); - } - - async getProjectOverview() { - try { - this.loading = true; - const res = await this.projectsApiService.getOverViewById(this.projectId); - if (res.done) { - this.projectOverview = res.body; - this.completed = this.projectOverview.done_task_count ? this.projectOverview.done_task_count : 0; - this.pending = this.projectOverview.pending_task_count ? this.projectOverview.pending_task_count : 0; - } - this.loading = false; - this.cdr.markForCheck(); - } catch (e) { - this.loading = false; - this.cdr.markForCheck(); - } - } - - async getProjectOverviewMembers() { - try { - this.loading = true; - const res = await this.projectsApiService.getOverViewMembersById(this.projectId, this.archivedTasksChoice); - if (res.done) { - this.projectMembers = res.body; - } - this.loading = false; - this.cdr.markForCheck(); - } catch (e) { - this.loading = false; - this.cdr.markForCheck(); - } - } - - async getTasksByMember(member: ITeamMemberOverviewGetResponse) { - try { - this.loadingTasks = true; - const res = await this.projectInsightsService.getMemberTasks({ - member_id: member.id, - project_id: this.projectId, - archived: this.archivedTasksChoice - }); - if (res.done) { - member.tasks = res.body; - } - this.loadingTasks = false; - this.cdr.markForCheck(); - } catch (e) { - this.loadingTasks = false; - this.cdr.markForCheck(); - } - } - - isRowClickable(rowData: ITeamMemberOverviewGetResponse): boolean { - if (!rowData.task_count) return false; - return rowData.task_count > 0 - } - - overdueTasksPresent(rowData: ITeamMemberOverviewGetResponse): boolean { - if (!rowData.overdue_task_count) return false; - return rowData.overdue_task_count > 0 - } - - rowClicked(member: ITeamMemberOverviewGetResponse) { - if (!member.task_count) return; - if (!this.expanded[member.id]) this.getTasksByMember(member); - this.expanded[member.id] = !this.expanded[member.id]; - this.cdr.markForCheck(); - } - - trackById(index: number, item: any) { - return item.id; - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/project-insights-member-overview/project-insights-member-overview.component.html b/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/project-insights-member-overview/project-insights-member-overview.component.html deleted file mode 100644 index 7fbec50d..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/project-insights-member-overview/project-insights-member-overview.component.html +++ /dev/null @@ -1,4 +0,0 @@ -
- - -
diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/project-insights-member-overview/project-insights-member-overview.component.scss b/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/project-insights-member-overview/project-insights-member-overview.component.scss deleted file mode 100644 index 4de95017..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/project-insights-member-overview/project-insights-member-overview.component.scss +++ /dev/null @@ -1,4 +0,0 @@ -nz-card:hover { - border-color: #d3d3d3; - box-shadow: 0 0 0 1px #f1f1f1, 0 2px 10px 0 rgba(109, 110, 111, 0.08); -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/project-insights-member-overview/project-insights-member-overview.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/project-insights-member-overview/project-insights-member-overview.component.spec.ts deleted file mode 100644 index 49f029e7..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/project-insights-member-overview/project-insights-member-overview.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ProjectInsightsMemberOverviewComponent} from './project-insights-member-overview.component'; - -describe('OverviewComponent', () => { - let component: ProjectInsightsMemberOverviewComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ProjectInsightsMemberOverviewComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ProjectInsightsMemberOverviewComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/project-insights-member-overview/project-insights-member-overview.component.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/project-insights-member-overview/project-insights-member-overview.component.ts deleted file mode 100644 index ffab28f6..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-members/project-insights-member-overview/project-insights-member-overview.component.ts +++ /dev/null @@ -1,59 +0,0 @@ -import {Component, ElementRef, Input, ViewChild} from '@angular/core'; -import html2canvas from 'html2canvas'; -import jsPDF from 'jspdf'; -import {formatDate} from '@angular/common'; -import {ProjectInsightsComponent} from '../../project-insights.component'; - -@Component({ - selector: 'worklenz-project-insights-member-overview', - templateUrl: './project-insights-member-overview.component.html', - styleUrls: ['./project-insights-member-overview.component.scss'] -}) -export class ProjectInsightsMemberOverviewComponent { - @ViewChild('membersInsights') membersInsights: ElementRef | undefined; - @Input() archived = false; - - private readonly includeArchivedTasks = "include-archived-tasks"; - - constructor( - private projectInsightsComponent: ProjectInsightsComponent, - ) { - } - - exportMembersInsight(projectName: string | null) { - - if (this.membersInsights) { - - this.projectInsightsComponent.isLoading = true; - - html2canvas(this.membersInsights.nativeElement).then((canvas) => { - - let img = canvas.toDataURL("image/PNG"); - let doc = new jsPDF('p', 'mm', 'a4', true); - const bufferX = 5; - const bufferY = 28; - const imgProps = (doc).getImageProperties(img); - const pdfWidth = doc.internal.pageSize.getWidth() - 2 * bufferX; - const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width; - - let LogoImg = new Image(); - LogoImg.src = location.origin + '/assets/images/logo.png'; - doc.addImage(LogoImg, 'PNG', (doc.internal.pageSize.getWidth() / 2) - 12, 5, 30, 6.5); - doc.setFontSize(14); - doc.setTextColor(0, 0, 0, 0.85); - doc.text([`Insights - ` + projectName + ` - Members`, `${formatDate(new Date(), 'yyyy-MM-dd', 'en')}`], 105, 17, { - maxWidth: pdfWidth, - align: 'center' - }); - doc.addImage(img, 'PNG', bufferX, bufferY, pdfWidth, pdfHeight); - return doc; - - }).then((doc) => { - - doc.save('Members Insights ' + formatDate(new Date(), 'yyyy-MM-dd', 'en') + '.pdf'); - this.projectInsightsComponent.isLoading = false; - - }); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-overview/project-overview.component.html b/worklenz-frontend/src/app/administrator/projects/project-insights/project-overview/project-overview.component.html deleted file mode 100644 index 8bf914b0..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-overview/project-overview.component.html +++ /dev/null @@ -1,31 +0,0 @@ -
- - - - - - - - - - - - - - - - - - - - - See all - - - - - - - - -
diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-overview/project-overview.component.scss b/worklenz-frontend/src/app/administrator/projects/project-insights/project-overview/project-overview.component.scss deleted file mode 100644 index 4de95017..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-overview/project-overview.component.scss +++ /dev/null @@ -1,4 +0,0 @@ -nz-card:hover { - border-color: #d3d3d3; - box-shadow: 0 0 0 1px #f1f1f1, 0 2px 10px 0 rgba(109, 110, 111, 0.08); -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-overview/project-overview.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/project-overview/project-overview.component.spec.ts deleted file mode 100644 index 197e052b..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-overview/project-overview.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ProjectOverviewComponent} from './project-overview.component'; - -describe('ProjectOverviewComponent', () => { - let component: ProjectOverviewComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ProjectOverviewComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ProjectOverviewComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/project-overview/project-overview.component.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/project-overview/project-overview.component.ts deleted file mode 100644 index 6dae4eb5..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/project-overview/project-overview.component.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - Input, - OnInit, - ViewChild -} from '@angular/core'; -import {ProjectInsightsService} from "@api/project-insights.service"; -import {ActivatedRoute, Router} from "@angular/router"; -import {Socket} from "ngx-socket-io"; -import {formatDistance} from "date-fns"; - -import {IProjectLogs} from "@interfaces/api-models/project-insights"; -import {log_error} from "@shared/utils"; -import {UtilsService} from "@services/utils.service"; -import {TaskPrioritiesService} from "@api/task-priorities.service"; -import {TaskStatusesApiService} from "@api/task-statuses-api.service"; -import {ITaskStatusCategory} from "@interfaces/task-status-category"; -import jsPDF from 'jspdf'; -import html2canvas from 'html2canvas'; -import {formatDate} from '@angular/common'; -import {ProjectInsightsComponent} from '../project-insights.component'; - -@Component({ - selector: 'worklenz-project-overview', - templateUrl: './project-overview.component.html', - styleUrls: ['./project-overview.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectOverviewComponent implements OnInit { - @ViewChild('overviewExportDiv') overviewExportDiv: ElementRef | undefined; - @Input() archived = false; - - private readonly includeArchivedTasks = "include-archived-tasks"; - - loading = false; - loadingProjectLogs = false; - loadingCategories = false; - - projectId: string = ''; - - projectLogs: IProjectLogs[] = []; - categories: ITaskStatusCategory[] = []; - - toDoColorCode: string = ''; - pendingColorCode: string = ''; - completedColorCode: string = ''; - - constructor( - private api: ProjectInsightsService, - private route: ActivatedRoute, - private router: Router, - private socket: Socket, - public utils: UtilsService, - private prioritiesApi: TaskPrioritiesService, - private statusesApi: TaskStatusesApiService, - private projectInsightsComponent: ProjectInsightsComponent, - private readonly cdr: ChangeDetectorRef - ) { - this.projectId = this.route.snapshot.paramMap.get('id') || ''; - } - - ngOnInit() { - this.getCategories(); - this.getProjectLogs(); - } - - async getCategories() { - try { - this.loadingCategories = true; - const res = await this.statusesApi.getCategories(); - if (res.done) { - this.categories = res.body; - this.toDoColorCode = this.categories.find(e => e.name === 'To do')?.color_code || ''; - this.pendingColorCode = this.categories.find(e => e.name === 'Doing')?.color_code || ''; - this.completedColorCode = this.categories.find(e => e.name === 'Done')?.color_code || ''; - } - this.loadingCategories = false; - this.cdr.markForCheck(); - } catch (e) { - log_error(e); - this.loadingCategories = false; - this.cdr.markForCheck(); - } - } - - async getProjectLogs() { - try { - this.loadingProjectLogs = true; - const res = await this.api.getProjectLogs(this.projectId); - if (res.done) { - this.projectLogs = res.body; - } - this.loadingProjectLogs = false; - this.cdr.markForCheck(); - } catch (e) { - this.loadingProjectLogs = false; - log_error(e); - this.cdr.markForCheck(); - } - } - - formatEndDate(updated_at: any) { - return formatDistance(new Date(updated_at), new Date(), {addSuffix: true}) - } - - goToList() { - this.router.navigate([], { - relativeTo: this.route, queryParams: {tab: "tasks-list"}, queryParamsHandling: 'merge', // remove to replace all query params by provided - }); - } - - exportOverview(projectName: string | null) { - if (this.overviewExportDiv) { - - this.projectInsightsComponent.isLoading = true; - - html2canvas(this.overviewExportDiv.nativeElement).then((canvas) => { - - let img = canvas.toDataURL("image/PNG"); - let doc = new jsPDF('p', 'mm', 'a4', true); - const bufferX = 5; - const bufferY = 28; - const imgProps = (doc).getImageProperties(img); - const pdfWidth = doc.internal.pageSize.getWidth() - 2 * bufferX; - const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width; - - let LogoImg = new Image(); - LogoImg.src = location.origin + '/assets/images/logo.png'; - doc.addImage(LogoImg, 'PNG', (doc.internal.pageSize.getWidth() / 2) - 12, 5, 30, 6.5); - doc.setFontSize(14); - doc.setTextColor(0, 0, 0, 0.85); - doc.text([`Insights - ` + projectName + ` - Overview`, `${formatDate(new Date(), 'yyyy-MM-dd', 'en')}`], 105, 17, { - maxWidth: pdfWidth, - align: 'center' - }); - doc.addImage(img, 'PNG', bufferX, bufferY, pdfWidth, pdfHeight); - return doc; - - }).then((doc) => { - doc.save('Overview ' + formatDate(new Date(), 'yyyy-MM-dd', 'en') + '.pdf'); - this.projectInsightsComponent.isLoading = false; - }); - - this.cdr.markForCheck(); - - } - - } - -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/task-insights/task-insights.component.html b/worklenz-frontend/src/app/administrator/projects/project-insights/task-insights/task-insights.component.html deleted file mode 100644 index bd5ba24b..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/task-insights/task-insights.component.html +++ /dev/null @@ -1,228 +0,0 @@ -
- - - - - - - - Overdue Tasks - - - - - See all - - - - - - Name - Status - End Date - Days overdue - - - - - - {{data.name}} - - - {{data.status_name}} - - - {{(data.end_date | date: 'mediumDate') || '-'}} - - {{data.days_overdue}} - - - - - - - - - - Over logged Tasks - - - - - See all - - - - - - Name - Status - Members - Over Logged Time - - - - - - {{task.name}} - - - {{task.status_name}} - - -
- - - - -
- -
    -
  • - -
  • -
-
- - {{task.overlogged_time_string}} - - -
-
-
-
- - - - - - See all - - - - - - Name - Status - End Date - Completed At - - - - - - {{data.name}} - - - {{data.status_name}} - - - {{(data.end_date | date: 'mediumDate') || '-'}} - - {{data.completed_at | date: 'MMM d, y'}} - - - - - - - - - - See all - - - - - - Name - Status - End Date - Completed At - - - - - - {{data.name}} - - - {{data.status_name}} - - - {{(data.end_date | date: 'mediumDate') || '-'}} - - {{data.completed_at | date: 'MMM d, y'}} - - - - - - -
- - -
-
- -
-
All tasks completed on time.
-
-
- - -
-
- -
-
All tasks completed on time.
-
-
- - -
-
- -
- No tasks completed early in the project. -
-
- - -
-
- -
- All tasks completed on time. -
-
diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/task-insights/task-insights.component.scss b/worklenz-frontend/src/app/administrator/projects/project-insights/task-insights/task-insights.component.scss deleted file mode 100644 index ece20be3..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/task-insights/task-insights.component.scss +++ /dev/null @@ -1,9 +0,0 @@ -nz-card:hover { - border-color: #d3d3d3; - box-shadow: 0 0 0 1px #f1f1f1, 0 2px 10px 0 rgba(109, 110, 111, 0.08); -} - -.no-data-img-holder { - width: 64px; - margin-top: 42px; -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/task-insights/task-insights.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/task-insights/task-insights.component.spec.ts deleted file mode 100644 index 503a42d9..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/task-insights/task-insights.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskInsightsComponent} from './task-insights.component'; - -describe('TaskInsightsComponent', () => { - let component: TaskInsightsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskInsightsComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskInsightsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/project-insights/task-insights/task-insights.component.ts b/worklenz-frontend/src/app/administrator/projects/project-insights/task-insights/task-insights.component.ts deleted file mode 100644 index 7c440a41..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-insights/task-insights/task-insights.component.ts +++ /dev/null @@ -1,232 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - Input, - OnChanges, - SimpleChanges, - ViewChild -} from '@angular/core'; -import {ActivatedRoute, Router} from "@angular/router"; -import {ProjectInsightsService} from "@api/project-insights.service"; -import {IProjectInsightsGetRequest} from "@interfaces/api-models/project-insights"; -import {log_error} from "@shared/utils"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {TaskStatusesApiService} from "@api/task-statuses-api.service"; -import {UtilsService} from "@services/utils.service"; -import {Socket} from "ngx-socket-io"; -import html2canvas from 'html2canvas'; -import jsPDF from 'jspdf'; -import {formatDate} from '@angular/common'; -import {ProjectInsightsComponent} from '../project-insights.component'; -import {ITeamMemberViewModel} from "@interfaces/api-models/team-members-get-response"; -import {TeamMembersApiService} from "@api/team-members-api.service"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-task-insights', - templateUrl: './task-insights.component.html', - styleUrls: ['./task-insights.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskInsightsComponent implements OnChanges { - @Input() archived = false; - @ViewChild('projectOverviewChart') projectOverviewChart!: ElementRef; - @ViewChild('tasksInsightsExportDiv') tasksInsightsExportDiv: ElementRef | undefined; - @ViewChild('memberSearchInput', {static: false}) memberSearchInput!: ElementRef; - - private readonly includeArchivedTasks = "include-archived-tasks"; - - private readonly highlight = 'highlight-col'; - - projectId: string = ''; - - overviewData: IProjectInsightsGetRequest = {}; - taskStatuses: ITaskStatusViewModel[] = []; - members: ITeamMemberViewModel[] = []; - data: IProjectTask = {}; - - overdueTasks: any = []; - overloggedTasks: any = []; - earlyTasks: any = []; - lateTasks: any = []; - - loadingOverdue = true; - loadingOverlogged = true; - loadingEarlyTasks = true; - loadingLateTasks = true; - loadingOverviewData = false; - loadingStatuses = false; - - completed = 0; - pending = 0; - - showTaskDrawer = false; - - selectedTaskId: string | null = null; - memberSearchText: string | null = null; - - constructor( - private route: ActivatedRoute, - private auth: AuthService, - private router: Router, - private api: ProjectInsightsService, - private statusesApi: TaskStatusesApiService, - private teamMembersApi: TeamMembersApiService, - public utils: UtilsService, - private socket: Socket, - private projectInsightsComponent: ProjectInsightsComponent, - private readonly cdr: ChangeDetectorRef - ) { - this.projectId = this.route.snapshot.paramMap.get('id') || ''; - } - - ngOnInit(): void { - // this.getData(); - } - - ngOnChanges(changes: SimpleChanges) { - this.getData(); - } - - getData() { - this.getOverdueTasks(); - this.getEarlyTasks(); - this.getLateTasks(); - this.getProjectOverviewData(); - this.getOverloggedTasks(); - } - - get archivedTasksChoice() { - return localStorage.getItem(this.includeArchivedTasks) === 'true'; - } - - async getOverdueTasks() { - try { - this.loadingOverdue = true; - const res = await this.api.getOverdueTasks(this.projectId, this.archivedTasksChoice); - if (res.done) { - this.overdueTasks = res.body; - } - this.loadingOverdue = false; - this.cdr.markForCheck(); - } catch (e) { - this.loadingOverdue = false; - log_error(e); - this.cdr.markForCheck(); - } - } - - async getEarlyTasks() { - try { - this.loadingEarlyTasks = true; - const res = await this.api.getTasksCompletedEarly(this.projectId, this.archivedTasksChoice); - if (res.done) { - this.earlyTasks = res.body; - } - this.loadingEarlyTasks = false; - this.cdr.markForCheck(); - } catch (e) { - this.loadingEarlyTasks = false; - log_error(e); - this.cdr.markForCheck(); - } - } - - async getLateTasks() { - try { - this.loadingLateTasks = true; - const res = await this.api.getTasksCompletedLate(this.projectId, this.archivedTasksChoice); - if (res.done) { - this.lateTasks = res.body; - } - this.loadingLateTasks = false; - this.cdr.markForCheck(); - } catch (e) { - this.loadingLateTasks = false; - log_error(e); - this.cdr.markForCheck(); - } - } - - async getProjectOverviewData() { - try { - this.loadingOverviewData = true; - const res = await this.api.getProjectOverviewData(this.projectId, this.archivedTasksChoice); - if (res.done) { - this.overviewData = res.body; - } - this.loadingOverviewData = false; - this.cdr.markForCheck(); - } catch (e) { - this.loadingOverviewData = false; - log_error(e); - this.cdr.markForCheck(); - } - } - - async getOverloggedTasks() { - try { - this.loadingOverlogged = true; - const res = await this.api.getOverloggedTasks(this.projectId, this.archivedTasksChoice); - if (res.done) { - this.overloggedTasks = res.body; - } - this.loadingOverlogged = false; - this.cdr.markForCheck(); - } catch (e) { - this.loadingOverlogged = false; - log_error(e); - this.cdr.markForCheck(); - } - } - - onCreateOrUpdate() { - this.showTaskDrawer = false; - this.selectedTaskId = null; - this.cdr.markForCheck(); - } - - trackById(index: number, item: any) { - return item.id; - } - - goToList() { - this.router.navigate([], { - relativeTo: this.route, queryParams: {tab: "tasks-list"}, queryParamsHandling: 'merge', // remove to replace all query params by provided - }); - } - - exportTaskInsights(projectName: string | null) { - if (this.tasksInsightsExportDiv) { - this.projectInsightsComponent.isLoading = true; - html2canvas(this.tasksInsightsExportDiv.nativeElement).then((canvas) => { - let img = canvas.toDataURL("image/PNG"); - let doc = new jsPDF('p', 'mm', 'a4', true); - const bufferX = 5; - const bufferY = 28; - const imgProps = (doc).getImageProperties(img); - const pdfWidth = doc.internal.pageSize.getWidth() - 2 * bufferX; - const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width; - - let LogoImg = new Image(); - LogoImg.src = location.origin + '/assets/images/logo.png'; - doc.addImage(LogoImg, 'PNG', (doc.internal.pageSize.getWidth() / 2) - 12, 5, 30, 6.5); - doc.setFontSize(14); - doc.setTextColor(0, 0, 0, 0.85); - doc.text([`Insights - ` + projectName + ` - Tasks`, `${formatDate(new Date(), 'yyyy-MM-dd', 'en')}`], 105, 17, { - maxWidth: pdfWidth, - align: 'center' - }); - doc.addImage(img, 'PNG', bufferX, bufferY, pdfWidth, pdfHeight); - return doc; - - }).then((doc) => { - doc.save('Tasks Insights ' + formatDate(new Date(), 'yyyy-MM-dd', 'en') + '.pdf'); - this.projectInsightsComponent.isLoading = false; - }); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-members/project-members.component.html b/worklenz-frontend/src/app/administrator/projects/project-members/project-members.component.html deleted file mode 100644 index 6a98626c..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-members/project-members.component.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - Name - Job Title - Email - Tasks - Tasks Progress - Access - - - - - - - {{data.name}} - - {{data.job_title || "-"}} - - {{data.email}} - - - - {{data.email}} (Pending Invitation) - - - - {{ data.completed_tasks_count || 0 }}/{{data.all_tasks_count || 0}} - - - - - {{ data.access || "-" }} - - -
- - - -
- - - -
-
-
- - - - - - - - - - - - - - - - - -
-
- -
- No members found in the project. -
-
- - - diff --git a/worklenz-frontend/src/app/administrator/projects/project-members/project-members.component.scss b/worklenz-frontend/src/app/administrator/projects/project-members/project-members.component.scss deleted file mode 100644 index e602fd99..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-members/project-members.component.scss +++ /dev/null @@ -1,4 +0,0 @@ -.no-data-img-holder { - width: 100px; - margin-top: 42px; -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-members/project-members.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/project-members/project-members.component.spec.ts deleted file mode 100644 index efc2654c..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-members/project-members.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ProjectMembersComponent} from './project-members.component'; - -describe('ProjectMembersComponent', () => { - let component: ProjectMembersComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ProjectMembersComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ProjectMembersComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/project-members/project-members.component.ts b/worklenz-frontend/src/app/administrator/projects/project-members/project-members.component.ts deleted file mode 100644 index 2833c4f6..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-members/project-members.component.ts +++ /dev/null @@ -1,147 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener} from '@angular/core'; -import {FormBuilder, FormGroup} from "@angular/forms"; -import {ActivatedRoute, Router} from "@angular/router"; -import {ProjectMembersApiService} from "@api/project-members-api.service"; -import {ProjectsApiService} from "@api/projects-api.service"; -import {IProjectMembersViewModel} from "@interfaces/api-models/project-members-view-model"; -import {IPaginationComponent} from "@interfaces/pagination-component"; -import {AuthService} from "@services/auth.service"; -import {UtilsService} from "@services/utils.service"; -import {AvatarNamesMap, DEFAULT_PAGE_SIZE} from "@shared/constants"; -import {EventProjectCreatedOrUpdated, EventTaskCreatedOrUpdate} from "@shared/events"; -import {log_error} from "@shared/utils"; -import {NzTableQueryParams} from "ng-zorro-antd/table"; -import {ProjectFormService} from "@services/project-form-service.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {ProjectsService} from "../projects.service"; - -@Component({ - selector: 'worklenz-project-members', - templateUrl: './project-members.component.html', - styleUrls: ['./project-members.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectMembersComponent implements IPaginationComponent { - model: IProjectMembersViewModel = {}; - searchForm!: FormGroup; - - loading = true; - showTaskDrawer = false; - - // Table sorting & pagination - total = 0; - pageSize = DEFAULT_PAGE_SIZE; - pageIndex = 1; - paginationSizes = [5, 10, 15, 20, 50, 100]; - sortField: string | null = null; - sortOrder: string | null = null; - projectId: string | null = null; - selectedTaskId: string | null = null; - - constructor( - public auth: AuthService, - private api: ProjectsApiService, - private fb: FormBuilder, - private route: ActivatedRoute, - private router: Router, - private projectMembersApi: ProjectMembersApiService, - private utilsService: UtilsService, - private readonly projectFormService: ProjectFormService, - private readonly cdr: ChangeDetectorRef, - private readonly projectsService: ProjectsService - ) { - this.projectId = this.route.snapshot.paramMap.get("id"); - this.searchForm = this.fb.group({search: []}); - this.searchForm.valueChanges.subscribe(() => this.searchMembers()); - - this.projectFormService.onMemberAssignOrRemoveReProject - .pipe(takeUntilDestroyed()) - .subscribe(() => { - void this.get(); - }) - } - - get title() { - return `${this.total || 0} Members`; - } - - @HostListener(`document:${EventTaskCreatedOrUpdate}`) - @HostListener(`document:${EventProjectCreatedOrUpdated}`) - async get() { - if (!this.projectId) return; - try { - this.loading = true; - const res = await this.api.getMembers(this.projectId, this.pageIndex, this.pageSize, this.sortField, - this.sortOrder, this.searchForm.value.search || null); - if (res.done) { - this.model = res.body; - this.total = this.model.total || 0; - - this.utilsService.handleLastIndex(this.total, this.model.data?.length || 0, this.pageIndex, - index => { - this.pageIndex = index; - this.get(); - }); - } - this.loading = false; - this.cdr.detectChanges(); - } catch (e) { - log_error(e); - this.loading = false; - this.cdr.detectChanges(); - } - } - - async searchMembers() { - await this.get(); - } - - isOwnerOrAdmin() { - return this.auth.getCurrentSession()?.owner || this.auth.getCurrentSession()?.is_admin; - } - - isProjectManager() { - if (this.projectsService.projectOwnerTeamMemberId) return this.auth.getCurrentSession()?.team_member_id === this.projectsService.projectOwnerTeamMemberId; - return false; - } - - async removeMember(id?: string) { - if (!id) return; - try { - const res = await this.projectMembersApi.deleteById(id, this.projectId as string); - if (res.done) { - this.get(); - } - } catch (e) { - log_error(e); - } - } - - getColor(name?: string) { - return AvatarNamesMap[name?.charAt(0).toUpperCase() || 'A']; - } - - async onQueryParamsChange(params: NzTableQueryParams) { - const {pageSize, pageIndex, sort} = params; - this.pageIndex = pageIndex; - this.pageSize = pageSize; - - const currentSort = sort.find(item => item.value !== null); - - this.sortField = (currentSort && currentSort.key) || null; - this.sortOrder = (currentSort && currentSort.value) || null; - - await this.get(); - } - - selectMember(id?: string) { - // if (!id) return; - // this.router.navigate(['/worklenz/team/member/' + id]); - } - - onShowChange(show: boolean) { - if (!show) { - this.selectedTaskId = null; - } - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-updates/project-updates.component.html b/worklenz-frontend/src/app/administrator/projects/project-updates/project-updates.component.html deleted file mode 100644 index ec00fc14..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-updates/project-updates.component.html +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/worklenz-frontend/src/app/administrator/projects/project-updates/project-updates.component.scss b/worklenz-frontend/src/app/administrator/projects/project-updates/project-updates.component.scss deleted file mode 100644 index 3493be39..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-updates/project-updates.component.scss +++ /dev/null @@ -1,5 +0,0 @@ -.mentions { - background: #f7f7f7; - font-weight: 500; - border-radius: 4px; -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-updates/project-updates.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/project-updates/project-updates.component.spec.ts deleted file mode 100644 index 84c2ad82..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-updates/project-updates.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ProjectUpdatesComponent } from './project-updates.component'; - -describe('ProjectUpdatesComponent', () => { - let component: ProjectUpdatesComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ProjectUpdatesComponent] - }); - fixture = TestBed.createComponent(ProjectUpdatesComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/project-updates/project-updates.component.ts b/worklenz-frontend/src/app/administrator/projects/project-updates/project-updates.component.ts deleted file mode 100644 index 140028d5..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-updates/project-updates.component.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core'; -import {ActivatedRoute} from "@angular/router"; - -@Component({ - selector: 'worklenz-project-updates', - templateUrl: './project-updates.component.html', - styleUrls: ['./project-updates.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectUpdatesComponent { - projectId: string | null = null; - selectedTaskId: string | null = null; - - showTaskDrawer = false; - - constructor( - private route: ActivatedRoute - ) { - this.projectId = this.route.snapshot.paramMap.get("id"); - } - - onShowChange(show: boolean) { - if (!show) { - this.selectedTaskId = null; - } - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-view/project-sharing/project-sharing-functions.ts b/worklenz-frontend/src/app/administrator/projects/project-view/project-sharing/project-sharing-functions.ts deleted file mode 100644 index f89ac8e7..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-view/project-sharing/project-sharing-functions.ts +++ /dev/null @@ -1,44 +0,0 @@ -// -// get sharePopoverTitle() { -// if (this.loadingSharingInfo) return 'Loading...'; -// return this.sharingEnabled ? 'Share a read-only version of this project' : 'Enable sharing on this project?'; -// }; -// -// get sharingEnabled() { -// return !!(this.sharedInfo && this.sharedInfo.url); -// } -// -// async enableSharing() { -// try { -// this.enablingSharing = true; -// const res = await this.sharedProjectsApi.create({project_id: this.projectId}); -// this.enablingSharing = false; -// if (res.done) { -// this.getSharingInfo(); -// } -// } catch (e) { -// this.enablingSharing = false; -// } -// } -// -// async cancelSharing() { -// try { -// this.sharingCanceling = true; -// const res = await this.sharedProjectsApi.delete(this.projectId); -// this.sharingCanceling = false; -// if (res.done) { -// this.sharedInfo = {}; -// } -// } catch (e) { -// this.sharingCanceling = false; -// } -// } -// -// async copy(textElement: HTMLSpanElement) { -// if (!this.sharingEnabled) return; -// textElement.innerText = "Copied!"; -// await navigator.clipboard.writeText(this.sharedInfo.url as string); -// setTimeout(() => { -// textElement.innerText = "Copy"; -// }, 500); -// } diff --git a/worklenz-frontend/src/app/administrator/projects/project-view/project-sharing/template.html b/worklenz-frontend/src/app/administrator/projects/project-view/project-sharing/template.html deleted file mode 100644 index 2b7a3c88..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-view/project-sharing/template.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/worklenz-frontend/src/app/administrator/projects/project-view/project-view-extra/project-view-extra.component.html b/worklenz-frontend/src/app/administrator/projects/project-view/project-view-extra/project-view-extra.component.html deleted file mode 100644 index 7c3193b4..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-view/project-view-extra/project-view-extra.component.html +++ /dev/null @@ -1,45 +0,0 @@ -
- - - -
- - -
-
-
- - -
-
-
Project Updated
-
{{UPDATES_MESSAGE}}
- - - -
-
-
- - -
-
-
Project Information Updated
-
{{UPDATES_MESSAGE}}
- - - -
-
-
diff --git a/worklenz-frontend/src/app/administrator/projects/project-view/project-view-extra/project-view-extra.component.scss b/worklenz-frontend/src/app/administrator/projects/project-view/project-view-extra/project-view-extra.component.scss deleted file mode 100644 index 6accd714..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-view/project-view-extra/project-view-extra.component.scss +++ /dev/null @@ -1,6 +0,0 @@ -.online-members-help { - position: absolute; - top: -10px; - right: -10px; - z-index: 1; -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-view/project-view-extra/project-view-extra.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/project-view/project-view-extra/project-view-extra.component.spec.ts deleted file mode 100644 index 6579cfcc..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-view/project-view-extra/project-view-extra.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ProjectViewExtraComponent} from './project-view-extra.component'; - -describe('ProjectViewExtraComponent', () => { - let component: ProjectViewExtraComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ProjectViewExtraComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ProjectViewExtraComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/project-view/project-view-extra/project-view-extra.component.ts b/worklenz-frontend/src/app/administrator/projects/project-view/project-view-extra/project-view-extra.component.ts deleted file mode 100644 index 669d7e67..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-view/project-view-extra/project-view-extra.component.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - HostListener, - Input, - NgZone, - OnDestroy, - OnInit, - Output, - TemplateRef, - ViewChild -} from '@angular/core'; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {NzNotificationRef, NzNotificationService} from "ng-zorro-antd/notification"; -import {Subject, takeUntil} from "rxjs"; -import {SocketService} from "@services/socket.service"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-project-view-extra', - templateUrl: './project-view-extra.component.html', - styleUrls: ['./project-view-extra.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectViewExtraComponent implements OnInit, OnDestroy { - @ViewChild("updatesTemplate", {static: false}) updatesTemplate!: TemplateRef; - @ViewChild("projectManagerUpdateTemplate", {static: false}) projectManagerUpdateTemplate!: TemplateRef; - - @Input() refreshing = false; - @Input() projectId: string | null = null; - @Output() refresh = new EventEmitter(); - @Output() refreshAll = new EventEmitter(); - - private readonly JOIN_TXT = "join"; - private readonly LEAVE_TXT = "leave"; - - readonly UPDATES_MESSAGE = 'Other members have made updates to the project. Would you like to apply them?'; - - private notificationRef: NzNotificationRef | null = null; - - members = []; - - disconnected = false; - updatesAvailable = false; - PDUpdateAvailable = false; - - private readonly destroy$ = new Subject(); - - constructor( - private readonly socket: Socket, - private readonly socketService: SocketService, - private readonly notification: NzNotificationService, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly auth: AuthService - ) { - this.socketService.onSocketLoginSuccess$ - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - this.emitJoinOrLeave(this.JOIN_TXT); - }); - - this.socketService.onSocketDisconnect$ - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - this.disconnected = true; - this.cdr.detectChanges(); - }); - - this.socketService.onSocketConnect$ - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - if (!this.disconnected) return; - this.disconnected = false; - this.emitJoinOrLeave(this.JOIN_TXT); - this.cdr.detectChanges(); - }); - } - - ngOnInit() { - this.emitJoinOrLeave(this.JOIN_TXT); - this.socket.on(SocketEvents.JOIN_OR_LEAVE_PROJECT_ROOM.toString(), this.handleMembersView); - this.socket.on(SocketEvents.PROJECT_UPDATES_AVAILABLE.toString(), this.handleProjectUpdates); - this.socket.on(SocketEvents.PROJECT_DATA_CHANGE.toString(), this.handleProjectDataChange); - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - this.emitJoinOrLeave(this.LEAVE_TXT); - this.socket.removeListener(SocketEvents.JOIN_OR_LEAVE_PROJECT_ROOM.toString(), this.handleMembersView); - this.socket.removeListener(SocketEvents.PROJECT_UPDATES_AVAILABLE.toString(), this.handleProjectUpdates); - this.socket.removeListener(SocketEvents.PROJECT_DATA_CHANGE.toString(), this.handleProjectDataChange); - } - - private emitJoinOrLeave(type: string) { - this.socket.emit(SocketEvents.JOIN_OR_LEAVE_PROJECT_ROOM.toString(), {type, id: this.projectId}); - } - - @HostListener("document:visibilitychange") - private onFocusChange() { - this.ngZone.runOutsideAngular(() => { - if (document.visibilityState === "visible") { - this.emitJoinOrLeave(this.JOIN_TXT); - } else { - this.emitJoinOrLeave(this.LEAVE_TXT); - } - }); - } - - private handleMembersView = (res: any) => { - this.members = res; - this.cdr.detectChanges(); - }; - - private handleProjectUpdates = () => { - this.updatesAvailable = true; - if (!this.notificationRef) { - this.notificationRef = this.notification.template(this.updatesTemplate, { - nzDuration: -1 - }); - - this.notificationRef.onClose.subscribe(() => { - this.notificationRef = null; - }); - } - - this.cdr.detectChanges(); - }; - - handleProjectDataChange = (value: { user_id: string }) => { - - this.PDUpdateAvailable = true; - // if (this.auth.getCurrentSession()?.id === value.user_id) return; - if (!this.notificationRef) { - this.notificationRef = this.notification.template(this.projectManagerUpdateTemplate, { - nzDuration: -1 - }); - - this.notificationRef.onClose.subscribe(() => { - this.notificationRef = null; - }); - } - - this.cdr.detectChanges(); - } - - applyUpdates() { - this.updatesAvailable = false; - this.cdr.detectChanges(); - setTimeout(() => { - if (this.PDUpdateAvailable) { - this.applyPMUpdate(); - } - this.refresh.emit(); - }); - } - - applyPMUpdate() { - this.PDUpdateAvailable = false; - this.updatesAvailable = false; - this.cdr.detectChanges(); - setTimeout(() => { - this.refreshAll.emit(); - }); - } - -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-view/project-view.component.html b/worklenz-frontend/src/app/administrator/projects/project-view/project-view.component.html deleted file mode 100644 index 3c451846..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-view/project-view.component.html +++ /dev/null @@ -1,208 +0,0 @@ -
- - - - -
-
- {{project && project.name}} -
-
- - - - {{project.category_name}} - - - - - - - - -
Start date : {{project.start_date | date: 'mediumDate'}}
-
End date : {{project.end_date | date: 'mediumDate'}}
-
-
-
-
-
- - -
-
- {{project.notes | nzEllipsis:60:'...'}} -
-
- -
-
-
- - - - {{project.notes}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
  • -   Import Tasks -
  • -
-
-
-
-
- - - - - {{item.label}} - - - - - Updates - - - - - - - - - - - - - - - - - - -
-
- -
- -
- -
- -
-
- -
- - - - - - -
-
- - - - - -
-
-
- - - - - - - - - - diff --git a/worklenz-frontend/src/app/administrator/projects/project-view/project-view.component.scss b/worklenz-frontend/src/app/administrator/projects/project-view/project-view.component.scss deleted file mode 100644 index 06a30d58..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-view/project-view.component.scss +++ /dev/null @@ -1,38 +0,0 @@ -nz-page-header-title { - max-width: 60%; -} - -nz-page-header-subtitle { - max-width: 35%; -} - -.project-title { - max-width: 400px; - overflow: hidden; - text-overflow: ellipsis; -} - -.project-subtitle.ellipsis-on { - max-width: 75%; - overflow: hidden; - text-overflow: ellipsis; -} - -.updates-badge { - position: absolute; - top: 6px; - right: 2px; - background-color: #ff4d4f; - width: 6px; - height: 6px; - border-radius: 100%; -} - -.tab-text, .pin-icon { - color: #1890ff; -} - -.rotated { - transform: rotateZ(-45deg); - transition: 0.15s all; -} diff --git a/worklenz-frontend/src/app/administrator/projects/project-view/project-view.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/project-view/project-view.component.spec.ts deleted file mode 100644 index 4a749b5d..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-view/project-view.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ProjectViewComponent} from './project-view.component'; - -describe('ProjectViewComponent', () => { - let component: ProjectViewComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ProjectViewComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ProjectViewComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/project-view/project-view.component.ts b/worklenz-frontend/src/app/administrator/projects/project-view/project-view.component.ts deleted file mode 100644 index 0a4378a1..00000000 --- a/worklenz-frontend/src/app/administrator/projects/project-view/project-view.component.ts +++ /dev/null @@ -1,500 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild} from '@angular/core'; -import {FormBuilder, FormGroup} from "@angular/forms"; -import {ActivatedRoute, NavigationEnd, Router} from "@angular/router"; -import {ProjectsApiService} from "@api/projects-api.service"; -import {TasksApiService} from "@api/tasks-api.service"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {IProjectViewModel} from "@interfaces/api-models/project-view-model"; -import {ITaskCreateRequest} from "@interfaces/api-models/task-create-request"; -import {EGanttChartTypes} from "@interfaces/gantt-chart"; -import {ITask} from "@interfaces/task"; -import {AppService} from "@services/app.service"; -import {AuthService} from "@services/auth.service"; -import {DEFAULT_TASK_NAME, UNMAPPED} from "@shared/constants"; -import {dispatchTasksChange} from "@shared/events"; -import {SocketEvents} from "@shared/socket-events"; -import {log_error} from "@shared/utils"; -import {Socket} from "ngx-socket-io"; -import {ProjectFormModalComponent} from "../../components/project-form-modal/project-form-modal.component"; -import {ProjectsService} from "../projects.service"; -import {TaskListV2Service} from "../../modules/task-list-v2/task-list-v2.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {NotificationSettingsService} from "@services/notification-settings.service"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {ProjectPhasesService} from "@services/project-phases.service"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; -import {ProjectFormService} from '@services/project-form-service.service'; -import {Location} from "@angular/common"; -import {filter} from "rxjs"; -import {ProjectUpdatesDrawerComponent} from "@admin/components/project-updates-drawer/project-updates-drawer.component"; -import {ProjectCommentsApiService} from "@api/project-comments-api.service"; -import {ProjectUpdatesService} from "@services/project-updates.service"; -import { - ProjectTemplateCreateDrawerComponent -} from "@admin/components/project-template-create-drawer/project-template-create-drawer.component"; -import {RoadmapV2Service} from "../../modules/roadmap-v2/project-roadmap-v2-custom/services/roadmap-v2-service.service"; - -@Component({ - selector: 'worklenz-project-view', - templateUrl: './project-view.component.html', - styleUrls: ['./project-view.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectViewComponent implements OnInit, OnDestroy { - @ViewChild(ProjectFormModalComponent) projectsForm!: ProjectFormModalComponent; - @ViewChild(ProjectUpdatesDrawerComponent) projectUpdatesDrawer!: ProjectUpdatesDrawerComponent; - @ViewChild(ProjectTemplateCreateDrawerComponent) projectTemplateCreateDrawer!: ProjectTemplateCreateDrawerComponent; - - tasksSearchForm!: FormGroup; - - readonly ganttType = EGanttChartTypes; - readonly options = [ - {label: 'Group', value: 0, icon: 'appstore'}, - {label: 'List', value: 1, icon: 'bars'} - ]; - readonly tabs = [ - {label: 'Task List', tab: 'tasks-list', index: 0, isPinned: false}, - {label: 'Board', tab: 'board', index: 1, isPinned: false}, - {label: 'Workload', tab: 'workload', index: 2, isPinned: false}, - {label: 'Roadmap', tab: 'roadmap', index: 3, isPinned: false}, - {label: 'Insights', tab: 'project-insights-member-overview', index: 4, isPinned: false}, - {label: 'Files', tab: 'all-attachments', index: 5, isPinned: false}, - {label: 'Members', tab: 'members', index: 6, isPinned: false} - ]; - - projects: IProjectViewModel[] = []; - project: IProjectViewModel = {}; - selectedTask: ITask = {}; - - private session: ILocalSession | null = null; - - loading = true; - projectId: string | null = null; - selectedTaskId: string | null = null; - - refreshing = false; - showInviteMembersModal = false; - importTemplateVisible = false; - taskUpdated = false; - creatingTask = false; - ownerOrAdmin = false; - projectManager = false; - showTaskModal = false; - showDescriptionModel = false; - backButtonClicked = false; - - selectedTabIndex = 0; - currentTabIndex = 0; - tasksViewMode = 0; - defaultView = 0; - pinnedTabIndex = 0; - - enableBadge = false; - - constructor( - private readonly app: AppService, - private readonly route: ActivatedRoute, - private readonly router: Router, - private readonly auth: AuthService, - private readonly projectsApiService: ProjectsApiService, - private readonly tasksService: TasksApiService, - private readonly fb: FormBuilder, - private readonly service: ProjectsService, - private readonly list: TaskListV2Service, - private readonly socket: Socket, - private readonly notificationSettings: NotificationSettingsService, - private readonly phasesService: ProjectPhasesService, - private readonly cdr: ChangeDetectorRef, - private readonly taskView: TaskViewService, - private readonly location: Location, - private readonly projectFormService: ProjectFormService, - private readonly projectCommentsApi: ProjectCommentsApiService, - private readonly projectCommentsService: ProjectUpdatesService, - private readonly roadmapService: RoadmapV2Service, - private readonly projectsService: ProjectsService - ) { - - this.app.setTitle('Loading...'); - - this.setProjectId(); - this.setDefaultTab(); - this.setPinnedTab(); - - this.tasksSearchForm = this.fb.group({ - search: [] - }); - - this.notificationSettings.onNotificationClick - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.setProjectId(); - this.refresh(); - void this.getProject(); - this.handleTaskViewOpened(); - }); - - this.projectFormService.onProjectUpdate - .pipe(takeUntilDestroyed()) - .subscribe(() => { - void this.getProject(); - }) - - this.projectCommentsService.onBadgeDisable - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.enableBadge = false; - }) - - this.session = this.auth.getCurrentSession(); - } - - get getLocalUpdatesCount() { - if (!this.projectId) return 0; - const count = localStorage.getItem("worklenz.project.updates-" + this.projectId); - return count ? +count : 0; - } - - hasUnreadUpdates() { - return this.getLocalUpdatesCount === 0; - } - - async ngOnInit() { - this.ownerOrAdmin = this.auth.isOwnerOrAdmin(); - this.projectManager = this.isProjectManager(); - void this.getProject(); - this.handleTaskViewOpened(); - - this.router.events - .pipe(filter((event) => event instanceof NavigationEnd)) - .subscribe(() => { - this.handleBackNavigation(); - }); - - window.onpopstate = (event) => { - this.backButtonClicked = true; - }; - - this.getUpdatesCount(); - - this.socket.on(SocketEvents.NEW_PROJECT_COMMENT_RECEIVED.toString(), () => { - this.enableBadge = true; - this.cdr.detectChanges(); - }); - - } - - ngOnDestroy(): void { - window.onpopstate = null; - this.socket.removeListener(SocketEvents.NEW_PROJECT_COMMENT_RECEIVED.toString(), () => { - return - }); - } - - private handleBackNavigation(): void { - const tabParam = this.route.snapshot.queryParamMap.get('tab'); - if (this.backButtonClicked && !tabParam) { - this.router.navigateByUrl('/worklenz/projects'); - } - this.backButtonClicked = false; - } - - private setProjectId() { - this.projectId = this.route.snapshot.paramMap.get('id'); - this.service.id = this.projectId; - } - - openCreateTaskModal() { - void this.addInstantTask(); - } - - private isGroupByPhase() { - return this.list.getCurrentGroup().value === this.list.GROUP_BY_PHASE_VALUE; - } - - isProjectManager() { - if (this.projectsService.projectOwnerTeamMemberId) return this.auth.getCurrentSession()?.team_member_id === this.projectsService.projectOwnerTeamMemberId; - return false; - } - - async addInstantTask() { - try { - this.creatingTask = true; - const session = this.auth.getCurrentSession(); - - const body: ITaskCreateRequest = { - name: DEFAULT_TASK_NAME, - project_id: this.projectId ?? "", - reporter_id: session?.id, - team_id: session?.team_id, - chart_start: this.roadmapService.chartStartDate ? this.roadmapService.chartStartDate : '' - }; - - this.socket.once(SocketEvents.QUICK_TASK.toString(), (task: IProjectTask) => { - if (task?.id) { - this.selectedTask = task; - this.selectedTaskId = task.id; - this.showTaskModal = true; - - this.taskView.emitOpenTask({ - task_id: task.id || null, - project_id: task.project_id || null - }); - - // add the new task to the task list - const groupId = this.isGroupByPhase() ? UNMAPPED : this.list.getGroupIdByGroupedColumn(task); - if (groupId) - this.list.addTask(task, groupId); - this.service.emitNewTaskCreated(task); - } - this.creatingTask = false; - this.cdr.markForCheck(); - }); - this.socket.emit(SocketEvents.QUICK_TASK.toString(), JSON.stringify(body)); - } catch (e) { - log_error(e); - this.creatingTask = false; - } - - this.cdr.markForCheck(); - } - - async getTasks() { - if (!this.projectId) return; - try { - this.loading = !this.taskUpdated; - await this.tasksService.getTasksByProject(this.projectId); - this.loading = false; - } catch (e) { - this.loading = false; - } - - this.cdr.markForCheck(); - } - - async getUpdatesCount() { - if (!this.projectId) return; - try { - const res = await this.projectCommentsApi.getCountByProjectId(this.projectId); - if (res) { - if (this.getLocalUpdatesCount < res.body) { - this.enableBadge = true; - } - } - this.cdr.markForCheck(); - } catch (e) { - log_error(e); - this.cdr.markForCheck(); - } - } - - async getProjectManager() { - if (!this.projectId) return; - try { - const res = await this.projectsApiService.getProjectManager(this.projectId); - if (res.done) { - } - } catch (e) { - log_error(e) - this.cdr.markForCheck(); - } - } - - onTaskCreateOrUpdate() { - this.taskUpdated = true; - void this.getTasks(); - this.selectedTaskId = null; - this.taskUpdated = false; - this.cdr.markForCheck(); - } - - async getProject() { - if (!this.projectId) return; - try { - this.loading = true; - const res = await this.projectsApiService.getById(this.projectId); - this.loading = false; - if (res.done) { - this.project = res.body; - if (this.project) { - if (this.project.project_manager && (this.auth.getCurrentSession()?.team_member_id === this.project.project_manager?.id)) { - this.service.projectOwnerTeamMemberId = this.project.project_manager.id as string; - } else { - this.service.projectOwnerTeamMemberId = null; - } - } - if (!this.project) { - this.back(); - return; - } - - if (this.project.name) - this.app.setTitle(this.project.name); - if (res.body.phase_label) - this.phasesService.updateLabel(res.body.phase_label); - } - } catch (e) { - this.loading = false; - } - - this.cdr.markForCheck(); - } - - tabChanged(event: number) { - this.currentTabIndex = event; - if (this.currentTabIndex !== 1) { - this.tasksViewMode = 0; - } - - this.cdr.markForCheck(); - } - - openProjectForm() { - if (!this.projectId) return; - this.projectsForm?.open(this.projectId, true); - } - - back() { - void this.router.navigate(['/worklenz/projects']); - this.service.projectOwnerTeamMemberId = null; - } - - refresh() { - this.refreshing = true; - setTimeout(() => { - this.refreshing = false; - this.cdr.markForCheck(); - }, 10); - } - - refreshAll() { - setTimeout(() => { - window.location.reload(); - this.cdr.markForCheck(); - }, 1); - } - - private setDefaultTab() { - if (!this.route.snapshot.queryParamMap.get('tab')) { - void this.router.navigate( - [], - { - relativeTo: this.route, - queryParams: {tab: this.route.snapshot.queryParamMap.get('tab')}, - queryParamsHandling: 'merge', // remove to replace all query params by provided - }); - } else { - switch (this.route.snapshot.queryParamMap.get('tab')) { - case 'tasks-list': - this.selectedTabIndex = 0 - break; - case 'board': - this.selectedTabIndex = 1 - break; - case 'workload': - this.selectedTabIndex = 2 - break; - case 'roadmap': - this.selectedTabIndex = 3 - break; - case 'project-insights-member-overview': - this.selectedTabIndex = 4 - break; - case 'all-attachments': - this.selectedTabIndex = 5 - break; - case 'members': - this.selectedTabIndex = 6 - break; - case 'updates': - this.selectedTabIndex = 7 - break; - default : this.selectedTabIndex = 0; - } - } - } - - private setPinnedTab() { - if(this.route.snapshot.queryParamMap.get('pinned_tab')) { - - } - this.tabs.forEach( (tab) => { - tab.isPinned = false - }); - if(this.route.snapshot.queryParamMap.get('tab') === 'board') { - this.selectedTabIndex = 1; - } - if(this.route.snapshot.queryParamMap.get('pinned_tab') === 'board') { - this.pinnedTabIndex = 1; - } - this.tabs[this.pinnedTabIndex].isPinned = true; - } - - openImportTasksDrawer(): void { - this.importTemplateVisible = true; - this.cdr.markForCheck(); - } - - importDone() { - dispatchTasksChange(); - this.closeImport(); - } - - closeImport() { - this.importTemplateVisible = false; - this.cdr.markForCheck(); - } - - openInviteMembersDrawer() { - this.showInviteMembersModal = true; - } - - private handleTaskViewOpened() { - const id = this.route.snapshot.queryParamMap.get("task"); - if (id) { - this.selectedTaskId = id; - this.showTaskModal = true; - this.cdr.markForCheck(); - } - } - - toggleProjectSubscription() { - if (!this.projectId) return; - this.project.subscribed = !this.project.subscribed; - const body = { - project_id: this.projectId, - user_id: this.session?.id, - team_member_id: this.session?.team_member_id, - mode: this.project.subscribed ? 0 : 1 - }; - this.socket.emit(SocketEvents.PROJECT_SUBSCRIBERS_CHANGE.toString(), body); - } - - openTemplateCreateDrawer() { - this.projectTemplateCreateDrawer.open(); - } - - async updatePinnedView(pinnedTabIndex: number) { - if(!this.projectId) return; - try { - const res = await this.projectsApiService.updateDefaultView(this.projectId, pinnedTabIndex); - if(res.done) { - this.tabs.forEach( (tab) => { - tab.isPinned = false - }); - - this.pinnedTabIndex = pinnedTabIndex; - this.tabs[pinnedTabIndex].isPinned = true; - - this.router.navigate([], { - queryParams: { pinned_tab: pinnedTabIndex === 1 ? 'board' : 'tasks-list'}, - queryParamsHandling: 'merge' - }); - - } - } catch (e) { - - } - this.cdr.markForCheck(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/projects/projects-routing.module.ts b/worklenz-frontend/src/app/administrator/projects/projects-routing.module.ts deleted file mode 100644 index 55efb0e7..00000000 --- a/worklenz-frontend/src/app/administrator/projects/projects-routing.module.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {ProjectsComponent} from './projects/projects.component'; -import {ProjectOverviewComponent} from './project-insights/project-overview/project-overview.component'; -import {ProjectViewComponent} from "./project-view/project-view.component"; - -const routes: Routes = [ - {path: '', component: ProjectsComponent}, - {path: 'member/:id', component: ProjectOverviewComponent}, - {path: ':id', component: ProjectViewComponent}, -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class ProjectsRoutingModule { -} diff --git a/worklenz-frontend/src/app/administrator/projects/projects.module.ts b/worklenz-frontend/src/app/administrator/projects/projects.module.ts deleted file mode 100644 index 98c6c862..00000000 --- a/worklenz-frontend/src/app/administrator/projects/projects.module.ts +++ /dev/null @@ -1,250 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; - -import {ProjectsRoutingModule} from './projects-routing.module'; -import {ProjectsComponent} from './projects/projects.component'; -import {NzDividerModule} from 'ng-zorro-antd/divider'; -import {NzModalModule} from 'ng-zorro-antd/modal'; -import {FormsModule, ReactiveFormsModule} from '@angular/forms'; -import {NzFormModule} from 'ng-zorro-antd/form'; -import {NzInputModule} from 'ng-zorro-antd/input'; -import {NzAutocompleteModule} from 'ng-zorro-antd/auto-complete'; -import {NzPageHeaderModule} from 'ng-zorro-antd/page-header'; -import {NzButtonModule} from 'ng-zorro-antd/button'; -import {NzTableModule} from 'ng-zorro-antd/table'; -import {NzTabsModule} from 'ng-zorro-antd/tabs'; -import {NzIconModule} from 'ng-zorro-antd/icon'; -import {NzListModule} from 'ng-zorro-antd/list'; -import {NzTypographyModule} from 'ng-zorro-antd/typography'; -import {NzSelectModule} from 'ng-zorro-antd/select'; -import {NzTagModule} from 'ng-zorro-antd/tag'; -import {NzCheckboxModule} from 'ng-zorro-antd/checkbox'; -import {NzProgressModule} from 'ng-zorro-antd/progress'; -import {NzPopconfirmModule} from 'ng-zorro-antd/popconfirm'; -import {NzAvatarModule} from 'ng-zorro-antd/avatar'; -import {NzToolTipModule} from 'ng-zorro-antd/tooltip'; -import {NzSkeletonModule} from 'ng-zorro-antd/skeleton'; -import {NzRadioModule} from 'ng-zorro-antd/radio'; -import {NzDatePickerModule} from 'ng-zorro-antd/date-picker'; -import {NzEmptyModule} from 'ng-zorro-antd/empty'; -import {ScrollingModule} from '@angular/cdk/scrolling'; -import {NzCardModule} from 'ng-zorro-antd/card'; -import {NzSpaceModule} from 'ng-zorro-antd/space'; -import {NzBreadCrumbModule} from 'ng-zorro-antd/breadcrumb'; -import {ProjectOverviewComponent} from './project-insights/project-overview/project-overview.component'; -import {NzAffixModule} from 'ng-zorro-antd/affix'; -import {NzSwitchModule} from 'ng-zorro-antd/switch'; -import {AdministratorModule} from '../administrator.module'; -import {NzSpinModule} from 'ng-zorro-antd/spin'; -import {NzLayoutModule} from 'ng-zorro-antd/layout'; -import {NzBadgeModule} from 'ng-zorro-antd/badge'; -import {NzPopoverModule} from 'ng-zorro-antd/popover'; -import {NzCollapseModule} from 'ng-zorro-antd/collapse'; -import {NzSegmentedModule} from 'ng-zorro-antd/segmented'; -import {ProjectMembersComponent} from "./project-members/project-members.component"; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {ProjectViewComponent} from './project-view/project-view.component'; -import {DragDropModule} from '@angular/cdk/drag-drop'; -import {AllTasksAttachmentsComponent} from './all-tasks-attachments/all-tasks-attachments.component'; -import {NzRateModule} from "ng-zorro-antd/rate"; -import {NzAlertModule} from "ng-zorro-antd/alert"; -import {StatusFormComponent} from "../components/status-form/status-form.component"; -import {FromNowPipe} from "@pipes/from-now.pipe"; -import {AvatarsComponent} from "../components/avatars/avatars.component"; -import {ProjectMembersFormComponent} from "../components/project-members-form/project-members-form.component"; -import {ProjectFormModalComponent} from "../components/project-form-modal/project-form-modal.component"; -import {ImportTasksTemplateComponent} from "../components/import-tasks-template/import-tasks-template.component"; -import {ProjectInsightsComponent} from './project-insights/project-insights.component'; -import {TaskInsightsComponent} from './project-insights/task-insights/task-insights.component'; -import {NzStatisticModule} from "ng-zorro-antd/statistic"; -import {StatusOverviewComponent} from './project-insights/project-insights/status-overview/status-overview.component'; -import { - PriorityBreakdownComponent -} from './project-insights/project-insights/priority-breakdown/priority-breakdown.component'; -import { - LastUpdatedTasksComponent -} from './project-insights/project-insights/last-updated-tasks/last-updated-tasks.component'; -import {ProjectStatsComponent} from './project-insights/components/project-stats/project-stats.component'; -import { - ProjectInsightsMemberOverviewComponent -} from './project-insights/project-members/project-insights-member-overview/project-insights-member-overview.component'; -import {MemberStatsComponent} from './project-insights/project-members/member-stats/member-stats.component'; -import {MemberTasksComponent} from './project-insights/project-members/member-tasks/member-tasks.component'; -import { - ProjectDeadlineComponent -} from './project-insights/project-insights/project-deadline/project-deadline.component'; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {NzDescriptionsModule} from "ng-zorro-antd/descriptions"; -import {TaskViewModule} from "../components/task-view/task-view.module"; -import {NzNoAnimationModule} from "ng-zorro-antd/core/no-animation"; -import {TaskListV2Module} from "../modules/task-list-v2/task-list-v2.module"; -import {NzPipesModule} from "ng-zorro-antd/pipes"; -import {FirstCharUpperPipe} from "@pipes/first-char-upper.pipe"; -import {WlSafeArrayPipe} from "@pipes/wl-safe-array.pipe"; -import {ProjectViewExtraComponent} from './project-view/project-view-extra/project-view-extra.component'; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; -import {KanbanViewV2Module} from '../modules/kanban-view-v2/kanban-view-v2.module'; -import {DateFormatterPipe} from "../../pipes/date-formatter.pipe"; -import {ProjectsListViewComponent} from './projects/projects-list-view/projects-list-view.component'; -import {ProjectsFolderViewComponent} from './projects/projects-folder-view/projects-folder-view.component'; -import { - ProjectsFolderFormDrawerComponent -} from './projects/projects-folder-form-drawer/projects-folder-form-drawer.component'; -import {ProjectFilterByTooltipPipe} from './projects/pipes/project-filter-by-tooltip.pipe'; -import {ProjectUpdatesDrawerComponent} from "@admin/components/project-updates-drawer/project-updates-drawer.component"; -import {ProjectUpdatesComponent} from './project-updates/project-updates.component'; -import {NzCommentModule} from "ng-zorro-antd/comment"; -import {ProjectUpdatesInputComponent} from "@admin/components/project-updates-input/project-updates-input.component"; -import {ProjectUpdatesListComponent} from "@admin/components/project-updates-list/project-updates-list.component"; -import {NgChartsModule} from "ng2-charts"; -import {EllipsisPipe} from "@pipes/ellipsis.pipe"; -import { - ProjectTemplateCreateDrawerComponent -} from "@admin/components/project-template-create-drawer/project-template-create-drawer.component"; -import { - ProjectTemplateImportDrawerComponent -} from "@admin/components/project-template-import-drawer/project-template-import-drawer.component"; -import {WorkloadGaantChartV2Component} from './components-v2/workload-gaant-chart-v2/workload-gaant-chart-v2.component'; -import {TaskNameComponent} from './components-v2/workload-gaant-chart-v2/components/task-name/task-name.component'; -import {WLStartDateComponent} from './components-v2/workload-gaant-chart-v2/components/start-date/start-date.component'; -import {WLEndDateComponent} from './components-v2/workload-gaant-chart-v2/components/end-date/end-date.component'; -import {RxFor} from "@rx-angular/template/for"; -import { - MemberTasksDrawerComponent -} from './components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/member-tasks-drawer.component'; -import { - WLTaskListRowComponent -} from './components-v2/workload-gaant-chart-v2/components/task-list-row/task-list-row.component'; -import { - TaskListAddTaskInputComponent -} from "../modules/task-list-v2/task-list-table/task-list-add-task-input/task-list-add-task-input.component"; -import {WLStatusComponent} from './components-v2/workload-gaant-chart-v2/components/status/status.component'; -import {WLPriorityComponent} from './components-v2/workload-gaant-chart-v2/components/priority/priority.component'; -import {TaskPriorityLabelComponent} from "@admin/components/task-priority-label/task-priority-label.component"; -import {WLPhaseComponent} from './components-v2/workload-gaant-chart-v2/components/phase/phase.component'; -import { - OverviewTabComponent -} from './components-v2/workload-gaant-chart-v2/components/member-tasks-drawer/overview-tab/overview-tab.component'; -import { MemberTaskAddContainerComponent } from './components-v2/workload-gaant-chart-v2/components/member-task-add-container/member-task-add-container.component'; -import { TaskListHeaderComponent } from './components-v2/workload-gaant-chart-v2/components/task-list-header/task-list-header.component'; -import { WLContextMenuComponent } from './components-v2/workload-gaant-chart-v2/components/context-menu/context-menu.component'; -import {GanttChartV2Module} from "../modules/roadmap-v2/gantt-chart-v2.module"; - -@NgModule({ - declarations: [ - ProjectsComponent, - ProjectOverviewComponent, - ProjectMembersComponent, - ProjectViewComponent, - AllTasksAttachmentsComponent, - ProjectInsightsComponent, - TaskInsightsComponent, - StatusOverviewComponent, - PriorityBreakdownComponent, - LastUpdatedTasksComponent, - ProjectStatsComponent, - ProjectInsightsMemberOverviewComponent, - MemberStatsComponent, - MemberTasksComponent, - ProjectDeadlineComponent, - ProjectViewExtraComponent, - ProjectsListViewComponent, - ProjectsFolderViewComponent, - ProjectsFolderFormDrawerComponent, - ProjectFilterByTooltipPipe, - ProjectUpdatesComponent, - WorkloadGaantChartV2Component, - TaskNameComponent, - WLStartDateComponent, - WLEndDateComponent, - MemberTasksDrawerComponent, - WLTaskListRowComponent, - WLStatusComponent, - WLPriorityComponent, - WLPhaseComponent, - OverviewTabComponent, - MemberTaskAddContainerComponent, - TaskListHeaderComponent, - WLContextMenuComponent - ], - imports: [ - CommonModule, - ProjectsRoutingModule, - NzDividerModule, - NzModalModule, - ReactiveFormsModule, - NzFormModule, - NzInputModule, - NzAutocompleteModule, - NzPageHeaderModule, - NzButtonModule, - NzTableModule, - NzTabsModule, - NzIconModule, - NzListModule, - NzTypographyModule, - NzSelectModule, - NzTagModule, - FormsModule, - NzCheckboxModule, - NzProgressModule, - NzPopconfirmModule, - NzAvatarModule, - NzToolTipModule, - NzSkeletonModule, - NzRadioModule, - NzDatePickerModule, - NzEmptyModule, - ScrollingModule, - NzCardModule, - NzSpaceModule, - NzBreadCrumbModule, - NzAffixModule, - NzLayoutModule, - NzSwitchModule, - AdministratorModule, - NzSpinModule, - NzBadgeModule, - NzPopoverModule, - NzCollapseModule, - NzSegmentedModule, - NzDrawerModule, - NzDropDownModule, - DragDropModule, - NzRateModule, - NzAlertModule, - StatusFormComponent, - FromNowPipe, - AvatarsComponent, - ProjectMembersFormComponent, - ProjectFormModalComponent, - ImportTasksTemplateComponent, - NzStatisticModule, - SearchByNamePipe, - NzDescriptionsModule, - TaskViewModule, - NzNoAnimationModule, - TaskListV2Module, - NzPipesModule, - FirstCharUpperPipe, - WlSafeArrayPipe, - SafeStringPipe, - DateFormatterPipe, - KanbanViewV2Module, - ProjectUpdatesDrawerComponent, - NzCommentModule, - ProjectUpdatesInputComponent, - ProjectUpdatesListComponent, - NgChartsModule, - EllipsisPipe, - ProjectTemplateCreateDrawerComponent, - ProjectTemplateImportDrawerComponent, - RxFor, - TaskListAddTaskInputComponent, - TaskPriorityLabelComponent, - GanttChartV2Module - ] -}) -export class ProjectsModule { -} diff --git a/worklenz-frontend/src/app/administrator/projects/projects.service.spec.ts b/worklenz-frontend/src/app/administrator/projects/projects.service.spec.ts deleted file mode 100644 index 189087c4..00000000 --- a/worklenz-frontend/src/app/administrator/projects/projects.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {TestBed} from '@angular/core/testing'; - -import {ProjectsService} from './projects.service'; - -describe('ProjectsService', () => { - let service: ProjectsService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(ProjectsService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/projects.service.ts b/worklenz-frontend/src/app/administrator/projects/projects.service.ts deleted file mode 100644 index 05ee18af..00000000 --- a/worklenz-frontend/src/app/administrator/projects/projects.service.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {Injectable} from '@angular/core'; -import {Subject} from "rxjs"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; - -@Injectable({ - providedIn: 'root' -}) -export class ProjectsService { - public id: string | null = null; - public projectOwnerTeamMemberId: string | null = null; - private readonly newTaskCreatedSbj$ = new Subject(); - - get onNewTaskCreated() { - return this.newTaskCreatedSbj$.asObservable(); - } - - public emitNewTaskCreated(task: IProjectTask) { - this.newTaskCreatedSbj$.next(task); - } - -} diff --git a/worklenz-frontend/src/app/administrator/projects/projects/pipes/project-filter-by-tooltip.pipe.spec.ts b/worklenz-frontend/src/app/administrator/projects/projects/pipes/project-filter-by-tooltip.pipe.spec.ts deleted file mode 100644 index 62a443d7..00000000 --- a/worklenz-frontend/src/app/administrator/projects/projects/pipes/project-filter-by-tooltip.pipe.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {ProjectFilterByTooltipPipe} from './project-filter-by-tooltip.pipe'; - -describe('ProjectFilterByTooltipPipe', () => { - it('create an instance', () => { - const pipe = new ProjectFilterByTooltipPipe(); - expect(pipe).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/projects/pipes/project-filter-by-tooltip.pipe.ts b/worklenz-frontend/src/app/administrator/projects/projects/pipes/project-filter-by-tooltip.pipe.ts deleted file mode 100644 index 9d518a41..00000000 --- a/worklenz-frontend/src/app/administrator/projects/projects/pipes/project-filter-by-tooltip.pipe.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; - -@Pipe({ - name: 'projectFilterByTooltip' -}) -export class ProjectFilterByTooltipPipe implements PipeTransform { - transform(name?: string): string { - return `Click to filter by "${name}".`; - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-form-drawer/projects-folder-form-drawer.component.html b/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-form-drawer/projects-folder-form-drawer.component.html deleted file mode 100644 index c158515e..00000000 --- a/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-form-drawer/projects-folder-form-drawer.component.html +++ /dev/null @@ -1,45 +0,0 @@ - - - -
- - - Name - - - - - - - Folder Color -   - - - -
    -
  • -   - -
  • -
-
-
- - -
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-form-drawer/projects-folder-form-drawer.component.scss b/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-form-drawer/projects-folder-form-drawer.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-form-drawer/projects-folder-form-drawer.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-form-drawer/projects-folder-form-drawer.component.spec.ts deleted file mode 100644 index 04f09c56..00000000 --- a/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-form-drawer/projects-folder-form-drawer.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ProjectsFolderFormDrawerComponent} from './projects-folder-form-drawer.component'; - -describe('ProjectsFolderFormDrawerComponent', () => { - let component: ProjectsFolderFormDrawerComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ProjectsFolderFormDrawerComponent] - }); - fixture = TestBed.createComponent(ProjectsFolderFormDrawerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-form-drawer/projects-folder-form-drawer.component.ts b/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-form-drawer/projects-folder-form-drawer.component.ts deleted file mode 100644 index 876909d9..00000000 --- a/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-form-drawer/projects-folder-form-drawer.component.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - Input, - NgZone, - ViewChild -} from '@angular/core'; -import {ProjectsDefaultColorCodes} from "@shared/constants"; -import {FormBuilder, FormGroup, Validators} from "@angular/forms"; -import {ProjectFoldersApiService} from "@api/project-folders-api.service"; -import {FolderCreateEventCallback, ProjectsFolderFormDrawerService} from "./projects-folder-form-drawer.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {IProjectFolder} from "@interfaces/project-folder"; - -@Component({ - selector: 'worklenz-projects-folder-form-drawer', - templateUrl: './projects-folder-form-drawer.component.html', - styleUrls: ['./projects-folder-form-drawer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectsFolderFormDrawerComponent { - @ViewChild('nameInput', {static: false}) nameInput!: ElementRef; - @Input() folderId: string | null = null; - - form!: FormGroup; - - show = false; - loading = false; - - readonly COLOR_CODES = ProjectsDefaultColorCodes; - private createCallback: FolderCreateEventCallback | null = null; - - get activeColor() { - return this.form.controls['color_code'].value; - } - - get title() { - return this.folderId ? "Update the folder" : "Create a folder"; - } - - get buttonText() { - return this.folderId ? "Update" : "Create"; - } - - get valid() { - return this.form.valid; - } - - constructor( - private readonly ngZone: NgZone, - private readonly cdr: ChangeDetectorRef, - private readonly fb: FormBuilder, - private readonly api: ProjectFoldersApiService, - private readonly service: ProjectsFolderFormDrawerService - ) { - this.form = this.fb.group({ - name: [null, [Validators.required]], - color_code: [null] - }); - - this.service.onCreateInvoke - .pipe(takeUntilDestroyed()) - .subscribe((callback) => { - this.createCallback = callback; - this.open(); - }); - } - - public open() { - this.show = true; - this.cdr.markForCheck(); - } - - public close() { - this.show = false; - this.form.reset({ - name: null, - color_code: null - }); - this.invokeCreate(); - this.cdr.markForCheck(); - } - - invokeCreate(folder?: IProjectFolder) { - if (this.createCallback) { - this.createCallback(folder); - this.createCallback = null; - } - } - - async submit() { - if (this.form.invalid) return; - try { - this.loading = true; - const body = { - name: this.form.value.name, - color_code: this.form.value.color_code - }; - const res = await this.api.create(body); - if (res.done) { - this.invokeCreate(res.body); - this.close(); - } - this.loading = false; - } catch (e) { - this.loading = false; - } - - this.cdr.markForCheck(); - } - - setColorCode(colorCode: string) { - this.form.controls["color_code"].setValue(colorCode); - } - - onVisibilityChange(visible: boolean) { - if (visible) { - this.setFocusToNameInput(); - } - } - - private setFocusToNameInput() { - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - const element = this.nameInput.nativeElement as HTMLInputElement; - if (element) - element.focus(); - }, 100); - }); - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-form-drawer/projects-folder-form-drawer.service.ts b/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-form-drawer/projects-folder-form-drawer.service.ts deleted file mode 100644 index 5d3225f0..00000000 --- a/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-form-drawer/projects-folder-form-drawer.service.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {Injectable} from '@angular/core'; -import {IProjectFolder} from "@interfaces/project-folder"; -import {Subject} from "rxjs"; - -export type FolderCreateEventCallback = (folder?: IProjectFolder) => void; - -@Injectable({ - providedIn: 'root' -}) -export class ProjectsFolderFormDrawerService { - private _createSbj$ = new Subject(); - - public get onCreateInvoke() { - return this._createSbj$.asObservable(); - } - - public create(fn: FolderCreateEventCallback) { - this._createSbj$.next(fn); - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-view/projects-folder-view.component.html b/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-view/projects-folder-view.component.html deleted file mode 100644 index c1e1af8f..00000000 --- a/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-view/projects-folder-view.component.html +++ /dev/null @@ -1 +0,0 @@ -

projects-folder-view works!

diff --git a/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-view/projects-folder-view.component.scss b/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-view/projects-folder-view.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-view/projects-folder-view.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-view/projects-folder-view.component.spec.ts deleted file mode 100644 index 66ed2eb5..00000000 --- a/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-view/projects-folder-view.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ProjectsFolderViewComponent} from './projects-folder-view.component'; - -describe('ProjectsFolderViewComponent', () => { - let component: ProjectsFolderViewComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ProjectsFolderViewComponent] - }); - fixture = TestBed.createComponent(ProjectsFolderViewComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-view/projects-folder-view.component.ts b/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-view/projects-folder-view.component.ts deleted file mode 100644 index e0043156..00000000 --- a/worklenz-frontend/src/app/administrator/projects/projects/projects-folder-view/projects-folder-view.component.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {Component} from '@angular/core'; - -@Component({ - selector: 'worklenz-projects-folder-view', - templateUrl: './projects-folder-view.component.html', - styleUrls: ['./projects-folder-view.component.scss'] -}) -export class ProjectsFolderViewComponent { - -} diff --git a/worklenz-frontend/src/app/administrator/projects/projects/projects-list-view/projects-list-view.component.html b/worklenz-frontend/src/app/administrator/projects/projects/projects-list-view/projects-list-view.component.html deleted file mode 100644 index f60b4021..00000000 --- a/worklenz-frontend/src/app/administrator/projects/projects/projects-list-view/projects-list-view.component.html +++ /dev/null @@ -1 +0,0 @@ -

projects-list-view works!

diff --git a/worklenz-frontend/src/app/administrator/projects/projects/projects-list-view/projects-list-view.component.scss b/worklenz-frontend/src/app/administrator/projects/projects/projects-list-view/projects-list-view.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/projects/projects/projects-list-view/projects-list-view.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/projects/projects-list-view/projects-list-view.component.spec.ts deleted file mode 100644 index a08b5286..00000000 --- a/worklenz-frontend/src/app/administrator/projects/projects/projects-list-view/projects-list-view.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ProjectsListViewComponent} from './projects-list-view.component'; - -describe('ProjectsListViewComponent', () => { - let component: ProjectsListViewComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ProjectsListViewComponent] - }); - fixture = TestBed.createComponent(ProjectsListViewComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/projects/projects-list-view/projects-list-view.component.ts b/worklenz-frontend/src/app/administrator/projects/projects/projects-list-view/projects-list-view.component.ts deleted file mode 100644 index 11a2a4b9..00000000 --- a/worklenz-frontend/src/app/administrator/projects/projects/projects-list-view/projects-list-view.component.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {Component} from '@angular/core'; - -@Component({ - selector: 'worklenz-projects-list-view', - templateUrl: './projects-list-view.component.html', - styleUrls: ['./projects-list-view.component.scss'] -}) -export class ProjectsListViewComponent { - -} diff --git a/worklenz-frontend/src/app/administrator/projects/projects/projects.component.html b/worklenz-frontend/src/app/administrator/projects/projects/projects.component.html deleted file mode 100644 index 2eba60f4..00000000 --- a/worklenz-frontend/src/app/administrator/projects/projects/projects.component.html +++ /dev/null @@ -1,276 +0,0 @@ -
- - {{ total || 0 }} Project{{total > 1 ? 's' : ''}} - - - - - - - - - - - - - - - - - - - - - -
- - - - - - -
- - - - - - - - - - -
    -
  • - - Create from template -
  • -
-
- - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
- - - - - - - Name - Client - - Category - - - - - - Status - - - - - Tasks Progress - Last Updated - Members - - - - - - -
- - - - - - - {{ data.name }} - - - - - -
Start date : {{data.start_date | date: 'mediumDate'}}
-
End date : {{data.end_date | date: 'mediumDate'}}
-
-
-
-
- - {{ data.client_name || "-" }} - - - {{data.category_name}} - - - - - - - {{data.status}} - - - - - - {{data.updated_at_string}} - - - - - N/A - - - - - - - -
- - - - -
- - - -
-
-
-
- - - - - - - - - -
-
- -
-
- The team currently has no active projects. - You're yet to mark any projects as favorites. - The selected team has no archived projects. -
-
-
- - -
    - -
  • {{item.name}}
  • -
    -
  • No categories found
  • -
-
- - -
    - -
  • {{item.name}}
  • -
    -
  • No categories found
  • -
-
diff --git a/worklenz-frontend/src/app/administrator/projects/projects/projects.component.scss b/worklenz-frontend/src/app/administrator/projects/projects/projects.component.scss deleted file mode 100644 index dbb0aaf6..00000000 --- a/worklenz-frontend/src/app/administrator/projects/projects/projects.component.scss +++ /dev/null @@ -1,21 +0,0 @@ -.actions-row { - td { - padding: 5px 8px !important; - } -} - -.project-name-col { - //height: 34px; - display: flex; - align-items: center; -} - -.no-data-img-holder { - width: 100px; -} - -.category-tag { - &:hover { - text-decoration: underline; - } -} diff --git a/worklenz-frontend/src/app/administrator/projects/projects/projects.component.spec.ts b/worklenz-frontend/src/app/administrator/projects/projects/projects.component.spec.ts deleted file mode 100644 index 99435829..00000000 --- a/worklenz-frontend/src/app/administrator/projects/projects/projects.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ProjectsComponent} from './projects.component'; - -describe('ProjectsComponent', () => { - let component: ProjectsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ProjectsComponent] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(ProjectsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/projects/projects/projects.component.ts b/worklenz-frontend/src/app/administrator/projects/projects/projects.component.ts deleted file mode 100644 index 72d98868..00000000 --- a/worklenz-frontend/src/app/administrator/projects/projects/projects.component.ts +++ /dev/null @@ -1,353 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, TemplateRef, ViewChild} from '@angular/core'; -import {FormBuilder, FormGroup,} from '@angular/forms'; - -import {AppService} from '@services/app.service'; -import {ProjectsApiService} from '@api/projects-api.service'; -import {IProjectsViewModel} from "@interfaces/api-models/projects-view-model"; -import {NzTableQueryParams} from "ng-zorro-antd/table"; -import {AuthService} from "@services/auth.service"; -import {ActivatedRoute, Router} from "@angular/router"; -import {DEFAULT_PAGE_SIZE} from "@shared/constants"; -import {IProject} from "@interfaces/project"; -import {ProjectFormModalComponent} from "../../components/project-form-modal/project-form-modal.component"; -import {UtilsService} from "@services/utils.service"; -import {log_error} from "@shared/utils"; -import {IProjectViewModel} from "@interfaces/api-models/project-view-model"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {NzSegmentedOption} from "ng-zorro-antd/segmented"; -import {ProjectsFolderFormDrawerComponent} from "./projects-folder-form-drawer/projects-folder-form-drawer.component"; -import {ProjectCategoriesApiService} from "@api/project-categories-api.service"; -import {IProjectCategoryViewModel} from "@interfaces/project-category"; -import {TaskListV2Service} from "../../modules/task-list-v2/task-list-v2.service"; -import { - ProjectTemplateImportDrawerComponent -} from "@admin/components/project-template-import-drawer/project-template-import-drawer.component"; -import {ProjectTemplateApiService} from "@api/project-template-api.service"; -import {IProjectStatus} from "@interfaces/project-status"; -import {ProjectStatusesApiService} from "@api/project-statuses-api.service"; - -@Component({ - selector: 'worklenz-projects', - templateUrl: './projects.component.html', - styleUrls: ['./projects.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectsComponent implements OnInit { - @ViewChild(ProjectFormModalComponent) projectsForm!: ProjectFormModalComponent; - @ViewChild(ProjectsFolderFormDrawerComponent) folderForm!: ProjectsFolderFormDrawerComponent; - @ViewChild('displayModeTemplate', {static: true, read: TemplateRef}) displayModeSegments!: TemplateRef<{ - $implicit: NzSegmentedOption; - index: number; - }>; - @ViewChild(ProjectTemplateImportDrawerComponent) projectTemplateDrawer!: ProjectTemplateImportDrawerComponent; - - private readonly FILTER_INDEX_KEY = "worklenz.projects.filter_index"; - private readonly DISPLAY_MODE_KEY = "worklenz.projects.display_as"; - - searchForm!: FormGroup; - - projectsModel: IProjectsViewModel = {}; - categories: IProjectCategoryViewModel[] = []; - statuses: IProjectStatus[] = []; - - expandSet = new Set(); - - // Table sorting & pagination - total = 0; - pageSize = DEFAULT_PAGE_SIZE; - pageIndex = 1; - paginationSizes = [5, 10, 15, 20, 50, 100]; - sortField: string | null = null; - sortOrder: string | null = null; - categoriesFilterString: string | null = null; - statusesFilterString: string | null = null; - - loading = true; - loadingCategories = false; - loadingStatuses = false; - showCategoriesFilter = false; - showStatusesFilter = false; - filteredByCategory = false; - filteredByStatus = false; - - readonly filters = [ - 'All', - 'Favorites', - 'Archived', - ]; - - get filterIndex() { - return +(localStorage.getItem(this.FILTER_INDEX_KEY) || 0); - } - - set filterIndex(index: number) { - localStorage.setItem(this.FILTER_INDEX_KEY, index.toString()); - } - - constructor( - private readonly app: AppService, - private readonly api: ProjectsApiService, - private readonly fb: FormBuilder, - public readonly auth: AuthService, - private readonly router: Router, - private readonly route: ActivatedRoute, - private readonly utilsService: UtilsService, - private readonly categoriesApi: ProjectCategoriesApiService, - private readonly templatesApi: ProjectTemplateApiService, - private readonly cdr: ChangeDetectorRef, - private readonly list: TaskListV2Service, - private readonly statusesApi: ProjectStatusesApiService - ) { - this.app.setTitle('Projects'); - - this.pageSize = +(this.route.snapshot.queryParamMap.get("size") || DEFAULT_PAGE_SIZE); - this.pageIndex = +(this.route.snapshot.queryParamMap.get("index") || 1); - } - - async ngOnInit() { - this.searchForm = this.fb.group({ - search: [], - }); - - this.searchForm.valueChanges.subscribe(() => { - this.searchProjects(); - }); - - void this.getCategories(); - void this.getStatuses(); - } - - private getFilterIndexText() { - return this.filters[this.filterIndex]; - } - - refresh() { - void this.getCategories(); - void this.getProjects(); - } - - async import() { - await this.templatesApi.createTemplates(); - } - - async getCategories() { - try { - this.loadingCategories = true; - const res = await this.categoriesApi.get(); - if (res.done) { - this.categories = res.body; - } - - this.loadingCategories = false; - } catch (e) { - this.loadingCategories = false; - } - - this.cdr.markForCheck(); - } - - async getStatuses() { - try { - this.loadingStatuses = true; - const res = await this.statusesApi.get(); - if (res.done) { - this.statuses = res.body; - } - - this.loadingStatuses = false; - } catch (e) { - this.loadingStatuses = false; - } - - this.cdr.markForCheck(); - } - - async getProjects(ignoreLoading = false) { - try { - if (!ignoreLoading) - this.loading = true; - const res = await this.api.getByConfig({ - index: this.pageIndex, - size: this.pageSize, - field: this.sortField, - order: this.sortOrder, - search: this.searchForm.value.search, - filter: this.filterIndex.toString(), - categories: this.categoriesFilterString, - statuses: this.statusesFilterString - }); - if (res.done) { - this.handleProjectsResponse(res); - } - this.loading = false; - } catch (e) { - this.loading = false; - log_error(e); - } - - this.cdr.markForCheck(); - } - - private handleProjectsResponse(res: IServerResponse) { - this.projectsModel = res.body; - this.total = this.projectsModel.total || 0; - - this.utilsService.handleLastIndex(this.total, this.projectsModel.data?.length || 0, this.pageIndex, - index => { - this.pageIndex = index; - this.getProjects(); - }); - } - - async searchProjects() { - await this.getProjects(); - } - - isOwnerOrAdmin() { - return this.auth.isOwnerOrAdmin(); - } - - openProjectForm(id?: string) { - this.projectsForm?.open(id, !!id); - } - - selectProject(id: string | undefined, view: string) { - if (!id) return; - let viewTab = 'tasks-list'; - switch (view) { - case 'TASK_LIST': - viewTab = 'tasks-list'; - break; - case 'BOARD': - viewTab = 'board'; - break; - default : - viewTab = 'tasks-list'; - } - void this.router.navigate( - [`/worklenz/projects/${id}`], - { - queryParams: {tab: `${viewTab}`, pinned_tab: `${viewTab}` } - }); - - } - - getTaskProgressTitle(data: IProjectViewModel) { - if (!data.all_tasks_count) - return 'No tasks available.'; - if (data.all_tasks_count == data.completed_tasks_count) - return 'All tasks completed.'; - return `${data.completed_tasks_count || 0}/${data.all_tasks_count || 0} tasks completed.`; - } - - async toggleFavorite(id?: string) { - if (!id) return; - try { - const res = await this.api.toggleFavorite(id); - if (this.filterIndex == 1 && res.done) { - void this.getProjects(true); - } - } catch (e) { - log_error(e); - } - } - - async toggleArchive(project?: IProjectViewModel) { - if (!project || !project.id) return; - if (project.loading) return; - try { - project.loading = true; - - if (this.isOwnerOrAdmin()) { - const res = await this.api.toggleArchiveAll(project.id); - if (res.done) { - await this.getProjects(true); - } - } else { - const res = await this.api.toggleArchive(project.id); - if (res.done) { - await this.getProjects(true); - } - } - - project.loading = false; - } catch (e) { - project.loading = false; - log_error(e); - } - } - - onFilterChange(index: number) { - if (this.loading) return; - this.filterIndex = index; - this.getProjects(); - } - - trackBy(index: number, item: IProjectViewModel) { - return item.id; - } - - onProjectCreate(project: IProject | null) { - if (!project?.id) return; - this.list.setCurrentGroup(this.list.GROUP_BY_OPTIONS[0]); - this.selectProject(project.id, "TASK_LIST"); - } - - onQueryParamsChange(params: NzTableQueryParams) { - const {pageSize, pageIndex, sort} = params; - this.pageIndex = pageIndex; - this.pageSize = pageSize; - - const currentSort = sort.find(item => item.value !== null); - - this.sortField = currentSort?.key ?? null; - this.sortOrder = currentSort?.value ?? null; - - void this.getProjects(); - } - - filterByCategory(categoryId: string | undefined) { - if (!categoryId) return; - const category = this.categories.find(c => c.id === categoryId); - if (category) { - category.selected = true; - this.onCategoryFilterChange(); - } - } - - filterByStatus(statusId: string | undefined) { - if (!statusId) return; - const status = this.statuses.find(c => c.id === statusId); - if (status) { - status.selected = true; - this.onStatusFilterChange(); - } - } - - onCategoryFilterChange() { - const categories = this.categories.filter(c => c.selected); - this.filteredByCategory = !!categories.length; - const filterString = categories.map(c => c.id).join("+"); - this.categoriesFilterString = filterString || null; - void this.getProjects(); - } - - onStatusFilterChange() { - const statuses = this.statuses.filter(c => c.selected); - this.filteredByStatus = !!statuses.length; - const filterString = statuses.map(s => s.id).join("+"); - this.statusesFilterString = filterString || null; - void this.getProjects(); - } - - onProjectUpdated() { - void this.getProjects(); - void this.getCategories(); - } - - openTemplateSelector() { - this.projectTemplateDrawer.open(); - this.cdr.markForCheck(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/components/estimated-vs-actual-chart/estimated-vs-actual-chart.component.html b/worklenz-frontend/src/app/administrator/reporting/components/estimated-vs-actual-chart/estimated-vs-actual-chart.component.html deleted file mode 100644 index 6ed30e03..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/estimated-vs-actual-chart/estimated-vs-actual-chart.component.html +++ /dev/null @@ -1,13 +0,0 @@ -
- - - Estimated :{{estimatedTimeString}} - Actual :{{actualTimeString}} -
diff --git a/worklenz-frontend/src/app/administrator/reporting/components/estimated-vs-actual-chart/estimated-vs-actual-chart.component.scss b/worklenz-frontend/src/app/administrator/reporting/components/estimated-vs-actual-chart/estimated-vs-actual-chart.component.scss deleted file mode 100644 index cff807d2..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/estimated-vs-actual-chart/estimated-vs-actual-chart.component.scss +++ /dev/null @@ -1,19 +0,0 @@ -.estimated-time-span { - position: absolute; - top: 3px; - left: 4px; - font-size: 12px; - font-weight: 500; - width: 100%; - color:hsla(0, 0%, 0%, 0.85) !important; -} - -.actual-time-span { - position: absolute; - bottom: 1px; - left: 4px; - font-size: 12px; - font-weight: 500; - width: 100%; - color:hsla(0, 0%, 0%, 0.85) !important; -} diff --git a/worklenz-frontend/src/app/administrator/reporting/components/estimated-vs-actual-chart/estimated-vs-actual-chart.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/components/estimated-vs-actual-chart/estimated-vs-actual-chart.component.spec.ts deleted file mode 100644 index 518419fa..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/estimated-vs-actual-chart/estimated-vs-actual-chart.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { EstimatedVsActualChartComponent } from './estimated-vs-actual-chart.component'; - -describe('EstimatedVsActualChartComponent', () => { - let component: EstimatedVsActualChartComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [EstimatedVsActualChartComponent] - }); - fixture = TestBed.createComponent(EstimatedVsActualChartComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/components/estimated-vs-actual-chart/estimated-vs-actual-chart.component.ts b/worklenz-frontend/src/app/administrator/reporting/components/estimated-vs-actual-chart/estimated-vs-actual-chart.component.ts deleted file mode 100644 index 1893c8e5..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/estimated-vs-actual-chart/estimated-vs-actual-chart.component.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { - AfterViewInit, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Input, - OnChanges, - SimpleChanges, - ViewChild -} from '@angular/core'; -import {BaseChartDirective} from "ng2-charts"; -import {ChartConfiguration} from "chart.js"; - -@Component({ - selector: 'worklenz-estimated-vs-actual-chart-common', - templateUrl: './estimated-vs-actual-chart.component.html', - styleUrls: ['./estimated-vs-actual-chart.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class EstimatedVsActualChartComponent implements OnChanges, AfterViewInit { - @ViewChild(BaseChartDirective) barChart: BaseChartDirective | undefined; - - @Input({required: true}) actualTime: number | null = 0; - @Input({required: true}) estimatedTime: number | null = 0; - @Input() estimatedTimeString: string | null = null; - @Input() actualTimeString: string | null = null; - - visible = false; - - public barChartLegend = false; - public barChartPlugins = []; - - public barChartData: ChartConfiguration<'bar'>['data'] = { - labels: [''], - datasets: [] - }; - - public barChartOptions: ChartConfiguration<'bar'>['options'] = { - plugins: { - datalabels: { - display: false - } - }, - responsive: false, - maintainAspectRatio: false, - indexAxis: "y", - scales: { - x: { - grid: { - display: false, - }, - display: false - }, - y: { - grid: { - display: false - }, - display: false - }, - } - }; - - constructor( - private readonly cdr: ChangeDetectorRef - ) {} - - ngOnChanges(changes: SimpleChanges) { - setTimeout(() => { - this.barChart?.update(); - this.cdr.markForCheck(); - }, 1000) - } - - ngAfterViewInit() { - this.visible = true; - setTimeout(() => { - this.barChart?.data?.datasets.push( - {data: [this.estimatedTime], label: '', backgroundColor: ['#A5AAD9']}, - {data: [this.actualTime], label: '', backgroundColor: ['#c191cc']} - ); - this.barChart?.update(); - this.cdr.markForCheck(); - }, 1000) - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/components/member-logs-breakdown/member-logs-breakdown.component.html b/worklenz-frontend/src/app/administrator/reporting/components/member-logs-breakdown/member-logs-breakdown.component.html deleted file mode 100644 index 67fa2385..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/member-logs-breakdown/member-logs-breakdown.component.html +++ /dev/null @@ -1 +0,0 @@ -

member-logs-breakdown works!

diff --git a/worklenz-frontend/src/app/administrator/reporting/components/member-logs-breakdown/member-logs-breakdown.component.scss b/worklenz-frontend/src/app/administrator/reporting/components/member-logs-breakdown/member-logs-breakdown.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/reporting/components/member-logs-breakdown/member-logs-breakdown.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/components/member-logs-breakdown/member-logs-breakdown.component.spec.ts deleted file mode 100644 index 162829a6..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/member-logs-breakdown/member-logs-breakdown.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { MemberLogsBreakdownComponent } from './member-logs-breakdown.component'; - -describe('MemberLogsBreakdownComponent', () => { - let component: MemberLogsBreakdownComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [MemberLogsBreakdownComponent] - }); - fixture = TestBed.createComponent(MemberLogsBreakdownComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/components/member-logs-breakdown/member-logs-breakdown.component.ts b/worklenz-frontend/src/app/administrator/reporting/components/member-logs-breakdown/member-logs-breakdown.component.ts deleted file mode 100644 index 65e30fce..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/member-logs-breakdown/member-logs-breakdown.component.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'worklenz-member-logs-breakdown', - templateUrl: './member-logs-breakdown.component.html', - styleUrls: ['./member-logs-breakdown.component.scss'] -}) -export class MemberLogsBreakdownComponent { - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/components/project-category/project-category.component.html b/worklenz-frontend/src/app/administrator/reporting/components/project-category/project-category.component.html deleted file mode 100644 index b3f341a5..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/project-category/project-category.component.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - {{ project?.category_name }} - - - Set Category - - - - - - - - -
    -
  • -
    - -
    - - Hit enter to create! - -
  • -
  • - -
  • -
-
- - diff --git a/worklenz-frontend/src/app/administrator/reporting/components/project-category/project-category.component.scss b/worklenz-frontend/src/app/administrator/reporting/components/project-category/project-category.component.scss deleted file mode 100644 index feb87b4c..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/project-category/project-category.component.scss +++ /dev/null @@ -1,43 +0,0 @@ -nz-tag { - font-size: 13px; - - &:hover { - & .clear-btn { - opacity: 1; - } - } -} - -input { - min-width: 225px; - max-width: 225px; -} - -.clear-btn { - max-width: 14px; - position: absolute; - right: 6px; - top: 0; - bottom: 0; - margin-top: auto; - margin-bottom: auto; - line-height: 14px; - max-height: 14px; - background: #ededed; - padding: 0; - border-radius: 100%; - font-size: 14px; - opacity: 0; - transition: opacity 0.3s; - - & span { - color: #959595; - margin-top: -2px; - } -} -.ellipsis { - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} diff --git a/worklenz-frontend/src/app/administrator/reporting/components/project-category/project-category.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/components/project-category/project-category.component.spec.ts deleted file mode 100644 index abf6c095..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/project-category/project-category.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ProjectCategoryComponent } from './project-category.component'; - -describe('ProjectCategoryComponent', () => { - let component: ProjectCategoryComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ProjectCategoryComponent] - }); - fixture = TestBed.createComponent(ProjectCategoryComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/components/project-category/project-category.component.ts b/worklenz-frontend/src/app/administrator/reporting/components/project-category/project-category.component.ts deleted file mode 100644 index e9c96880..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/project-category/project-category.component.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - Input, - NgZone, - OnDestroy, - OnInit, - ViewChild -} from '@angular/core'; -import {IProjectCategory} from "@interfaces/project-category"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {FormBuilder, FormGroup} from "@angular/forms"; -import {log_error} from "@shared/utils"; -import {ProjectCategoriesApiService} from "@api/project-categories-api.service"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {AuthService} from "@services/auth.service"; -import {IRPTProject} from "../../interfaces"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; - -@Component({ - selector: 'worklenz-project-category', - templateUrl: './project-category.component.html', - styleUrls: ['./project-category.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectCategoryComponent implements OnInit, OnDestroy { - @ViewChild('searchInput', {static: false}) searchInput!: ElementRef; - @Input({required: true}) teamId: string | null = null; - @Input({required: true}) project: IRPTProject | null = null; - - projCategories: IProjectCategory[] = []; - private session: ILocalSession | null = null; - - categorySearchText = null; - - showText = false; - show = false; - loadingCategories = true; - - form!: FormGroup; - - get hasFilteredCategories() { - return !!this.filteredCategories.length; - } - - get filteredCategories() { - return this.searchPipe.transform(this.projCategories, this.categorySearchText); - } - - constructor( - private readonly ngZone: NgZone, - private readonly fb: FormBuilder, - private readonly categoriesApi: ProjectCategoriesApiService, - private readonly cdr: ChangeDetectorRef, - private readonly socket: Socket, - private readonly auth: AuthService, - private readonly searchPipe: SearchByNamePipe, - ) { - this.form = this.fb.group({ - category: [], - }); - this.session = this.auth.getCurrentSession(); - } - - ngOnInit() { - this.socket.on(SocketEvents.PROJECT_CATEGORY_CHANGE.toString(), this.handleResponse); - this.socket.on(SocketEvents.CREATE_PROJECT_CATEGORY.toString(), this.handleLabelsCreate); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.PROJECT_CATEGORY_CHANGE.toString(), this.handleResponse); - this.socket.removeListener(SocketEvents.CREATE_PROJECT_CATEGORY.toString(), this.handleLabelsCreate); - } - - trackById(index: number, item: ITaskStatusViewModel) { - return item.id; - } - - async getProjectCategories() { - if (!this.teamId) return; - try { - this.loadingCategories = true; - const res = await this.categoriesApi.getByTeamId(this.teamId); - if (res.done) { - this.projCategories = res.body; - } - this.loadingCategories = false; - this.cdr.markForCheck(); - } catch (e) { - log_error(e); - this.loadingCategories = false; - this.cdr.markForCheck(); - } - } - - create() { - if (!this.teamId || this.hasFilteredCategories || !this.categorySearchText) return; - this.socket.emit(SocketEvents.CREATE_PROJECT_CATEGORY.toString(), JSON.stringify({ - project_id: this.project?.id, - name: this.categorySearchText, - team_id: this.teamId, - user_id: this.session?.id - })); - } - - update(categoryId?: string) { - if (!categoryId) return; - this.socket.emit(SocketEvents.PROJECT_CATEGORY_CHANGE.toString(), JSON.stringify({ - project_id: this.project?.id, - category_id: categoryId, - is_update: true - })); - this.cdr.markForCheck(); - } - - remove() { - this.socket.emit(SocketEvents.PROJECT_CATEGORY_CHANGE.toString(), JSON.stringify({ - project_id: this.project?.id, - category_id: null, - is_update: false - })); - this.cdr.markForCheck(); - } - - handleLabelsVisibleChange(visible: boolean) { - if (visible) { - this.show = true; - this.getProjectCategories(); - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.searchInput?.nativeElement?.focus(); - }, 100); - }); - } else { - this.reset(); - } - } - - private handleResponse = (response: { - id: string; - category_id: string | null; - category_name: string; - category_color: string - }) => { - if (response && this.project && response.id === this.project.id) { - this.project.category_id = response.category_id; - this.project.category_name = response.category_name; - this.project.category_color = response.category_color; - this.reset(); - this.cdr.markForCheck(); - } - } - - handleLabelsCreate = (response: { - id: string; - category_id: string | null; - category_name: string; - category_color: string - }) => { - if (this.project && response.id === this.project.id && this.teamId && response && response.category_id) { - this.projCategories.push({ - id: response.category_id, - name: response.category_name, - color_code: response.category_color, - team_id: this.teamId - }); - this.update(response.category_id as string); - this.cdr.markForCheck(); - } - } - - - reset() { - this.searchInput?.nativeElement?.blur(); - this.categorySearchText = null; - this.show = false; - this.projCategories = []; - this.showText = false; - this.form.controls["category"].setValue(null); - this.cdr.markForCheck(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/components/project-health/project-health.component.html b/worklenz-frontend/src/app/administrator/reporting/components/project-health/project-health.component.html deleted file mode 100644 index ced08c92..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/project-health/project-health.component.html +++ /dev/null @@ -1,11 +0,0 @@ -
- - - - - -
diff --git a/worklenz-frontend/src/app/administrator/reporting/components/project-health/project-health.component.scss b/worklenz-frontend/src/app/administrator/reporting/components/project-health/project-health.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/reporting/components/project-health/project-health.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/components/project-health/project-health.component.spec.ts deleted file mode 100644 index e877a957..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/project-health/project-health.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ProjectHealthComponent } from './project-health.component'; - -describe('ProjectHealthComponent', () => { - let component: ProjectHealthComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ProjectHealthComponent] - }); - fixture = TestBed.createComponent(ProjectHealthComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/components/project-health/project-health.component.ts b/worklenz-frontend/src/app/administrator/reporting/components/project-health/project-health.component.ts deleted file mode 100644 index 480cfac0..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/project-health/project-health.component.ts +++ /dev/null @@ -1,56 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {IProjectHealth} from "@interfaces/project-health"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {IRPTProject} from "../../interfaces"; - -@Component({ - selector: 'worklenz-project-health-selector', - templateUrl: './project-health.component.html', - styleUrls: ['./project-health.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectHealthComponent implements OnInit, OnDestroy { - @Input({required: true}) projHealths: IProjectHealth[] = []; - @Input({required: true}) project: IRPTProject| null = null; - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef - ) { - } - - ngOnInit() { - this.socket.on(SocketEvents.PROJECT_HEALTH_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.PROJECT_HEALTH_CHANGE.toString(), this.handleResponse); - } - - trackById(index: number, item: ITaskStatusViewModel) { - return item.id; - } - - handleHealthChange(healthId: string) { - if (!this.project?.id) return; - this.socket.emit(SocketEvents.PROJECT_HEALTH_CHANGE.toString(), JSON.stringify({ - project_id: this.project?.id, - health_id: healthId - })); - this.cdr.markForCheck(); - } - - private handleResponse = (response: { - health_id: string; - id: string; - color_code: string; - }) => { - if (response && this.project && response.id === this.project.id) { - this.project.health_color = response.color_code; - this.project.project_health = response.health_id; - this.cdr.markForCheck(); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/components/project-logs-breakdown/project-logs-breakdown.component.html b/worklenz-frontend/src/app/administrator/reporting/components/project-logs-breakdown/project-logs-breakdown.component.html deleted file mode 100644 index ff3e7012..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/project-logs-breakdown/project-logs-breakdown.component.html +++ /dev/null @@ -1,100 +0,0 @@ - - - -
- -
- -
-
- -
- No time logs to show. -
-
- -
- -
- - - - {{log_item.user_name}} logged {{log_item.time_spent_string}} for {{log_item.task_name}} - {{log_item.task_key}} - - - -
- No time logs to show. -
-
-
-
- -
- {{log.log_day | date : 'MMM dd, YYYY'}} -
-
-
- -
-
-
- - -
-
-
{{project?.name}}
-
-
-
- -
-
-
-
- -
- - - -
    -
  • - {{item.label}} {{item.dates}} -
  • -
  • -
  • -
    - -
    - -
    - -
    -
  • -
-
diff --git a/worklenz-frontend/src/app/administrator/reporting/components/project-logs-breakdown/project-logs-breakdown.component.scss b/worklenz-frontend/src/app/administrator/reporting/components/project-logs-breakdown/project-logs-breakdown.component.scss deleted file mode 100644 index c964c202..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/project-logs-breakdown/project-logs-breakdown.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -.no-data-img-holder { - max-width: 64px; -} diff --git a/worklenz-frontend/src/app/administrator/reporting/components/project-logs-breakdown/project-logs-breakdown.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/components/project-logs-breakdown/project-logs-breakdown.component.spec.ts deleted file mode 100644 index 1e08d1c1..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/project-logs-breakdown/project-logs-breakdown.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ProjectLogsBreakdownComponent } from './project-logs-breakdown.component'; - -describe('ProjectLogsBreakdownComponent', () => { - let component: ProjectLogsBreakdownComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ProjectLogsBreakdownComponent] - }); - fixture = TestBed.createComponent(ProjectLogsBreakdownComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/components/project-logs-breakdown/project-logs-breakdown.component.ts b/worklenz-frontend/src/app/administrator/reporting/components/project-logs-breakdown/project-logs-breakdown.component.ts deleted file mode 100644 index 050e846e..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/project-logs-breakdown/project-logs-breakdown.component.ts +++ /dev/null @@ -1,208 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output} from '@angular/core'; -import {log_error} from "@shared/utils"; -import {ReportingApiService} from "../../reporting-api.service"; -import {IProjectLogsBreakdown, IRPTDuration, IRPTTimeProject, ITimeLogBreakdownReq} from "../../interfaces"; -import { - ALL_TIME, - AvatarNamesMap, - LAST_MONTH, - LAST_QUARTER, - LAST_WEEK, - PREV_MONTH, - PREV_WEEK, - YESTERDAY -} from "@shared/constants"; -import moment from "moment"; -import {ReportingService} from "../../reporting.service"; -import {ReportingExportApiService} from "@api/reporting-export-api.service"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-project-logs-breakdown', - templateUrl: './project-logs-breakdown.component.html', - styleUrls: ['./project-logs-breakdown.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectLogsBreakdownComponent { - @Input() show: boolean = false; - @Input() project: IRPTTimeProject | null = null; - @Input() isDurationLabelSelected = true; - - @Output() onClose: EventEmitter = new EventEmitter(); - - loading = true; - exporting = false; - isDrawerDurationLabelSelected = false; - - dateRange: string[] = []; - durations: IRPTDuration[] = [ - {label: "Yesterday", key: YESTERDAY, dates: moment().subtract(1, "days").format('MMM,DD YYYY').toString()}, - {label: "Last 7 days", key: LAST_WEEK, dates: moment().subtract(1, "weeks").format('MMM,DD YYYY').toString() + " - " + moment().format('MMM,DD YYYY').toString()}, - {label: "Last week", key: PREV_WEEK, dates: moment().subtract(1, "weeks").startOf("week").format('MMM,DD YYYY').toString() + " - " + moment().subtract(1, "weeks").endOf("week").format('MMM,DD YYYY').toString()}, - {label: "Last 30 days", key: LAST_MONTH, dates: moment().subtract(1, "month").format('MMM,DD YYYY').toString() + " - " + moment().format('MMM,DD YYYY').toString()}, - {label: "Last month", key: PREV_MONTH, dates: moment().subtract(1, "month").startOf("month").format('MMM,DD YYYY').toString() + " - " + moment().subtract(1, "month").endOf("month").format('MMM,DD YYYY').toString()}, - {label: "Last 3 months", key: LAST_QUARTER, dates: moment().subtract(3, "months").format('MMM,DD YYYY').toString() + " - " + moment().format('MMM,DD YYYY').toString()}, - {label: "All time", key: ALL_TIME, dates: ''} - ]; - selectedDuration: IRPTDuration | null = null; - - timelogsBreakDown: IProjectLogsBreakdown[] | null = null; - - getColor(name?: string) { - return AvatarNamesMap[name?.charAt(0).toUpperCase() || 'A']; - } - - get durationLabel() { - const f = "yy-MM-DD"; - if(this.isDrawerDurationLabelSelected) { - return this.selectedDuration ? this.selectedDuration.label : "Duration"; - } - - if (this.dateRange.length == 2) { - return `${moment(this.dateRange[0]).format(f)} - ${moment(this.dateRange[1]).format(f)}`; - } - - return this.selectedDuration ? this.selectedDuration.label : "Duration"; - } - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: ReportingApiService, - private readonly reportingService: ReportingService, - private readonly reportingExportApi: ReportingExportApiService, - private readonly auth: AuthService - ) { - } - - handleCancel() { - this.show = false; - this.project = null; - this.onClose.emit(); - } - - private async setDatesForKeys() { - if(this.selectedDuration?.key) { - const key = this.selectedDuration?.key; - const today = moment(); - - switch (key) { - case YESTERDAY: - const yesterday = moment().subtract(1, "days"); - this.dateRange = ([yesterday.toString(), yesterday.toString()]); - break; - case LAST_WEEK: - const lastWeekStart = moment().subtract(1, "weeks"); - this.dateRange = ([lastWeekStart.toString(), today.toString()]); - break; - case LAST_MONTH: - const lastMonthStart = moment().subtract(1, "months"); - this.dateRange = ([lastMonthStart.toString(), today.toString()]); - break; - case LAST_QUARTER: - const lastQuaterStart = moment().subtract(3, "months"); - this.dateRange = ([lastQuaterStart.toString(), today.toString()]); - break; - case PREV_WEEK: - const prevWeekStart = moment().subtract(1, "weeks").startOf("week"); - const prevWeekEnd = moment().subtract(1, "weeks").endOf("week"); - this.dateRange = ([prevWeekStart.toString(), prevWeekEnd.toString()]); - break; - case PREV_MONTH: - const prevMonthStart = moment().subtract(1, "month").startOf("month"); - const prevMonthEnd = moment().subtract(1, "month").endOf("month"); - this.dateRange = ([prevMonthStart.toString(), prevMonthEnd.toString()]); - break; - case ALL_TIME: - this.dateRange = []; - } - } - } - - async get() { - if (!this.project) return - try { - this.loading = true; - if(this.isDrawerDurationLabelSelected) { - await this.setDatesForKeys(); - } - const body: ITimeLogBreakdownReq = { - id: this.project.id, - duration: this.selectedDuration ? this.selectedDuration.key : this.durations[1].key, - date_range: this.dateRange, - time_zone: this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name as string : Intl.DateTimeFormat().resolvedOptions().timeZone as string - } - const res = await this.api.getProjectTimeLogs(body); - if (res.done) { - - res.body.sort((a, b) => { - const dateA = new Date(a.log_day); - const dateB = new Date(b.log_day); - return dateB.getTime() - dateA.getTime(); - }); - - this.timelogsBreakDown = res.body; - } - this.loading = false; - if(this.isDrawerDurationLabelSelected) this.dateRange = []; - this.cdr.markForCheck(); - } catch (e) { - log_error(e); - this.loading = false; - } - } - - async exportExcel() { - if (!this.project) return - try { - this.exporting = false; - if(this.isDrawerDurationLabelSelected) { - await this.setDatesForKeys(); - } - const body: ITimeLogBreakdownReq = { - id: this.project.id, - duration: this.selectedDuration ? this.selectedDuration.key : this.durations[1].key, - date_range: this.dateRange, - time_zone: this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name as string : Intl.DateTimeFormat().resolvedOptions().timeZone as string - } - void this.reportingExportApi.exportProjectTimeLogs(body, this.project.name); - this.exporting = false; - } catch (e) { - this.exporting = false; - log_error(e); - } - } - - onVisibilityChange(visible: boolean) { - if (visible) { - const globalDuration = this.reportingService.getDuration()?.key; - const globalDateRange = this.reportingService.getDateRange(); - - if (globalDateRange.length === 2 && !this.isDurationLabelSelected) { - this.dateRange = globalDateRange - } - - this.isDrawerDurationLabelSelected = this.isDurationLabelSelected; - - const selectedItem = this.durations.find(d => d.key === globalDuration); - if (selectedItem) this.selectedDuration = selectedItem; - - void this.get() - } else { - this.timelogsBreakDown = null; - this.onClose.emit(); - } - } - - onDurationChange(item: IRPTDuration) { - this.dateRange = []; - this.selectedDuration = item; - this.isDrawerDurationLabelSelected = true; - void this.get() - } - - customDateChange() { - this.isDrawerDurationLabelSelected = false; - void this.get() - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/components/project-start-end-dates/project-start-end-dates.component.html b/worklenz-frontend/src/app/administrator/reporting/components/project-start-end-dates/project-start-end-dates.component.html deleted file mode 100644 index 3a04f533..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/project-start-end-dates/project-start-end-dates.component.html +++ /dev/null @@ -1,20 +0,0 @@ - - -

-

- - -
diff --git a/worklenz-frontend/src/app/administrator/reporting/components/project-start-end-dates/project-start-end-dates.component.scss b/worklenz-frontend/src/app/administrator/reporting/components/project-start-end-dates/project-start-end-dates.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/reporting/components/project-start-end-dates/project-start-end-dates.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/components/project-start-end-dates/project-start-end-dates.component.spec.ts deleted file mode 100644 index 73601d42..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/project-start-end-dates/project-start-end-dates.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ProjectStartEndDatesComponent } from './project-start-end-dates.component'; - -describe('ProjectStartEndDatesComponent', () => { - let component: ProjectStartEndDatesComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ProjectStartEndDatesComponent] - }); - fixture = TestBed.createComponent(ProjectStartEndDatesComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/components/project-start-end-dates/project-start-end-dates.component.ts b/worklenz-frontend/src/app/administrator/reporting/components/project-start-end-dates/project-start-end-dates.component.ts deleted file mode 100644 index 8f496913..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/project-start-end-dates/project-start-end-dates.component.ts +++ /dev/null @@ -1,73 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {IRPTProject} from "../../interfaces"; - -@Component({ - selector: 'worklenz-project-start-end-dates', - templateUrl: './project-start-end-dates.component.html', - styleUrls: ['./project-start-end-dates.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectStartEndDatesComponent implements OnInit, OnDestroy { - @Input({required: true}) project: IRPTProject | null = null; - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef - ) {} - - ngOnInit() { - this.socket.on(SocketEvents.PROJECT_START_DATE_CHANGE.toString(), this.handleStartdateChangeResponse); - this.socket.on(SocketEvents.PROJECT_END_DATE_CHANGE.toString(), this.handleEnddateChangeResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.PROJECT_START_DATE_CHANGE.toString(), this.handleStartdateChangeResponse); - this.socket.removeListener(SocketEvents.PROJECT_END_DATE_CHANGE.toString(), this.handleEnddateChangeResponse); - } - - trackById(index: number, item: ITaskStatusViewModel) { - return item.id; - } - - handleStartDateChange(date: string) { - if (!this.project?.id) return; - this.socket.emit(SocketEvents.PROJECT_START_DATE_CHANGE.toString(), JSON.stringify({ - project_id: this.project?.id, - start_date: date - })); - this.cdr.markForCheck(); - } - - handleEndDateChange(date: string) { - if (!this.project?.id) return; - this.socket.emit(SocketEvents.PROJECT_END_DATE_CHANGE.toString(), JSON.stringify({ - project_id: this.project?.id, - end_date: date - })); - this.cdr.markForCheck(); - } - - private handleStartdateChangeResponse = (response: { - start_date: string; - id: string; - }) => { - if (response && this.project && response.id === this.project.id) { - this.project.start_date = response.start_date; - this.cdr.markForCheck(); - } - } - - private handleEnddateChangeResponse = (response: { - end_date: string; - id: string; - }) => { - if (response && this.project && response.id === this.project.id) { - this.project.end_date = response.end_date; - this.cdr.markForCheck(); - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/components/project-status/project-status.component.html b/worklenz-frontend/src/app/administrator/reporting/components/project-status/project-status.component.html deleted file mode 100644 index 6816acf1..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/project-status/project-status.component.html +++ /dev/null @@ -1,10 +0,0 @@ - -{{ project?.status_name }} - - - - - {{item.name}} - - diff --git a/worklenz-frontend/src/app/administrator/reporting/components/project-status/project-status.component.scss b/worklenz-frontend/src/app/administrator/reporting/components/project-status/project-status.component.scss deleted file mode 100644 index 84e21547..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/project-status/project-status.component.scss +++ /dev/null @@ -1,10 +0,0 @@ -nz-select { - color: transparent; - position: absolute; - height: 40px; - top: 2px; - bottom: 0; - left: 10px; - margin-top: auto; - margin-bottom: auto; -} diff --git a/worklenz-frontend/src/app/administrator/reporting/components/project-status/project-status.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/components/project-status/project-status.component.spec.ts deleted file mode 100644 index 244d321a..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/project-status/project-status.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ProjectStatusComponent } from './project-status.component'; - -describe('ProjectStatusComponent', () => { - let component: ProjectStatusComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ProjectStatusComponent] - }); - fixture = TestBed.createComponent(ProjectStatusComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/components/project-status/project-status.component.ts b/worklenz-frontend/src/app/administrator/reporting/components/project-status/project-status.component.ts deleted file mode 100644 index a4f5e85b..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/project-status/project-status.component.ts +++ /dev/null @@ -1,61 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {IProjectStatus} from "@interfaces/project-status"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {IRPTProject} from "../../interfaces"; - -@Component({ - selector: 'worklenz-project-status', - templateUrl: './project-status.component.html', - styleUrls: ['./project-status.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) - -export class ProjectStatusComponent implements OnInit, OnDestroy { - @Input({required: true}) projStatuses: IProjectStatus[] = []; - @Input({required: true}) project: IRPTProject | null = null; - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef - ) { - } - - ngOnInit() { - this.socket.on(SocketEvents.PROJECT_STATUS_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.PROJECT_STATUS_CHANGE.toString(), this.handleResponse); - } - - trackById(index: number, item: ITaskStatusViewModel) { - return item.id; - } - - handleStatusChange(statusId: string) { - if (!this.project?.id) return; - this.socket.emit(SocketEvents.PROJECT_STATUS_CHANGE.toString(), JSON.stringify({ - project_id: this.project?.id, - status_id: statusId - })); - this.cdr.markForCheck(); - } - - private handleResponse = (response: { - id: string; - status: string; - status_icon: string; - status_color: string; - status_name: string - }) => { - if (response && this.project && response.id === this.project.id) { - this.project.status_id = response.status; - this.project.status_icon = response.status_icon; - this.project.status_color = response.status_color; - this.project.status_name = response.status_name; - this.cdr.markForCheck(); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/components/rpt-header/rpt-header.component.html b/worklenz-frontend/src/app/administrator/reporting/components/rpt-header/rpt-header.component.html deleted file mode 100644 index 4b849158..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/rpt-header/rpt-header.component.html +++ /dev/null @@ -1,65 +0,0 @@ -
- - {{title}} - - - - - - - - - - - - - - - -
    -
  • Excel
  • -
  • PNG
  • -
-
-
-
-
-
-
-
- - -
    -
  • - {{item.label}} {{item.dates}} -
  • -
  • -
  • -
    - -
    - -
    - -
    -
  • -
-
diff --git a/worklenz-frontend/src/app/administrator/reporting/components/rpt-header/rpt-header.component.scss b/worklenz-frontend/src/app/administrator/reporting/components/rpt-header/rpt-header.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/reporting/components/rpt-header/rpt-header.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/components/rpt-header/rpt-header.component.spec.ts deleted file mode 100644 index 0bd5a23c..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/rpt-header/rpt-header.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptHeaderComponent} from './rpt-header.component'; - -describe('RptHeaderComponent', () => { - let component: RptHeaderComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptHeaderComponent] - }); - fixture = TestBed.createComponent(RptHeaderComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/components/rpt-header/rpt-header.component.ts b/worklenz-frontend/src/app/administrator/reporting/components/rpt-header/rpt-header.component.ts deleted file mode 100644 index 0432ffe0..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/rpt-header/rpt-header.component.ts +++ /dev/null @@ -1,122 +0,0 @@ -import {ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; -import {NzPageHeaderModule} from "ng-zorro-antd/page-header"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NgClass, NgForOf, NgIf} from "@angular/common"; -import {FormsModule} from "@angular/forms"; -import {NzCheckboxModule} from "ng-zorro-antd/checkbox"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {LAST_MONTH, LAST_QUARTER, LAST_WEEK, YESTERDAY, ALL_TIME, PREV_WEEK, PREV_MONTH} from "@shared/constants"; -import {IDurationChangedEmitter, IRPTDuration} from "../../interfaces"; -import {NzDatePickerModule} from "ng-zorro-antd/date-picker"; -import {NzFormModule} from "ng-zorro-antd/form"; -import moment, {duration} from "moment/moment"; -import {ReportingService} from "../../reporting.service"; - -@Component({ - selector: 'worklenz-rpt-header', - templateUrl: './rpt-header.component.html', - styleUrls: ['./rpt-header.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [ - NzPageHeaderModule, - NzIconModule, - NzSpaceModule, - NzButtonModule, - NgClass, - FormsModule, - NzCheckboxModule, - NzDropDownModule, - NgForOf, - NzDatePickerModule, - NzFormModule, - NgIf - ], - standalone: true -}) -export class RptHeaderComponent implements OnInit { - @Input({required: true}) title!: string; - @Input() showExport = true; - @Input() showDuration = true; - @Input() showArchivedToggle = true; - @Input() isPngOnly = false; - @Output() durationChanged: EventEmitter = new EventEmitter(); - @Output() exportFile = new EventEmitter(); - @Output() isDurationLabelSelected = new EventEmitter(); - - includeArchived = false; - exporting = false; - - dateRange: string[] = []; - - durations: IRPTDuration[] = [ - {label: "Yesterday", key: YESTERDAY, dates: moment().subtract(1, "days").format('MMM,DD YYYY').toString()}, - {label: "Last 7 days", key: LAST_WEEK, dates: moment().subtract(1, "weeks").format('MMM,DD YYYY').toString() + " - " + moment().format('MMM,DD YYYY').toString()}, - {label: "Last week", key: PREV_WEEK, dates: moment().subtract(1, "weeks").startOf("week").format('MMM,DD YYYY').toString() + " - " + moment().subtract(1, "weeks").endOf("week").format('MMM,DD YYYY').toString()}, - {label: "Last 30 days", key: LAST_MONTH, dates: moment().subtract(1, "month").format('MMM,DD YYYY').toString() + " - " + moment().format('MMM,DD YYYY').toString()}, - {label: "Last month", key: PREV_MONTH, dates: moment().subtract(1, "month").startOf("month").format('MMM,DD YYYY').toString() + " - " + moment().subtract(1, "month").endOf("month").format('MMM,DD YYYY').toString()}, - {label: "Last 3 months", key: LAST_QUARTER, dates: moment().subtract(3, "months").format('MMM,DD YYYY').toString() + " - " + moment().format('MMM,DD YYYY').toString()}, - {label: "All time", key: ALL_TIME, dates: ''} - ]; - - get durationLabel() { - const f = "yy-MM-DD"; - if (this.dateRange.length == 2) - return `${moment(this.dateRange[0]).format(f)} - ${moment(this.dateRange[1]).format(f)}`; - return this.selectedDuration ? this.selectedDuration.label : "Duration"; - } - - get selectedDuration() { - return this.api.getDuration(); - } - - constructor( - private api: ReportingService - ) { - } - - ngOnInit() { - this.setInitialValues(); - - } - - setInitialValues() { - if (!this.api.getDuration()) - this.api.setDuration(this.durations.find(d => d.key === LAST_WEEK) || null); - - if (this.api.getIncludeToggle()) - this.includeArchived = this.api.getIncludeToggle(); - } - - onArchiveChange(event: any) { - this.api.setIncludeToggle(event); - this.api.emitIncludeToggleChanged(); - } - - onDurationChange(item: IRPTDuration) { - this.isDurationLabelSelected.emit(true); - setTimeout(() => { - this.api.setDuration(item); - this.api.setDrawerDuration(item); - this.dateRange = []; - this.api.setDateRange(this.dateRange); - this.api.setDrawerDateRange(this.dateRange); - this.api.emitDurationChanged(); - }, 500); - } - - customDateChange() { - this.isDurationLabelSelected.emit(false); - setTimeout(() => { - this.api.setDateRange(this.dateRange); - this.api.setDrawerDateRange(this.dateRange); - this.api.emitDateRangeChanged(); - }) - } - - export() { - this.exportFile.emit(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/components/rpt-layout/rpt-layout.component.html b/worklenz-frontend/src/app/administrator/reporting/components/rpt-layout/rpt-layout.component.html deleted file mode 100644 index 0fe6a9f4..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/rpt-layout/rpt-layout.component.html +++ /dev/null @@ -1,51 +0,0 @@ -
- - -

- - - - - {{organization}} - -

-
    -
  • Overview -
  • -
  • Projects -
  • -
  • Members -
  • -
  • -
      -
    • Overview -
    • -
    • Projects -
    • -
    • Members -
    • -
    • Estimated vs Actual -
    • -
    -
  • -
-
- - - -
- -
-
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/reporting/components/rpt-layout/rpt-layout.component.scss b/worklenz-frontend/src/app/administrator/reporting/components/rpt-layout/rpt-layout.component.scss deleted file mode 100644 index cb47faaf..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/rpt-layout/rpt-layout.component.scss +++ /dev/null @@ -1,114 +0,0 @@ -nz-header { - background: white; - padding: 0; -} - -nz-sider { - height: 100vh; - position: fixed; - left: 0; - - p { - //padding-left: 20px; - //color: #00000073; - padding: 15px 10px; - text-align: center; - margin: 0; - font-weight: bold; - background: #00000008; - } -} - -nz-layout { - background: white; -} - -.trigger { - position: fixed; - top: 80px; - padding: 0; - border-radius: 100%; - background-color: white; - border: none; - font-size: 22px; - cursor: pointer; - color: #c5c5c5; - z-index: 1; - transition: 0.3s all; -} - -.reporting-page-content { - margin-left: 160px; - padding-left: 24px; - transition: 0.3s all; - - ::before { - content: ""; - position: fixed; - left: 160px; - top: 0; - bottom: 0; - width: 1px; - background: rgb(240 240 240); - transition: 0.3s all; - z-index: 0; - } - - &.full { - margin-left: 10px; - - ::before { - left: 10px; - transition: 0.3s all; - } - - .trigger { - left: 0px; - } - } - - .trigger { - left: 150px; - } -} - -.sider-ul { - padding-left: 0; - overflow-x: hidden; - border-top: 1px solid rgba(0, 0, 0, .06); - padding-top: 16px; - border-right: none; - - li { - padding-left: 20px; - height: 40px; - margin-bottom: 8px; - display: flex; - align-items: center; - - &.active { - color: #1890ff; - border-right: 2px solid #1890ff; - background-color: #e6f7ff; - } - } -} - -.inner-content { - padding-top: 26px; -} - -[nz-menu-item] { - user-select: none; -} - -.ps-26 { - padding-left: 26px !important; -} - -.ellipsis-trigger { - text-overflow: ellipsis; - &:hover { - text-overflow: clip; - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/components/rpt-layout/rpt-layout.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/components/rpt-layout/rpt-layout.component.spec.ts deleted file mode 100644 index f7f9e195..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/rpt-layout/rpt-layout.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptLayoutComponent} from './rpt-layout.component'; - -describe('RptLayoutComponent', () => { - let component: RptLayoutComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptLayoutComponent] - }); - fixture = TestBed.createComponent(RptLayoutComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/components/rpt-layout/rpt-layout.component.ts b/worklenz-frontend/src/app/administrator/reporting/components/rpt-layout/rpt-layout.component.ts deleted file mode 100644 index 1e9f4867..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/components/rpt-layout/rpt-layout.component.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - NgZone, - OnDestroy, - OnInit, - Renderer2 -} from '@angular/core'; -import {ReportingApiService} from "../../reporting-api.service"; -import {ReportingService} from "../../reporting.service"; -import {Chart} from "chart.js"; -import ChartDataLabels from "chartjs-plugin-datalabels"; - -@Component({ - selector: 'worklenz-rpt-layout', - templateUrl: './rpt-layout.component.html', - styleUrls: ['./rpt-layout.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptLayoutComponent implements OnInit, OnDestroy { - collapsed = false; - loading = false; - opened = false; - - get organization() { - return this.service.currentOrganization; - } - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly service: ReportingService, - private readonly api: ReportingApiService, - private readonly renderer: Renderer2, - private readonly ngZone: NgZone - ) { - Chart.register(ChartDataLabels); - } - - ngOnInit() { - void this.getInfo(); - this.ngZone.runOutsideAngular(() => { - const currentUrl = window.location.href; - if (currentUrl.includes('/time-sheet-')) { - this.opened = true; - } else { - this.opened = false; - } - this.renderer.addClass(document.body, "reporting"); - }); - } - - ngOnDestroy() { - this.ngZone.runOutsideAngular(() => { - this.renderer.removeClass(document.body, "reporting"); - }); - } - - async getInfo() { - try { - this.loading = true; - const res = await this.api.getInfo(); - if (res.done) { - this.service.currentOrganization = res.body.organization_name; - } - this.loading = false; - } catch (e) { - this.loading = false; - } - - this.cdr.markForCheck(); - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-drawer-title/rpt-drawer-title.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-drawer-title/rpt-drawer-title.component.html deleted file mode 100644 index 97df3855..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-drawer-title/rpt-drawer-title.component.html +++ /dev/null @@ -1,5 +0,0 @@ -
-   -   - {{title}} -
diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-drawer-title/rpt-drawer-title.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-drawer-title/rpt-drawer-title.component.scss deleted file mode 100644 index 84d04f7b..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-drawer-title/rpt-drawer-title.component.scss +++ /dev/null @@ -1,8 +0,0 @@ -.ellipsis { - max-width: 325px; - line-height: 24px; - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-drawer-title/rpt-drawer-title.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-drawer-title/rpt-drawer-title.component.spec.ts deleted file mode 100644 index a0c337c4..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-drawer-title/rpt-drawer-title.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptDrawerTitleComponent} from './rpt-drawer-title.component'; - -describe('RptDrawerTitleComponent', () => { - let component: RptDrawerTitleComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [RptDrawerTitleComponent] - }); - fixture = TestBed.createComponent(RptDrawerTitleComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-drawer-title/rpt-drawer-title.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-drawer-title/rpt-drawer-title.component.ts deleted file mode 100644 index 5cc38ed4..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-drawer-title/rpt-drawer-title.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {NzIconModule} from "ng-zorro-antd/icon"; - -@Component({ - selector: 'worklenz-rpt-drawer-title', - standalone: true, - imports: [CommonModule, NzIconModule], - templateUrl: './rpt-drawer-title.component.html', - styleUrls: ['./rpt-drawer-title.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptDrawerTitleComponent { - @Input({required: true}) title!: string | null; - @Input() icon: 'bank' | 'file' | null = null; -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-flat-task-list/rpt-flat-task-list.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-flat-task-list/rpt-flat-task-list.component.html deleted file mode 100644 index 7f68ebdf..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-flat-task-list/rpt-flat-task-list.component.html +++ /dev/null @@ -1,61 +0,0 @@ - -
- - - - - - - - - Filter by:  - - - - - - -
- - - - Task - Project - Status - Priority - Due Date - Completed Date - Estimated Time - Logged Time - Overlogged Time - - - - - - - - - {{ data.name }} - - - - - - {{data.status_name}} - - - {{data.priority_name}} - - {{ (data.end_date | date: 'MMM dd,yyyy') || '-'}} - {{ (data.completed_date | date: 'MMM dd,yyyy') || '-'}} - {{ data.estimated_string }} - {{ data.time_spent_string }} - {{ data.overlogged_time }} - - - -
diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-flat-task-list/rpt-flat-task-list.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-flat-task-list/rpt-flat-task-list.component.scss deleted file mode 100644 index edd2584b..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-flat-task-list/rpt-flat-task-list.component.scss +++ /dev/null @@ -1,4 +0,0 @@ -nz-tag { - border-radius: 20px; - color: rgba(0, 0, 0, 0.85); -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-flat-task-list/rpt-flat-task-list.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-flat-task-list/rpt-flat-task-list.component.spec.ts deleted file mode 100644 index 8871e628..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-flat-task-list/rpt-flat-task-list.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptFlatTaskListComponent} from './rpt-flat-task-list.component'; - -describe('RptFlatTaskListComponent', () => { - let component: RptFlatTaskListComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptFlatTaskListComponent] - }); - fixture = TestBed.createComponent(RptFlatTaskListComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-flat-task-list/rpt-flat-task-list.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-flat-task-list/rpt-flat-task-list.component.ts deleted file mode 100644 index 48dd6b9c..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-flat-task-list/rpt-flat-task-list.component.ts +++ /dev/null @@ -1,194 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {ReportingApiService} from "../../../reporting-api.service"; -import {IRPTOverviewProject, IRPTReportingMemberTask} from "../../../interfaces"; -import {ReportingService} from "../../../reporting.service"; -import {ReportingDrawersService} from "../../reporting-drawers.service"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {SocketEvents} from "@shared/socket-events"; -import {TaskTimerService} from "@admin/components/task-timer/task-timer.service"; -import {Socket} from "ngx-socket-io"; -import {merge} from "chart.js/helpers"; -import moment from "moment"; -import {LAST_MONTH, LAST_QUARTER, LAST_WEEK, PREV_MONTH, PREV_WEEK, YESTERDAY} from "@shared/constants"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-rpt-flat-task-list', - templateUrl: './rpt-flat-task-list.component.html', - styleUrls: ['./rpt-flat-task-list.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptFlatTaskListComponent implements OnInit, OnDestroy { - @Input() projectId: string | null = null; - @Input() teamMemberId!: string; - @Input() disableProjectsFilter = false; - @Input() isMultiple: boolean = false; - @Input() onlySingleMember: boolean = false; - @Input() isDurationLabelSelected = true; - - loading = false; - loadingProjects = false; - - tasks: IRPTReportingMemberTask[] = []; - projects: IRPTOverviewProject[] = []; - - searchText!: string; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: ReportingApiService, - private readonly service: ReportingService, - private readonly drawer: ReportingDrawersService, - private readonly view: TaskViewService, - private readonly timerService: TaskTimerService, - private readonly socket: Socket, - private readonly auth: AuthService, - ) { - this.view.onRefresh - .pipe(takeUntilDestroyed()) - .subscribe(() => { - void this.get(false); - }); - - this.timerService.onSubmitOrUpdate - .pipe(takeUntilDestroyed()) - .subscribe(() => { - void this.get(false); - }); - - this.service.onDrawerDateRangeChange.pipe(takeUntilDestroyed()).subscribe(async () => { - await this.get(true); - }); - - this.service.onDrawerDurationChange.pipe(takeUntilDestroyed()).subscribe(async () => { - await this.get(true); - }); - - } - - ngOnInit() { - void this.get(); - void this.getProjects(); - this.socket.on(SocketEvents.TASK_STATUS_CHANGE.toString(), this.refreshList); - this.socket.on(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.refreshList); - this.socket.on(SocketEvents.TASK_NAME_CHANGE.toString(), this.refreshList); - this.socket.on(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.refreshList); - this.socket.on(SocketEvents.TASK_START_DATE_CHANGE.toString(), this.refreshList); - this.socket.on(SocketEvents.TASK_TIME_ESTIMATION_CHANGE.toString(), this.refreshList); - this.socket.on(SocketEvents.TASK_TIMER_STOP.toString(), this.refreshList); - this.socket.on(SocketEvents.TASK_PHASE_CHANGE.toString(), this.refreshList); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_STATUS_CHANGE.toString(), this.refreshList); - this.socket.removeListener(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.refreshList); - this.socket.removeListener(SocketEvents.TASK_NAME_CHANGE.toString(), this.refreshList); - this.socket.removeListener(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.refreshList); - this.socket.removeListener(SocketEvents.TASK_START_DATE_CHANGE.toString(), this.refreshList); - this.socket.removeListener(SocketEvents.TASK_TIME_ESTIMATION_CHANGE.toString(), this.refreshList); - this.socket.removeListener(SocketEvents.TASK_TIMER_STOP.toString(), this.refreshList); - this.socket.removeListener(SocketEvents.TASK_PHASE_CHANGE.toString(), this.refreshList); - } - - private async setDatesForKeys() { - if(this.service.getDrawerDuration()?.key) { - const key = this.service.getDrawerDuration()?.key; - const today = moment(); - - switch (key) { - case YESTERDAY: - const yesterday = moment().subtract(1, "days"); - this.service.setDrawerDateRange([yesterday.toString(), yesterday.toString()]); - break; - case LAST_WEEK: - const lastWeekStart = moment().subtract(1, "weeks"); - this.service.setDrawerDateRange([lastWeekStart.toString(), today.toString()]); - break; - case LAST_MONTH: - const lastMonthStart = moment().subtract(1, "months"); - this.service.setDrawerDateRange([lastMonthStart.toString(), today.toString()]); - break; - case LAST_QUARTER: - const lastQuaterStart = moment().subtract(3, "months"); - this.service.setDrawerDateRange([lastQuaterStart.toString(), today.toString()]); - break; - case PREV_WEEK: - const prevWeekStart = moment().subtract(1, "weeks").startOf("week"); - const prevWeekEnd = moment().subtract(1, "weeks").endOf("week"); - this.service.setDrawerDateRange([prevWeekStart.toString(), prevWeekEnd.toString()]); - break; - case PREV_MONTH: - const prevMonthStart = moment().subtract(1, "month").startOf("month"); - const prevMonthEnd = moment().subtract(1, "month").endOf("month"); - this.service.setDrawerDateRange([prevMonthStart.toString(), prevMonthEnd.toString()]); - break; - } - } -} - - private async get(showLoading = true) { - if (this.loading) return; - this.loading = showLoading; - if (this.isDurationLabelSelected && this.onlySingleMember) { - await this.setDatesForKeys(); - } - try { - const additionalBody = { - duration : this.onlySingleMember ? this.service.getDrawerDuration()?.key : this.service.getDuration()?.key, - date_range : this.onlySingleMember ? this.service.getDrawerDateRange() : this.service.getDateRange(), - only_single_member : this.onlySingleMember, - archived: this.service.getIncludeToggle() - }; - const res = await this.api.getTasksByMember( - this.teamMemberId, - this.projectId, - false, - null, additionalBody); - if (res.done) { - this.tasks = res.body; - } - this.loading = false; - } catch (e) { - this.loading = false; - } - - this.cdr.markForCheck(); - } - - private async getProjects() { - if (this.disableProjectsFilter || this.loadingProjects) return; - this.loadingProjects = true; - try { - const teamId = this.service.getCurrentTeam()?.id as string; - const res = await this.api.getOverviewProjectsByTeam(teamId, this.teamMemberId); - if (res.done) { - this.projects = res.body; - } - this.loadingProjects = false; - } catch (e) { - this.loadingProjects = false; - } - - this.cdr.markForCheck(); - } - - trackBy(index: number, data: IRPTReportingMemberTask) { - return data.id; - } - - openTask(data: IRPTReportingMemberTask) { - if (data.id && data.project_id) - this.drawer.openTask({taskId: data.id, projectId: data.project_id}); - } - - onProjectChange(projectId: string) { - this.projectId = projectId; - void this.get(); - } - - refreshList = (response: any) => { - this.get(false); - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-flat-task-list/rpt-flat-tasks-list.module.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-flat-task-list/rpt-flat-tasks-list.module.ts deleted file mode 100644 index 43494b20..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-flat-task-list/rpt-flat-tasks-list.module.ts +++ /dev/null @@ -1,44 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {RptFlatTaskListComponent} from './rpt-flat-task-list.component'; -import {BindNaPipe} from "@pipes/bind-na.pipe"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzTableModule} from "ng-zorro-antd/table"; -import {FormsModule, ReactiveFormsModule} from "@angular/forms"; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import {NzTagModule} from "ng-zorro-antd/tag"; -import {NzGridModule} from "ng-zorro-antd/grid"; -import {NzSelectModule} from "ng-zorro-antd/select"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; - -@NgModule({ - declarations: [ - RptFlatTaskListComponent - ], - exports: [ - RptFlatTaskListComponent - ], - imports: [ - CommonModule, - BindNaPipe, - NzIconModule, - NzInputModule, - NzSkeletonModule, - NzTableModule, - ReactiveFormsModule, - SafeStringPipe, - SearchByNamePipe, - FormsModule, - NzBadgeModule, - NzTagModule, - NzGridModule, - NzSelectModule, - NzTypographyModule - ] -}) -export class RptFlatTasksListModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-grouped-task-list/rpt-grouped-task-list.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-grouped-task-list/rpt-grouped-task-list.component.html deleted file mode 100644 index b9cdbc88..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-grouped-task-list/rpt-grouped-task-list.component.html +++ /dev/null @@ -1,73 +0,0 @@ -
- - - - - - - - - Group by:  - - - - - - -
- - - - - - - - Task - Status - Priority - Phase - Due Date - Completed On - Days Overdue - Estimated Time - Logged Time - Overlogged Time - - - - - - - - - {{ data.name }} - - - {{ data.status_name }} - - - {{ data.priority_name }} - - - {{ data.phase_name}} - - - - {{ (data.end_date | date: 'MMM dd,yyyy') || '-'}} - {{ (data.completed_at | date: 'MMM dd,yyyy') || '-'}} - {{ data.overdue_days || '-'}} - {{ data.total_time_string }} - {{ data.time_spent_string }} - {{ data.overlogged_time_string }} - - - - - - - - - - diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-grouped-task-list/rpt-grouped-task-list.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-grouped-task-list/rpt-grouped-task-list.component.scss deleted file mode 100644 index 077cbdde..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-grouped-task-list/rpt-grouped-task-list.component.scss +++ /dev/null @@ -1,4 +0,0 @@ -nz-tag{ - border-radius: 30px; - color: rgba(0, 0, 0, 0.85); -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-grouped-task-list/rpt-grouped-task-list.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-grouped-task-list/rpt-grouped-task-list.component.spec.ts deleted file mode 100644 index 9761f2c7..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-grouped-task-list/rpt-grouped-task-list.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptGroupedTaskListComponent} from './rpt-grouped-task-list.component'; - -describe('RptGroupedTaskListComponent', () => { - let component: RptGroupedTaskListComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptGroupedTaskListComponent] - }); - fixture = TestBed.createComponent(RptGroupedTaskListComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-grouped-task-list/rpt-grouped-task-list.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-grouped-task-list/rpt-grouped-task-list.component.ts deleted file mode 100644 index 58990cae..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-grouped-task-list/rpt-grouped-task-list.component.ts +++ /dev/null @@ -1,132 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {ITaskListGroup} from "../../../../modules/task-list-v2/interfaces"; -import {ReportingApiService} from "../../../reporting-api.service"; -import {TaskListV2Service} from "../../../../modules/task-list-v2/task-list-v2.service"; -import {ReportingDrawersService} from "../../reporting-drawers.service"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {TasksLogTimeService} from "@api/tasks-log-time.service"; -import {TaskTimerService} from "@admin/components/task-timer/task-timer.service"; - -@Component({ - selector: 'worklenz-rpt-grouped-task-list', - templateUrl: './rpt-grouped-task-list.component.html', - styleUrls: ['./rpt-grouped-task-list.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptGroupedTaskListComponent implements OnInit, OnDestroy { - @Input({required: true}) projectId: string | null = null; - - loading = false; - - groups: ITaskListGroup[] = []; - - groupBy = this.list.GROUP_BY_STATUS_VALUE; - searchText!: string; - - get groupByOptions() { - return this.list.GROUP_BY_OPTIONS; - } - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: ReportingApiService, - private readonly list: TaskListV2Service, - private readonly drawer: ReportingDrawersService, - private readonly view: TaskViewService, - private readonly timerService: TaskTimerService, - private readonly socket: Socket, - ) { - this.view.onRefresh - .pipe(takeUntilDestroyed()) - .subscribe(() => { - void this.get(false); - }); - - this.timerService.onSubmitOrUpdate - .pipe(takeUntilDestroyed()) - .subscribe(() => { - void this.get(false); - }); - } - - ngOnInit() { - void this.get(); - this.socket.on(SocketEvents.TASK_STATUS_CHANGE.toString(), this.refreshList); - this.socket.on(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.refreshList); - this.socket.on(SocketEvents.TASK_NAME_CHANGE.toString(), this.refreshList); - this.socket.on(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.refreshList); - this.socket.on(SocketEvents.TASK_START_DATE_CHANGE.toString(), this.refreshList); - this.socket.on(SocketEvents.TASK_TIME_ESTIMATION_CHANGE.toString(), this.refreshList); - this.socket.on(SocketEvents.TASK_TIMER_STOP.toString(), this.refreshList); - this.socket.on(SocketEvents.TASK_PHASE_CHANGE.toString(), this.refreshList); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_STATUS_CHANGE.toString(), this.refreshList); - this.socket.removeListener(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.refreshList); - this.socket.removeListener(SocketEvents.TASK_NAME_CHANGE.toString(), this.refreshList); - this.socket.removeListener(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.refreshList); - this.socket.removeListener(SocketEvents.TASK_START_DATE_CHANGE.toString(), this.refreshList); - this.socket.removeListener(SocketEvents.TASK_TIME_ESTIMATION_CHANGE.toString(), this.refreshList); - this.socket.removeListener(SocketEvents.TASK_TIMER_STOP.toString(), this.refreshList); - this.socket.removeListener(SocketEvents.TASK_PHASE_CHANGE.toString(), this.refreshList); - } - - refreshList = (response: any) => { - this.get(false); - } - - trackByGroup(index: number, data: ITaskListGroup) { - return data.id; - } - - trackByTask(index: number, data: IProjectTask) { - return data.id; - } - - private async get(showLoading = true) { - if (!this.projectId) return; - try { - this.loading = showLoading; - const res = await this.api.getTasks(this.projectId, this.groupBy); - if (res.done) { - this.groups = res.body; - } - this.loading = false; - } catch (e) { - this.loading = false; - } - - this.cdr.markForCheck(); - } - - isGroupByStatus() { - return this.groupBy === this.list.GROUP_BY_STATUS_VALUE; - } - - isGroupByPriority() { - return this.groupBy === this.list.GROUP_BY_PRIORITY_VALUE; - } - - isGroupByPhase() { - return this.groupBy === this.list.GROUP_BY_PHASE_VALUE; - } - - openTask(data: IProjectTask) { - if (data.id && this.projectId) { - this.drawer.openTask({ - taskId: data.id, - projectId: this.projectId - }); - } - } - - onGroupByChange() { - void this.get(); - } -} - diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-grouped-task-list/rpt-grouped-task-list.module.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-grouped-task-list/rpt-grouped-task-list.module.ts deleted file mode 100644 index 0ac20e43..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-grouped-task-list/rpt-grouped-task-list.module.ts +++ /dev/null @@ -1,46 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {RptGroupedTaskListComponent} from './rpt-grouped-task-list.component'; -import {BindNaPipe} from "@pipes/bind-na.pipe"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import {NzCollapseModule} from "ng-zorro-antd/collapse"; -import {NzGridModule} from "ng-zorro-antd/grid"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NzSelectModule} from "ng-zorro-antd/select"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzTableModule} from "ng-zorro-antd/table"; -import {FormsModule, ReactiveFormsModule} from "@angular/forms"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {NaComponent} from "@admin/components/na/na.component"; -import {NzTagModule} from "ng-zorro-antd/tag"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; - -@NgModule({ - declarations: [ - RptGroupedTaskListComponent - ], - exports: [ - RptGroupedTaskListComponent - ], - imports: [ - CommonModule, - BindNaPipe, - NzBadgeModule, - NzCollapseModule, - NzGridModule, - NzIconModule, - NzInputModule, - NzSelectModule, - NzSkeletonModule, - NzTableModule, - ReactiveFormsModule, - SearchByNamePipe, - FormsModule, - NaComponent, - NzTagModule, - NzTypographyModule - ] -}) -export class RptGroupedTaskListModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-member-projects-list/rpt-member-projects-list.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-member-projects-list/rpt-member-projects-list.component.html deleted file mode 100644 index 56e76194..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-member-projects-list/rpt-member-projects-list.component.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - -
- - - - Project - Team - Tasks - Contribution - Incompleted Tasks - Completed Tasks - Overdue Tasks - Logged Time - - - - - {{project.name}} - {{project.team}} - {{project.task_count}} - - - - {{project.incompleted}} - {{project.completed}} - {{project.overdue}} - {{project.time_logged}} - - - -
-
- - - - diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-member-projects-list/rpt-member-projects-list.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-member-projects-list/rpt-member-projects-list.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-member-projects-list/rpt-member-projects-list.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-member-projects-list/rpt-member-projects-list.component.spec.ts deleted file mode 100644 index ea4e6cf1..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-member-projects-list/rpt-member-projects-list.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { RptMemberProjectsListComponent } from './rpt-member-projects-list.component'; - -describe('RptMemberProjectsListComponent', () => { - let component: RptMemberProjectsListComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptMemberProjectsListComponent] - }); - fixture = TestBed.createComponent(RptMemberProjectsListComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-member-projects-list/rpt-member-projects-list.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-member-projects-list/rpt-member-projects-list.component.ts deleted file mode 100644 index 52adf313..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-member-projects-list/rpt-member-projects-list.component.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - OnInit, - Output -} from '@angular/core'; -import {ReportingApiService} from "../../../reporting-api.service"; -import {IRPTMemberProject, IRPTOverviewProject, IRPTOverviewProjectExt} from "../../../interfaces"; -import {ReportingDrawersService} from "../../reporting-drawers.service"; -import {ReportingService} from "../../../reporting.service"; - -@Component({ - selector: 'worklenz-rpt-member-projects-list', - templateUrl: './rpt-member-projects-list.component.html', - styleUrls: ['./rpt-member-projects-list.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) - -export class RptMemberProjectsListComponent implements OnInit { - @Input({required: true}) teamMemberId = ''; - @Input() teamId: string | null = null; - - @Output() openTasks = new EventEmitter(); - - searchText: string | null = null; - - loading = false; - model: IRPTMemberProject[] = []; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: ReportingApiService, - private readonly drawer: ReportingDrawersService, - private readonly service: ReportingService, - ) { - } - - async ngOnInit() { - await this.get(true); - } - - async search() { - await this.get(false); - } - - private async get(loading = true) { - if (!this.teamMemberId) return; - try { - this.loading = loading; - const res = await this.api.getMemberProjects({ - teamMemberId: this.teamMemberId, - teamId: this.teamId, - search: this.searchText, - archived: this.service.getIncludeToggle() - }); - if (res.done) { - this.model = res.body; - } - this.loading = false; - this.cdr.markForCheck(); - } catch (e) { - this.loading = false; - this.cdr.markForCheck(); - } - } - - openProject(project: IRPTMemberProject) { - const body: IRPTOverviewProjectExt = { - id: project.id, - name: project.name, - client: '', - status: {name: '', color_code: '', icon: ''}, - team_member_id: project.team_member_id - } - this.openTasks.emit(body); - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-projects-list/rpt-projects-list.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-projects-list/rpt-projects-list.component.html deleted file mode 100644 index 2400657e..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-projects-list/rpt-projects-list.component.html +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - - - -
- - - - - Project - - Estimated vs Actual - Tasks Progress - Last Activity - Status - - Start/End dates - Days Left/Overdue - - Project Health - - Category - Project Update - Client - Team - - - - - - - -
- - - {{data.name}} - - -
- - - - - - - - - - - - - - - - - - - - {{data.last_activity.last_activity_string}}. - - - - - - - - - - - - - - - - - - - - - - {{ data.days_left }} days overdue - - Today - {{ data.days_left }} days left - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ data.client }} - - - - - - {{data.team_name}} - - - - -
-
-
- - - - diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-projects-list/rpt-projects-list.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-projects-list/rpt-projects-list.component.scss deleted file mode 100644 index 385609cc..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-projects-list/rpt-projects-list.component.scss +++ /dev/null @@ -1,27 +0,0 @@ - -.ellipsis { - max-width: 225px; - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.open-btn { - position: absolute; - right: 0; - top: 0; - bottom: 0; - opacity: 0; - margin-top: auto; - margin-bottom: auto; - padding: 0px 4px; - background: #edebf0 !important; - color: rgba(0, 0, 0, 0.85); - border: none; -} - -tr:hover { - & .open-btn { - opacity: 1; - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-projects-list/rpt-projects-list.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-projects-list/rpt-projects-list.component.spec.ts deleted file mode 100644 index 7225061f..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-projects-list/rpt-projects-list.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptProjectsListComponent} from './rpt-projects-list.component'; - -describe('RptProjectsListComponent', () => { - let component: RptProjectsListComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptProjectsListComponent] - }); - fixture = TestBed.createComponent(RptProjectsListComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-projects-list/rpt-projects-list.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-projects-list/rpt-projects-list.component.ts deleted file mode 100644 index b378ea5a..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-projects-list/rpt-projects-list.component.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - OnDestroy, - OnInit, - Output, ViewChild -} from '@angular/core'; -import {IRPTOverviewProject, IRPTProject, IRPTProjectsViewModel} from "../../../interfaces"; -import {ReportingApiService} from "../../../reporting-api.service"; -import {log_error} from "@shared/utils"; -import {ProjectHealthsApiService} from "@api/project-healths-api.service"; -import {IProjectHealth} from "@interfaces/project-health"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {IProjectStatus} from "@interfaces/project-status"; -import {ProjectStatusesApiService} from "@api/project-statuses-api.service"; -import {NzTableQueryParams} from "ng-zorro-antd/table"; -import {ProjectUpdatesDrawerComponent} from "@admin/components/project-updates-drawer/project-updates-drawer.component"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {ProjectUpdatesService} from "@services/project-updates.service"; -import {ReportingService} from "../../../reporting.service"; - -@Component({ - selector: 'worklenz-rpt-projects-list', - templateUrl: './rpt-projects-list.component.html', - styleUrls: ['./rpt-projects-list.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptProjectsListComponent implements OnInit, OnDestroy { - @ViewChild(ProjectUpdatesDrawerComponent) updatesDrawer!: ProjectUpdatesDrawerComponent; - - @Input() teamId!: string; - @Input() teamMemberId!: string; - - @Output() length = new EventEmitter(); - @Output() selectProject = new EventEmitter(); - - private readonly FILTER_INDEX_KEY = "worklenz.projects.filter_index"; - private readonly PROJECT_LIST_COLUMNS = "worklenz.reporting.projects.column_list"; - - loading = false; - projects: IRPTProject[] = []; - projHealths: IProjectHealth[] = []; - projectStatuses: IProjectStatus[] = [] - - searchText!: string; - - total = 0; - pageSize = 10; - pageIndex = 1; - paginationSizes = [5, 10, 15, 20, 50, 100]; - sortField: string | null = null; - sortOrder: string | null = null; - - public COLUMN_KEYS = { - CLIENT: "CLIENT", - TEAM: "TEAM", - STATUS: "STATUS", - CATEGORY: "CATEGORY", - START_END_DATE: "START_END_DATE", - DAYS_LEFT_OVERDUE: "DAYS_LEFT_OVERDUE", - ESTIMATED_VS_ACTUAL: "ESTIMATED_VS_ACTUAL", - TASKS_PROGRESS: "TASKS_PROGRESS", - LAST_ACTIVITY: "LAST_ACTIVITY", - HEALTH: "HEALTH", - UPDATE: "UPDATE", - PROJECT_MANAGER: "PROJECT_MANAGER" - }; - - public columns: { key: string; label: string; pinned: boolean }[] = [ - {key: "ESTIMATED_VS_ACTUAL", label: "Estimated vs Actual", pinned: true}, - {key: "TASKS_PROGRESS", label: "Tasks Progress", pinned: true}, - {key: "LAST_ACTIVITY", label: "Last Activity", pinned: true}, - {key: "STATUS", label: "Status", pinned: true}, - {key: "START_END_DATE", label: "Start/End dates", pinned: true}, - {key: "DAYS_LEFT_OVERDUE", label: "Days Left/Overdue", pinned: true}, - {key: "HEALTH", label: "Project Health", pinned: true}, - {key: "CATEGORY", label: "Category", pinned: true}, - {key: "UPDATE", label: "Project Update", pinned: true}, - {key: "PROJECT_MANAGER", label: "Project Manager", pinned: true}, - {key: "CLIENT", label: "Client", pinned: false}, - {key: "TEAM", label: "Team", pinned: false}, - ]; - - clientActive = false; - managerActive = false; - teamActive = false; - statusActive = true; - categoryActive = true; - startEndDateActive = true; - daysLeftActive = true; - estimatedActive = true; - progressActive = true; - lastActivityActive = true; - healthActive = true; - updateActive = true; - - get columnList() { - if (localStorage.hasOwnProperty(this.PROJECT_LIST_COLUMNS)) - return JSON.parse(localStorage.getItem(this.PROJECT_LIST_COLUMNS) || ''); - } - - get filterIndex() { - return +(localStorage.getItem(this.FILTER_INDEX_KEY) || 0); - } - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: ReportingApiService, - private readonly projectHealthsApi: ProjectHealthsApiService, - private readonly socket: Socket, - private readonly statusesApi: ProjectStatusesApiService, - private readonly service: ReportingService - ) {} - - ngOnInit() { - void this.getProjectHealths(); - void this.getProjectStatuses(); - void this.getProjects(); - this.socket.on(SocketEvents.PROJECT_START_DATE_CHANGE.toString(), this.refreshWithoutLoading); - this.socket.on(SocketEvents.PROJECT_END_DATE_CHANGE.toString(), this.refreshWithoutLoading); - if (this.columnList) { - this.columns = this.columnList; - this.updateState(); - } - } - - ngOnDestroy() { - this.projects = []; - } - - onQueryParamsChange(params: NzTableQueryParams) { - const {pageSize, pageIndex, sort} = params; - this.pageIndex = pageIndex; - this.pageSize = pageSize; - - const currentSort = sort.find(item => item.value !== null); - - this.sortField = currentSort?.key ?? null; - this.sortOrder = currentSort?.value ?? null; - void this.getProjects(); - } - - protected async getProjects(loading = true) { - if (!this.teamId) return; - try { - this.loading = loading; - const res = await this.api.getOverviewProjects( - { - team: this.teamId, - index: this.pageIndex, - size: this.pageSize, - field: this.sortField, - order: this.sortOrder, - search: this.searchText || null, - filter: this.filterIndex.toString(), - archived: this.service.getIncludeToggle() - } - ); - if (res.done) { - this.total = res.body.total || 0; - this.projects = res.body.projects || []; - this.length.emit(this.total); - } - this.loading = false; - } catch (e) { - this.loading = false; - } - this.cdr.markForCheck(); - } - - private async getProjectHealths() { - try { - const res = await this.projectHealthsApi.get(); - if (res) { - this.projHealths = res.body - } - } catch (e) { - log_error(e); - } - this.cdr.markForCheck(); - } - - async getProjectStatuses() { - try { - const res = await this.statusesApi.get(); - if (res.done) { - this.projectStatuses = res.body; - } - } catch (e) { - log_error(e); - } - this.cdr.markForCheck(); - } - - trackBy(index: number, data: IRPTOverviewProject) { - return data.id; - } - - openProject(project: IRPTOverviewProject) { - if (project) { - this.selectProject.emit(project); - } - } - - refreshWithoutLoading = () => { - void this.getProjects(false); - } - - openUpdates(data: IRPTProject) { - this.updatesDrawer.open(data.id); - } - - private updateState() { - this.clientActive = this.canActive(this.COLUMN_KEYS.CLIENT); - this.teamActive = this.canActive(this.COLUMN_KEYS.TEAM); - this.categoryActive = this.canActive(this.COLUMN_KEYS.CATEGORY); - this.statusActive = this.canActive(this.COLUMN_KEYS.STATUS); - this.startEndDateActive = this.canActive(this.COLUMN_KEYS.START_END_DATE); - this.daysLeftActive = this.canActive(this.COLUMN_KEYS.DAYS_LEFT_OVERDUE); - this.estimatedActive = this.canActive(this.COLUMN_KEYS.ESTIMATED_VS_ACTUAL); - this.progressActive = this.canActive(this.COLUMN_KEYS.TASKS_PROGRESS); - this.lastActivityActive = this.canActive(this.COLUMN_KEYS.LAST_ACTIVITY); - this.healthActive = this.canActive(this.COLUMN_KEYS.HEALTH); - this.updateActive = this.canActive(this.COLUMN_KEYS.UPDATE); - this.managerActive = this.canActive(this.COLUMN_KEYS.PROJECT_MANAGER); - this.cdr.markForCheck(); - this.setColumnList(); - } - - public canActive(key: string) { - return !!this.columns.find(c => c.key === key)?.pinned; - } - - setColumnList() { - localStorage.setItem(this.PROJECT_LIST_COLUMNS, JSON.stringify(this.columns)); - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-projects-list/rpt-projects-list.module.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-projects-list/rpt-projects-list.module.ts deleted file mode 100644 index 7e910c49..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/common/rpt-projects-list/rpt-projects-list.module.ts +++ /dev/null @@ -1,63 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {RptProjectsListComponent} from './rpt-projects-list.component'; -import {BindNaPipe} from "@pipes/bind-na.pipe"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; -import {NzTableModule} from "ng-zorro-antd/table"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {FormsModule} from "@angular/forms"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzWaveModule} from "ng-zorro-antd/core/wave"; -import {NzProgressModule} from "ng-zorro-antd/progress"; -import {NzTagModule} from "ng-zorro-antd/tag"; -import {NaComponent} from "@admin/components/na/na.component"; -import {TasksProgressBarComponent} from "@admin/components/tasks-progress-bar/tasks-progress-bar.component"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzSelectModule} from "ng-zorro-antd/select"; -import {TaskPriorityLabelComponent} from "@admin/components/task-priority-label/task-priority-label.component"; -import {ReportingModule} from "../../../reporting.module"; -import {NgChartsModule} from "ng2-charts"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {EllipsisPipe} from "@pipes/ellipsis.pipe"; -import {ProjectUpdatesDrawerComponent} from "@admin/components/project-updates-drawer/project-updates-drawer.component"; - -@NgModule({ - declarations: [ - RptProjectsListComponent - ], - exports: [ - RptProjectsListComponent, - ], - imports: [ - CommonModule, - BindNaPipe, - NzIconModule, - SafeStringPipe, - NzTableModule, - NzSkeletonModule, - NzInputModule, - FormsModule, - SearchByNamePipe, - NzBadgeModule, - NzButtonModule, - NzWaveModule, - NzProgressModule, - NzTagModule, - NaComponent, - TasksProgressBarComponent, - NzToolTipModule, - NzSelectModule, - TaskPriorityLabelComponent, - ReportingModule, - NgChartsModule, - NzSpaceModule, - EllipsisPipe, - ProjectUpdatesDrawerComponent, - ] -}) -export class RptProjectsListModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/reporting-drawers.service.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/reporting-drawers.service.ts deleted file mode 100644 index d1e764e3..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/reporting-drawers.service.ts +++ /dev/null @@ -1,132 +0,0 @@ -import {Injectable} from '@angular/core'; -import {Subject} from "rxjs"; -import { - IRPTMember, - IRPTMemberDrawerData, - IRPTOverviewProject, - IRPTSingleMemberDrawerData, - IRPTTaskDrawerData, - IRPTTasksDrawerData, - IRPTTeam -} from "../interfaces"; -import {ReportingService} from "../reporting.service"; - -@Injectable({ - providedIn: "root" -}) -export class ReportingDrawersService { - private readonly _openTeamDrawerSbj = new Subject(); - private readonly _openProjectDrawerSbj = new Subject(); - private readonly _openMemberDrawerSbj = new Subject(); - private readonly _openSingleMemberDrawerSbj = new Subject(); - private readonly _openTasksDrawerSbj = new Subject(); - private readonly _openTaskDrawerSbj = new Subject(); - private readonly _openSingleMemberTaskStat = new Subject<{team_member_id: string}>(); - private readonly _openSingleMemberProjects = new Subject<{team_member_id: string}>(); - private readonly _openSingleMemberTimeLogs = new Subject(); - - private readonly _singleMemberOverviewSbj = new Subject(); - private readonly _singleMemberTimeLogsSbj = new Subject(); - private readonly _singleMemberActivityLogsSbj = new Subject(); - private readonly _singleMemberProjectsSbj = new Subject(); - private readonly _singleMemberTasksSbj = new Subject(); - - constructor( - private readonly rptService: ReportingService - ) { - } - - public get onOpenTeam() { - return this._openTeamDrawerSbj.asObservable(); - } - - public get onOpenProject() { - return this._openProjectDrawerSbj.asObservable(); - } - - public get onOpenMember() { - return this._openMemberDrawerSbj.asObservable(); - } - - public get onOpenSingleMember() { - return this._openSingleMemberDrawerSbj.asObservable(); - } - - public get onOpenTasks() { - return this._openTasksDrawerSbj.asObservable(); - } - - public get onOpenTask() { - return this._openTaskDrawerSbj.asObservable(); - } - - public get onOpenSingleMemberTaskStat() { - return this._openSingleMemberTaskStat.asObservable(); - } - - public get onOpenSingleMemberProjects() { - return this._openSingleMemberProjects.asObservable(); - } - - public get onOpenSingleMemberTimeLogs() { - return this._openSingleMemberTimeLogs.asObservable(); - } - - public openTeam(team: IRPTTeam | null) { - this.rptService.setCurrentTeam(team); - this._openTeamDrawerSbj.next(team); - } - - public openProject(project: IRPTOverviewProject | null) { - this._openProjectDrawerSbj.next(project); - } - - public openMember(member: IRPTMember | null, project: IRPTOverviewProject | null) { - this._openMemberDrawerSbj.next({member, project}); - } - - public openSingleMember(member: IRPTMember | null) { - this._openSingleMemberDrawerSbj.next({member}); - } - - public openTasks(project: IRPTOverviewProject, member: IRPTMember) { - this._openTasksDrawerSbj.next({project, member}); - } - - public openTask(data: IRPTTaskDrawerData) { - this._openTaskDrawerSbj.next(data); - } - - public openSingleMemberTaskStat(data: {team_member_id: string}) { - this._openSingleMemberTaskStat.next(data) - } - - public openSingleMemberProjects(data: {team_member_id: string}) { - this._openSingleMemberProjects.next(data) - } - - public openTimeLogsTab() { - this._openSingleMemberTimeLogs.next(); - } - - public emitGetSingleMemberOverview() { - this._singleMemberOverviewSbj.next(); - } - - public emitGetSingleMemberTimeLogs() { - this._singleMemberTimeLogsSbj.next(); - } - - public emitGetSingleMemberActivityLogs() { - this._singleMemberActivityLogsSbj.next(); - } - - public emitGetSingleMemberProjects() { - this._singleMemberProjectsSbj.next(); - } - - public emitGetSingleMemberTasks() { - this._singleMemberTasksSbj.next(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-overview/rpt-member-drawer-overview.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-overview/rpt-member-drawer-overview.component.html deleted file mode 100644 index d031d6b3..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-overview/rpt-member-drawer-overview.component.html +++ /dev/null @@ -1,164 +0,0 @@ -
-
- -
    -
  • - - {{model.stats?.projects || 0}} Projects -
  • -
  • - - {{model.stats?.completed || 0}} Completed Tasks -
  • -
  • - - {{model.stats?.ongoing || 0}} Ongoing Tasks -
  • -
  • - - {{model.stats?.overdue || 0}} Overdue Tasks -
  • -
  • - - {{model.stats?.total_logged || 0}} Logged Hours -
  • -
-
-
-
- - -
-

Tasks By Project

-
-
-
- -
-
- -
- No tasks to show. -
-
-
-
- - -
-
-
    -
  • - -
  • -
  • - - {{item.label | ellipsis: 30}} -
  • -
-
-
-
-
-
-
- - -
-
-

Tasks By Status

-
-
-
-
- -
-
- -
- No tasks to show. -
-
-
-
- - -
-
-
    -
  • - -
  • -
  • - -
  • -
-
-
-
-
-
-
- - -
-
-

Tasks By Priority

-
-
-
-
- -
-
- -
- No tasks to show. -
-
-
-
- - -
-
-
    -
  • - -
  • -
  • - -
  • -
-
-
-
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-overview/rpt-member-drawer-overview.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-overview/rpt-member-drawer-overview.component.scss deleted file mode 100644 index fd33a5be..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-overview/rpt-member-drawer-overview.component.scss +++ /dev/null @@ -1,14 +0,0 @@ -.chart { - width: 215px; - max-width: 215px; -} - -.chart-details { - max-width: 145px; - max-height: 215px; - overflow-y: auto; -} - -.no-data-img-holder { - width: 65px; -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-overview/rpt-member-drawer-overview.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-overview/rpt-member-drawer-overview.component.spec.ts deleted file mode 100644 index 4302ec3e..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-overview/rpt-member-drawer-overview.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptMemberDrawerOverviewComponent} from './rpt-member-drawer-overview.component'; - -describe('RptMemberDrawerOverviewComponent', () => { - let component: RptMemberDrawerOverviewComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptMemberDrawerOverviewComponent] - }); - fixture = TestBed.createComponent(RptMemberDrawerOverviewComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-overview/rpt-member-drawer-overview.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-overview/rpt-member-drawer-overview.component.ts deleted file mode 100644 index c6c4f0ba..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-overview/rpt-member-drawer-overview.component.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - Input, - NgZone, OnDestroy, - OnInit, - ViewChild -} from '@angular/core'; -import {IRPTOverviewMemberInfo} from "../../../interfaces"; -import {ReportingApiService} from "../../../reporting-api.service"; -import {ChartConfiguration} from "chart.js"; -import {BaseChartDirective} from "ng2-charts"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {ReportingService} from "../../../reporting.service"; - -@Component({ - selector: 'worklenz-rpt-member-drawer-overview', - templateUrl: './rpt-member-drawer-overview.component.html', - styleUrls: ['./rpt-member-drawer-overview.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptMemberDrawerOverviewComponent implements OnInit, OnDestroy { - @Input({required: true}) memberId: string | null = null; - - @ViewChild(BaseChartDirective) projectsTaskChart: BaseChartDirective | undefined; - @ViewChild(BaseChartDirective) priorityTaskChart: BaseChartDirective | undefined; - @ViewChild(BaseChartDirective) statusTaskChart: BaseChartDirective | undefined; - - isProjectsChartEmpty = false; - isPriorityChartEmpty = false; - isStatusChartEmpty = false; - - projectColors: string[] = []; - priorityColors: string[] = []; - statusColors: string[] = []; - - projectsChartData: ChartConfiguration<'doughnut'>['data'] = { - labels: [], - datasets: [{ - label: 'Tasks', - data: [], - backgroundColor: this.projectColors, - hoverOffset: 2 - }] - }; - - prioritiesChartData: ChartConfiguration<'doughnut'>['data'] = { - labels: [], - datasets: [{ - label: 'Tasks', - data: [], - backgroundColor: this.priorityColors, - hoverOffset: 2 - }] - }; - - statusChartData: ChartConfiguration<'doughnut'>['data'] = { - labels: [], - datasets: [{ - label: 'Tasks', - data: [], - backgroundColor: this.statusColors, - hoverOffset: 2 - }] - }; - - chartOptions: ChartConfiguration<'doughnut'>['options'] = { - plugins: { - datalabels: { - display: false - } - }, - responsive: false - } - - loading = false; - model: IRPTOverviewMemberInfo = {}; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly service: ReportingService, - private readonly api: ReportingApiService, - protected readonly ngZone: NgZone, - private readonly socket: Socket, - ) { - - } - - ngOnInit() { - void this.get(); - this.listenSockets(); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_STATUS_CHANGE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.QUICK_TASK.toString(), this.refresh); - this.socket.removeListener(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), this.refresh); - } - - listenSockets() { - this.socket.on(SocketEvents.TASK_STATUS_CHANGE.toString(), this.refresh); - this.socket.on(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.refresh); - this.socket.on(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.refresh); - this.socket.on(SocketEvents.QUICK_TASK.toString(), this.refresh); - this.socket.on(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), this.refresh); - } - - public async get() { - if (!this.memberId) return; - try { - this.loading = true; - this.clearCharts(); - const res = await this.api.getTeamMemberInfo( - { - teamMemberId: this.memberId, - duration: 'LAST_WEEK', - date_range: [], - archived: this.service.getIncludeToggle() - } - ); - if (res.done) { - this.model = res.body; - this.drawCharts(res.body); - } - this.loading = false; - } catch (e) { - this.loading = false; - } - - this.cdr.markForCheck(); - } - - private drawCharts(data: IRPTOverviewMemberInfo) { - if (data.by_project) { - for (const item of data.by_project.chart) { - this.projectsChartData.labels?.push(item.name); - this.projectsChartData.datasets[0].data.push(item.y || 0); - this.projectColors.push(item.color as string); - } - this.projectsTaskChart?.update(); - - if (this.projectsChartData.datasets[0].data.every(value => value === 0)) - this.isProjectsChartEmpty = true; - this.cdr.markForCheck(); - } - if (data.by_priority) { - for (const item of data.by_priority.chart) { - this.prioritiesChartData.labels?.push(item.name); - this.prioritiesChartData.datasets[0].data.push(item.y || 0); - this.priorityColors.push(item.color as string); - } - this.priorityTaskChart?.update(); - - if (this.prioritiesChartData.datasets[0].data.every(value => value === 0)) - this.isPriorityChartEmpty = true; - this.cdr.markForCheck(); - } - if (data.by_status) { - for (const item of data.by_status.chart) { - this.statusChartData.labels?.push(item.name); - this.statusChartData.datasets[0].data.push(item.y || 0); - this.statusColors.push(item.color as string); - } - this.statusTaskChart?.update(); - - if (this.statusChartData.datasets[0].data.every(value => value === 0)) - this.isStatusChartEmpty = true; - this.cdr.markForCheck(); - } - this.cdr.markForCheck(); - } - - clearCharts() { - this.projectsChartData.datasets[0].data = []; - this.prioritiesChartData.datasets[0].data = []; - this.statusChartData.datasets[0].data = []; - - this.projectsChartData.labels = []; - this.prioritiesChartData.labels = []; - this.statusChartData.labels = []; - - this.cdr.markForCheck(); - } - - refresh = (response: any) => { - this.get(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-projects/rpt-member-drawer-projects.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-projects/rpt-member-drawer-projects.component.html deleted file mode 100644 index 2a8bf801..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-projects/rpt-member-drawer-projects.component.html +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-projects/rpt-member-drawer-projects.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-projects/rpt-member-drawer-projects.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-projects/rpt-member-drawer-projects.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-projects/rpt-member-drawer-projects.component.spec.ts deleted file mode 100644 index 11c34b70..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-projects/rpt-member-drawer-projects.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptMemberDrawerProjectsComponent} from './rpt-member-drawer-projects.component'; - -describe('RptMemberDrawerProjectsComponent', () => { - let component: RptMemberDrawerProjectsComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptMemberDrawerProjectsComponent] - }); - fixture = TestBed.createComponent(RptMemberDrawerProjectsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-projects/rpt-member-drawer-projects.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-projects/rpt-member-drawer-projects.component.ts deleted file mode 100644 index 91257603..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-projects/rpt-member-drawer-projects.component.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {ChangeDetectionStrategy, Component, EventEmitter, Input, Output} from '@angular/core'; -import {IRPTOverviewProject} from "../../../interfaces"; - -@Component({ - selector: 'worklenz-rpt-member-drawer-projects', - templateUrl: './rpt-member-drawer-projects.component.html', - styleUrls: ['./rpt-member-drawer-projects.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptMemberDrawerProjectsComponent { - @Input({required: true}) teamId!: string; - @Input({required: true}) teamMemberId!: string; - - @Output() selectProject = new EventEmitter(); - - onSelectProject(project: IRPTOverviewProject) { - this.selectProject.emit(project); - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-tasks/rpt-member-drawer-tasks.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-tasks/rpt-member-drawer-tasks.component.html deleted file mode 100644 index c0639900..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-tasks/rpt-member-drawer-tasks.component.html +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-tasks/rpt-member-drawer-tasks.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-tasks/rpt-member-drawer-tasks.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-tasks/rpt-member-drawer-tasks.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-tasks/rpt-member-drawer-tasks.component.spec.ts deleted file mode 100644 index ec93eacf..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-tasks/rpt-member-drawer-tasks.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptMemberDrawerTasksComponent} from './rpt-member-drawer-tasks.component'; - -describe('RptMemberDrawerTasksComponent', () => { - let component: RptMemberDrawerTasksComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptMemberDrawerTasksComponent] - }); - fixture = TestBed.createComponent(RptMemberDrawerTasksComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-tasks/rpt-member-drawer-tasks.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-tasks/rpt-member-drawer-tasks.component.ts deleted file mode 100644 index 57bf0945..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer-tasks/rpt-member-drawer-tasks.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; - -@Component({ - selector: 'worklenz-rpt-member-drawer-tasks', - templateUrl: './rpt-member-drawer-tasks.component.html', - styleUrls: ['./rpt-member-drawer-tasks.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptMemberDrawerTasksComponent { - @Input() projectId!: string | null; - @Input({required: true}) teamMemberId!: string; -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer.component.html deleted file mode 100644 index b3620b81..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer.component.html +++ /dev/null @@ -1,69 +0,0 @@ - - - -
- - - - - {{member?.name}} - - -
- - - -
    -
  • Projects
  • -
  • Tasks
  • -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer.component.spec.ts deleted file mode 100644 index 411ee476..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptMemberDrawerComponent} from './rpt-member-drawer.component'; - -describe('RptMemberDrawerComponent', () => { - let component: RptMemberDrawerComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptMemberDrawerComponent] - }); - fixture = TestBed.createComponent(RptMemberDrawerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer.component.ts deleted file mode 100644 index c9d0f10a..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer.component.ts +++ /dev/null @@ -1,75 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component} from '@angular/core'; -import {IRPTDuration, IRPTMember, IRPTMemberDrawerData, IRPTOverviewProject, IRPTTeam} from "../../interfaces"; -import {ReportingDrawersService} from "../reporting-drawers.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {ReportingService} from "../../reporting.service"; -import {log_error} from "@shared/utils"; -import {ReportingExportApiService} from "@api/reporting-export-api.service"; -import {ALL_TIME, LAST_MONTH, LAST_QUARTER, LAST_WEEK, YESTERDAY} from "@shared/constants"; -import moment from "moment"; - -@Component({ - selector: 'worklenz-rpt-member-drawer', - templateUrl: './rpt-member-drawer.component.html', - styleUrls: ['./rpt-member-drawer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptMemberDrawerComponent { - member: IRPTMember | null = null; - team: IRPTTeam | null = null; - project: IRPTOverviewProject | null = null; - - show = false; - exporting = false; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly service: ReportingService, - private readonly drawer: ReportingDrawersService, - private readonly exportApiService: ReportingExportApiService, - ) { - this.drawer.onOpenMember - .pipe(takeUntilDestroyed()) - .subscribe(data => { - this.open(data); - }); - } - - close() { - this.show = false; - this.member = null; - this.team = null; - this.project = null; - } - - onSelectProject(project: IRPTOverviewProject) { - if (this.member) - this.drawer.openTasks(project, this.member); - } - - private open(data: IRPTMemberDrawerData) { - this.team = this.service.getCurrentTeam(); - this.member = data.member; - this.project = data.project; - this.show = true - this.cdr.markForCheck(); - } - - async exportProjects() { - if (!this.member || !this.team) return; - try { - await this.exportApiService.exportMemberProjects(this.member.id, this.team.id, this.member.name, this.team.name, this.service.getIncludeToggle()); - } catch (e) { - log_error(e); - } - } - - async exportTasks() { - if (!this.member) return; - try { - await this.exportApiService.exportMemberTasks(this.member.id, this.member.name, this.team?.name, {archived: this.service.getIncludeToggle(), only_single_member: true}); - } catch (e) { - log_error(e); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer.module.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer.module.ts deleted file mode 100644 index 52dad58c..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-member-drawer/rpt-member-drawer.module.ts +++ /dev/null @@ -1,70 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {RptMemberDrawerComponent} from './rpt-member-drawer.component'; -import {RptMemberDrawerOverviewComponent} from './rpt-member-drawer-overview/rpt-member-drawer-overview.component'; -import {RptMemberDrawerProjectsComponent} from './rpt-member-drawer-projects/rpt-member-drawer-projects.component'; -import {RptMemberDrawerTasksComponent} from './rpt-member-drawer-tasks/rpt-member-drawer-tasks.component'; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzTabsModule} from "ng-zorro-antd/tabs"; -import {NzWaveModule} from "ng-zorro-antd/core/wave"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import {NzCardModule} from "ng-zorro-antd/card"; -import {NzGridModule} from "ng-zorro-antd/grid"; -import {ReportingModule} from "../../reporting.module"; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; -import {RptProjectsListModule} from "../common/rpt-projects-list/rpt-projects-list.module"; -import {RptGroupedTaskListModule} from "../common/rpt-grouped-task-list/rpt-grouped-task-list.module"; -import {RptFlatTasksListModule} from "../common/rpt-flat-task-list/rpt-flat-tasks-list.module"; -import {NzBreadCrumbModule} from "ng-zorro-antd/breadcrumb"; -import {RptDrawerTitleComponent} from "../common/rpt-drawer-title/rpt-drawer-title.component"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {NzMenuModule} from "ng-zorro-antd/menu"; -import {EllipsisPipe} from "@pipes/ellipsis.pipe"; -import {NgChartsModule} from "ng2-charts"; -import {FormsModule, ReactiveFormsModule} from "@angular/forms"; -import {NzDatePickerModule} from "ng-zorro-antd/date-picker"; -import {NzFormModule} from "ng-zorro-antd/form"; - -@NgModule({ - declarations: [ - RptMemberDrawerComponent, - RptMemberDrawerOverviewComponent, - RptMemberDrawerProjectsComponent, - RptMemberDrawerTasksComponent - ], - exports: [ - RptMemberDrawerComponent - ], - imports: [ - CommonModule, - NzButtonModule, - NzDrawerModule, - NzIconModule, - NzSpaceModule, - NzTabsModule, - NzWaveModule, - NzBadgeModule, - NzCardModule, - NzGridModule, - ReportingModule, - SafeStringPipe, - RptProjectsListModule, - RptGroupedTaskListModule, - RptFlatTasksListModule, - NzBreadCrumbModule, - RptDrawerTitleComponent, - NzDropDownModule, - NzMenuModule, - EllipsisPipe, - NgChartsModule, - FormsModule, - NzDatePickerModule, - NzFormModule, - ReactiveFormsModule - ] -}) -export class RptMemberDrawerModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-members/rpt-project-drawer-members.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-members/rpt-project-drawer-members.component.html deleted file mode 100644 index 5a5db36c..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-members/rpt-project-drawer-members.component.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - Name - Tasks Count - Completed Tasks - Incomplete Tasks - Overdue Tasks - Contribution - Progress - Logged Time - - - - - {{ data.name }} - {{ data.tasks_count }} - {{ data.completed }} - {{ data.incompleted }} - {{ data.overdue }} - - - - - - - - {{data.time_logged}} - - - - - diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-members/rpt-project-drawer-members.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-members/rpt-project-drawer-members.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-members/rpt-project-drawer-members.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-members/rpt-project-drawer-members.component.spec.ts deleted file mode 100644 index 3a4506c9..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-members/rpt-project-drawer-members.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptProjectDrawerMembersComponent} from './rpt-project-drawer-members.component'; - -describe('RptProjectDrawerMembersComponent', () => { - let component: RptProjectDrawerMembersComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptProjectDrawerMembersComponent] - }); - fixture = TestBed.createComponent(RptProjectDrawerMembersComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-members/rpt-project-drawer-members.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-members/rpt-project-drawer-members.component.ts deleted file mode 100644 index b003ba18..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-members/rpt-project-drawer-members.component.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - OnInit, - Output -} from '@angular/core'; -import {IRPTOverviewProjectMember} from "../../../interfaces"; -import {ReportingApiService} from "../../../reporting-api.service"; - -@Component({ - selector: 'worklenz-rpt-project-drawer-members', - templateUrl: './rpt-project-drawer-members.component.html', - styleUrls: ['./rpt-project-drawer-members.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptProjectDrawerMembersComponent implements OnInit { - @Input({required: true}) projectId: string | null = null; - - @Output() selectMember = new EventEmitter(); - - loading = false; - members: IRPTOverviewProjectMember[] = []; - - searchText!: string; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: ReportingApiService - ) { - } - - ngOnInit() { - void this.get(); - } - - trackBy(index: number, data: IRPTOverviewProjectMember) { - return data.id; - } - - private async get() { - if (!this.projectId) return; - try { - this.loading = true; - const res = await this.api.getProjectMembers(this.projectId); - if (res.done) { - this.members = res.body; - } - this.loading = false; - } catch (e) { - this.loading = false; - } - - this.cdr.markForCheck(); - } - - openMember(member: IRPTOverviewProjectMember) { - if (member) { - this.selectMember.emit(member); - } - } -} - diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-overview/rpt-project-drawer-overview.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-overview/rpt-project-drawer-overview.component.html deleted file mode 100644 index ae57af6b..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-overview/rpt-project-drawer-overview.component.html +++ /dev/null @@ -1,204 +0,0 @@ -
-
- -
    -
  • - - {{model.stats?.completed}} Completed Tasks -
  • -
  • - - {{model.stats?.incompleted}} Incomplete Tasks -
  • -
  • - - {{model.stats?.overdue}} Overdue Tasks -
  • -
  • - - {{model.stats?.total_allocated}} Allocated Hours -
  • -
  • - - {{model.stats?.total_logged}} Logged Hours -
  • -
-
-
-
- - -
-
-

Tasks By Status

-
- - - - - -
-
-
- -
-
- -
- No tasks to show. -
-
-
-
- - -
-
-
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
-
-
-
- - -
-
-

Tasks By Priority

-
- - - - - -
-
-
- -
-
- -
- No tasks to show. -
-
-
-
- - -
-
-
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
-
-
-
- - -
-
-

Tasks By Due Date

-
-
- - - -
-
-
-
- -
-
- -
- No tasks to show. -
-
-
-
- - -
-
-
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-overview/rpt-project-drawer-overview.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-overview/rpt-project-drawer-overview.component.scss deleted file mode 100644 index 73a73e70..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-overview/rpt-project-drawer-overview.component.scss +++ /dev/null @@ -1,25 +0,0 @@ -.stat-icon { - font-size: 24px !important; -} - -.chart { - width: 215px; - max-width: 215px; -} - -.chart-details { - display: flex; - align-items: center; - height: 220px; - max-height: 220px; - overflow-y: auto; -} - -.chart-label { - font-size: 12px; - font-weight: 500; -} - -.no-data-img-holder { - width: 65px; -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-overview/rpt-project-drawer-overview.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-overview/rpt-project-drawer-overview.component.spec.ts deleted file mode 100644 index 993ed4e9..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-overview/rpt-project-drawer-overview.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptProjectDrawerOverviewComponent} from './rpt-project-drawer-overview.component'; - -describe('RptProjectDrawerOverviewComponent', () => { - let component: RptProjectDrawerOverviewComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptProjectDrawerOverviewComponent] - }); - fixture = TestBed.createComponent(RptProjectDrawerOverviewComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-overview/rpt-project-drawer-overview.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-overview/rpt-project-drawer-overview.component.ts deleted file mode 100644 index 58ef7995..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-overview/rpt-project-drawer-overview.component.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - Input, - NgZone, OnDestroy, - OnInit, - ViewChild -} from '@angular/core'; -import {IRPTOverviewProjectInfo} from "../../../interfaces"; -import {ReportingApiService} from "../../../reporting-api.service"; -import {BaseChartDirective} from "ng2-charts"; -import {ChartConfiguration} from "chart.js"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; - -@Component({ - selector: 'worklenz-rpt-project-drawer-overview', - templateUrl: './rpt-project-drawer-overview.component.html', - styleUrls: ['./rpt-project-drawer-overview.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptProjectDrawerOverviewComponent implements OnInit, OnDestroy { - @Input({required: true}) projectId: string | null = null; - - // @ViewChild('statusTaskChart', {static: false}) statusTaskChart!: ElementRef; - // @ViewChild('priorityTaskChart', {static: false}) priorityTaskChart!: ElementRef; - // @ViewChild('dueDateTaskChart', {static: false}) dueDateTaskChart!: ElementRef; - - @ViewChild(BaseChartDirective) statusTaskChart: BaseChartDirective | undefined; - @ViewChild(BaseChartDirective) priorityTaskChart: BaseChartDirective | undefined; - @ViewChild(BaseChartDirective) duedateTasksChart: BaseChartDirective | undefined; - - isStatusChartEmpty = false; - isPriorityChartEmpty = false; - isDueDateChartEmpty = false; - - statusColors: string[] = []; - priorityColors: string[] = []; - duedateColors: string[] = []; - - statusChartData: ChartConfiguration<'doughnut'>['data'] = { - labels: [], - datasets: [{ - label: 'Tasks', - data: [], - backgroundColor: this.statusColors, - hoverOffset: 2 - }] - }; - - priorityChartData: ChartConfiguration<'doughnut'>['data'] = { - labels: [], - datasets: [{ - label: 'Tasks', - data: [], - backgroundColor: this.priorityColors, - hoverOffset: 2 - }] - }; - - duedateChartData: ChartConfiguration<'doughnut'>['data'] = { - labels: [], - datasets: [{ - label: 'Tasks', - data: [], - backgroundColor: this.duedateColors, - hoverOffset: 2 - }] - }; - - chartOptions: ChartConfiguration<'doughnut'>['options'] = { - plugins: { - datalabels: { - display: false - } - }, - responsive: false - } - - loading = false; - model: IRPTOverviewProjectInfo = {}; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: ReportingApiService, - protected readonly ngZone: NgZone, - private readonly socket: Socket, - ) { - } - - ngOnInit() { - void this.get(); - this.listenSockets(); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_STATUS_CHANGE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.QUICK_TASK.toString(), this.refresh); - } - - listenSockets() { - this.socket.on(SocketEvents.TASK_STATUS_CHANGE.toString(), this.refresh); - this.socket.on(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.refresh); - this.socket.on(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.refresh); - this.socket.on(SocketEvents.QUICK_TASK.toString(), this.refresh); - } - - public async get() { - if (!this.projectId) return; - try { - this.loading = true; - this.clearCharts(); - const res = await this.api.getProjectInfo(this.projectId); - if (res.done) { - this.model = res.body; - this.drawCharts(res.body); - } - this.loading = false; - } catch (e) { - this.loading = false; - } - - this.cdr.markForCheck(); - } - - private drawCharts(data: IRPTOverviewProjectInfo) { - if (data.by_status) { - for (const item of data.by_status.chart) { - this.statusChartData.labels?.push(item.name); - this.statusChartData.datasets[0].data.push(item.y || 0); - this.statusColors.push(item.color as string); - } - this.statusTaskChart?.update(); - if (this.statusChartData.datasets[0].data.every(value => value === 0)) - this.isStatusChartEmpty = true; - } - if (data.by_priority) { - for (const item of data.by_priority.chart) { - this.priorityChartData.labels?.push(item.name); - this.priorityChartData.datasets[0].data.push(item.y || 0); - this.priorityColors.push(item.color as string); - } - this.priorityTaskChart?.update(); - - if (this.priorityChartData.datasets[0].data.every(value => value === 0)) - this.isPriorityChartEmpty = true; - } - if (data.by_due) { - for (const item of data.by_due.chart) { - this.duedateChartData.labels?.push(item.name); - this.duedateChartData.datasets[0].data.push(item.y || 0); - this.duedateColors.push(item.color as string); - } - this.duedateTasksChart?.update(); - - if (this.duedateChartData.datasets[0].data.every(value => value === 0)) - this.isDueDateChartEmpty = true; - } - this.cdr.markForCheck(); - } - - openList(group: string) { - if (!this.projectId) return; - this.ngZone.runOutsideAngular(() => { - const a = document.createElement("a"); - if (group === 'status') a.href = `/worklenz/projects/${this.projectId}?group=status`; - if (group === 'priority') a.href = `/worklenz/projects/${this.projectId}?group=priority`; - a.target = "_blank"; - a.click(); - }); - } - - clearCharts() { - this.statusChartData.datasets[0].data = []; - this.priorityChartData.datasets[0].data = []; - this.duedateChartData.datasets[0].data = []; - - this.statusChartData.labels = []; - this.priorityChartData.labels = []; - this.duedateChartData.labels = []; - - this.cdr.markForCheck(); - } - - refresh = (response: any) => { - this.get(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-tasks/rpt-project-drawer-tasks.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-tasks/rpt-project-drawer-tasks.component.html deleted file mode 100644 index 9688c216..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-tasks/rpt-project-drawer-tasks.component.html +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-tasks/rpt-project-drawer-tasks.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-tasks/rpt-project-drawer-tasks.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-tasks/rpt-project-drawer-tasks.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-tasks/rpt-project-drawer-tasks.component.spec.ts deleted file mode 100644 index 10859502..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-tasks/rpt-project-drawer-tasks.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptProjectDrawerTasksComponent} from './rpt-project-drawer-tasks.component'; - -describe('RptProjectDrawerTasksComponent', () => { - let component: RptProjectDrawerTasksComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptProjectDrawerTasksComponent] - }); - fixture = TestBed.createComponent(RptProjectDrawerTasksComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-tasks/rpt-project-drawer-tasks.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-tasks/rpt-project-drawer-tasks.component.ts deleted file mode 100644 index 5b53017a..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer-tasks/rpt-project-drawer-tasks.component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; - -@Component({ - selector: 'worklenz-rpt-project-drawer-tasks', - templateUrl: './rpt-project-drawer-tasks.component.html', - styleUrls: ['./rpt-project-drawer-tasks.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptProjectDrawerTasksComponent { - @Input({required: true}) projectId: string | null = null; -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer.component.html deleted file mode 100644 index 745ce4e2..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer.component.html +++ /dev/null @@ -1,63 +0,0 @@ - - - -
- - - - - {{project?.name}} - - -
- - - -
    -
  • Members
  • -
  • Tasks
  • -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer.component.spec.ts deleted file mode 100644 index 4557986d..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptProjectDrawerComponent} from './rpt-project-drawer.component'; - -describe('RptProjectDrawerComponent', () => { - let component: RptProjectDrawerComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptProjectDrawerComponent] - }); - fixture = TestBed.createComponent(RptProjectDrawerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer.component.ts deleted file mode 100644 index 7a5ab544..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer.component.ts +++ /dev/null @@ -1,75 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component} from '@angular/core'; -import {IRPTMember, IRPTOverviewProject, IRPTOverviewProjectMember, IRPTTeam} from "../../interfaces"; -import {ReportingDrawersService} from "../reporting-drawers.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {ReportingService} from "../../reporting.service"; -import {log_error} from "@shared/utils"; -import {ReportingExportApiService} from "@api/reporting-export-api.service"; - -@Component({ - selector: 'worklenz-rpt-project-drawer', - templateUrl: './rpt-project-drawer.component.html', - styleUrls: ['./rpt-project-drawer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptProjectDrawerComponent { - team: IRPTTeam | null = null; - project: IRPTOverviewProject | null = null; - - exporting = false - - get show() { - return !!this.project; - } - - constructor( - private readonly drawer: ReportingDrawersService, - private readonly service: ReportingService, - private readonly cdr: ChangeDetectorRef, - private readonly exportApi: ReportingExportApiService - ) { - this.drawer.onOpenProject - .pipe(takeUntilDestroyed()) - .subscribe(project => { - this.open(project); - }); - } - - close() { - this.project = null; - } - - onSelectMember(member: IRPTOverviewProjectMember) { - if (this.project) { - const mem = { - id: member.team_member_id, - name: member.name - }; - this.drawer.openTasks(this.project, mem as IRPTMember); - } - } - - private open(project: IRPTOverviewProject | null) { - this.team = this.service.getCurrentTeam(); - this.project = project; - this.cdr.markForCheck(); - } - - async exportMembers() { - if (!this.project?.id) return; - try { - await this.exportApi.exportProjectMembers(this.project.id, this.project.name, this.team?.name); - } catch (e) { - log_error(e); - } - } - - async exportTasks() { - if (!this.project?.id) return; - try { - await this.exportApi.exportProjectTasks(this.project.id, this.project.name, this.team?.name); - } catch (e) { - log_error(e); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer.module.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer.module.ts deleted file mode 100644 index 53703e7f..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-project-drawer/rpt-project-drawer.module.ts +++ /dev/null @@ -1,71 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {RptProjectDrawerComponent} from './rpt-project-drawer.component'; -import {RptProjectDrawerOverviewComponent} from './rpt-project-drawer-overview/rpt-project-drawer-overview.component'; -import {RptProjectDrawerMembersComponent} from './rpt-project-drawer-members/rpt-project-drawer-members.component'; -import {RptProjectDrawerTasksComponent} from './rpt-project-drawer-tasks/rpt-project-drawer-tasks.component'; -import {NzBreadCrumbModule} from "ng-zorro-antd/breadcrumb"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzTabsModule} from "ng-zorro-antd/tabs"; -import {NzWaveModule} from "ng-zorro-antd/core/wave"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import {NzCardModule} from "ng-zorro-antd/card"; -import {NzGridModule} from "ng-zorro-antd/grid"; -import {FormsModule} from "@angular/forms"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NzProgressModule} from "ng-zorro-antd/progress"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzTableModule} from "ng-zorro-antd/table"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {BindNaPipe} from "@pipes/bind-na.pipe"; -import {NzCollapseModule} from "ng-zorro-antd/collapse"; -import {NzSelectModule} from "ng-zorro-antd/select"; -import {RptGroupedTaskListModule} from "../common/rpt-grouped-task-list/rpt-grouped-task-list.module"; -import {RptDrawerTitleComponent} from "../common/rpt-drawer-title/rpt-drawer-title.component"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {NzMenuModule} from "ng-zorro-antd/menu"; -import {NgChartsModule} from "ng2-charts"; - -@NgModule({ - declarations: [ - RptProjectDrawerComponent, - RptProjectDrawerOverviewComponent, - RptProjectDrawerMembersComponent, - RptProjectDrawerTasksComponent - ], - imports: [ - CommonModule, - NzBreadCrumbModule, - NzButtonModule, - NzDrawerModule, - NzIconModule, - NzSpaceModule, - NzTabsModule, - NzWaveModule, - NzBadgeModule, - NzCardModule, - NzGridModule, - FormsModule, - NzInputModule, - NzProgressModule, - NzSkeletonModule, - NzTableModule, - SearchByNamePipe, - BindNaPipe, - NzCollapseModule, - NzSelectModule, - RptGroupedTaskListModule, - RptDrawerTitleComponent, - NzDropDownModule, - NzMenuModule, - NgChartsModule - ], - exports: [ - RptProjectDrawerComponent - ] -}) -export class RptProjectDrawerModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/activity-logs/activity-logs.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/activity-logs/activity-logs.component.html deleted file mode 100644 index eca98da9..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/activity-logs/activity-logs.component.html +++ /dev/null @@ -1,131 +0,0 @@ - - - -
-
- -
- No activity logs to show. -
-
- -
- -
- - - - Updated - - - - Status - Priority - Phase - Start Date - End Date - - - - from - - - - - {{log_item.previous}} - - - - {{log_item.previous}} - - - - {{log_item.previous ? log_item.previous : 'Unmapped'}} - - - - - {{log_item.previous}} - - - None - - - - - - {{log_item.previous}} - - - None - - - - - - to - - - - - {{log_item.current}} - - - - {{log_item.current}} - - - - {{log_item.current ? log_item.current : 'Unmapped'}} - - - - - {{log_item.current}} - - - None - - - - - - {{log_item.current}} - - - None - - - - - - in {{log_item.task_name}} within {{log_item.project_name}} {{log_item.task_key}} - - - - -
- No time logs to show. -
-
-
-
- -
- {{log.log_day | date : 'MMM dd, YYYY'}} -
-
-
- -
-
-
diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/activity-logs/activity-logs.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/activity-logs/activity-logs.component.scss deleted file mode 100644 index 8103a481..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/activity-logs/activity-logs.component.scss +++ /dev/null @@ -1,25 +0,0 @@ -.no-data-img-holder { - max-width: 64px; -} - -.ant-tag.rounded-pill { - border-radius: 100px; - color: rgba(0, 0, 0, 0.85); - margin-right: 0px; -} - -.weight-500 { - font-weight: 500 !important; -} - -.line-28 { - line-height: 28px; -} - -.activity-log-text { - cursor: pointer; - transition: 0.15s all; - &:hover { - color: #1890ff; - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/activity-logs/activity-logs.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/activity-logs/activity-logs.component.spec.ts deleted file mode 100644 index b3b0a8ff..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/activity-logs/activity-logs.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ActivityLogsComponent } from './activity-logs.component'; - -describe('ActivityLogsComponent', () => { - let component: ActivityLogsComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ActivityLogsComponent] - }); - fixture = TestBed.createComponent(ActivityLogsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/activity-logs/activity-logs.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/activity-logs/activity-logs.component.ts deleted file mode 100644 index 9fe87838..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/activity-logs/activity-logs.component.ts +++ /dev/null @@ -1,128 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit} from '@angular/core'; -import {AuthService} from "@services/auth.service"; -import {IRPTMember, ISingleMemberActivityLogs} from "../../../../interfaces"; -import {ReportingApiService} from "../../../../reporting-api.service"; -import {LogHeaderService} from "../service/log-header.service"; -import {ReportingDrawersService} from "../../../reporting-drawers.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {ReportingService} from "../../../../reporting.service"; -import {merge} from "chart.js/helpers"; -import moment from "moment"; -import {LAST_MONTH, LAST_QUARTER, LAST_WEEK, PREV_MONTH, PREV_WEEK, YESTERDAY} from "@shared/constants"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; - -@Component({ - selector: 'worklenz-single-member-activity-logs', - templateUrl: './activity-logs.component.html', - styleUrls: ['./activity-logs.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ActivityLogsComponent implements OnInit { - @Input({required: true}) member: IRPTMember | null = null; - @Input() isDurationLabelSelected = true; - - loading = false; - activityLogs: ISingleMemberActivityLogs[] = []; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: ReportingApiService, - private readonly auth: AuthService, - public readonly service: LogHeaderService, - private readonly drawer: ReportingDrawersService, - private readonly reportingService: ReportingService, - private readonly taskView: TaskViewService, - ) { - this.reportingService.onDrawerDurationChange.pipe(takeUntilDestroyed()).subscribe(async () => { - await this.getActivities(); - }); - - this.reportingService.onDrawerDateRangeChange.pipe(takeUntilDestroyed()).subscribe(async () => { - await this.getActivities(); - }); - - this.taskView.onRefresh.pipe(takeUntilDestroyed()) - .subscribe(async () => { - await this.getActivities(); - }); - - } - - async ngOnInit() { - await this.getActivities(); - } - - private async setDatesForKeys() { - if (this.reportingService.getDrawerDuration()?.key) { - const key = this.reportingService.getDrawerDuration()?.key; - const today = moment(); - - switch (key) { - case YESTERDAY: - const yesterday = moment().subtract(1, "days"); - this.reportingService.setDrawerDateRange([yesterday.toString(), yesterday.toString()]); - break; - case LAST_WEEK: - const lastWeekStart = moment().subtract(1, "weeks"); - this.reportingService.setDrawerDateRange([lastWeekStart.toString(), today.toString()]); - break; - case LAST_MONTH: - const lastMonthStart = moment().subtract(1, "months"); - this.reportingService.setDrawerDateRange([lastMonthStart.toString(), today.toString()]); - break; - case LAST_QUARTER: - const lastQuaterStart = moment().subtract(3, "months"); - this.reportingService.setDrawerDateRange([lastQuaterStart.toString(), today.toString()]); - break; - case PREV_WEEK: - const prevWeekStart = moment().subtract(1, "weeks").startOf("week"); - const prevWeekEnd = moment().subtract(1, "weeks").endOf("week"); - this.reportingService.setDrawerDateRange([prevWeekStart.toString(), prevWeekEnd.toString()]); - break; - case PREV_MONTH: - const prevMonthStart = moment().subtract(1, "month").startOf("month"); - const prevMonthEnd = moment().subtract(1, "month").endOf("month"); - this.reportingService.setDrawerDateRange([prevMonthStart.toString(), prevMonthEnd.toString()]); - break; - } - } - } - - public async getActivities() { - if (!this.member) return; - - try { - this.loading = true; - const teamId = this.auth.getCurrentSession()?.team_id; - const body = { - team_member_id: this.member.id, - team_id: teamId as string, - duration: this.reportingService.getDrawerDuration()?.key, - date_range: this.reportingService.getDrawerDateRange(), - archived: this.reportingService.getIncludeToggle() - } - const res = await this.api.getSingleMemberActivities(body); - if (res.done) { - - res.body.sort((a, b) => { - const dateA = new Date(a.log_day); - const dateB = new Date(b.log_day); - return dateB.getTime() - dateA.getTime(); - }); - - this.activityLogs = res.body; - } - this.loading = false; - this.cdr.markForCheck(); - } catch (e) { - this.loading = false; - this.cdr.markForCheck(); - } - } - - openTask(taskId: string, projectId: string) { - if (taskId && projectId) - this.drawer.openTask({taskId: taskId, projectId: projectId}); - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/duration-filter/duration-filter.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/duration-filter/duration-filter.component.html deleted file mode 100644 index ff6f0188..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/duration-filter/duration-filter.component.html +++ /dev/null @@ -1,34 +0,0 @@ -
-
-
- -
-
-
- - -
    -
  • - {{item.label}} -
  • -
  • -
  • -
    - -
    - -
    - -
    -
  • -
-
diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/duration-filter/duration-filter.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/duration-filter/duration-filter.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/duration-filter/duration-filter.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/duration-filter/duration-filter.component.spec.ts deleted file mode 100644 index dec261ec..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/duration-filter/duration-filter.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { DurationFilterComponent } from './duration-filter.component'; - -describe('DurationFilterComponent', () => { - let component: DurationFilterComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [DurationFilterComponent] - }); - fixture = TestBed.createComponent(DurationFilterComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/duration-filter/duration-filter.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/duration-filter/duration-filter.component.ts deleted file mode 100644 index e39e69ad..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/duration-filter/duration-filter.component.ts +++ /dev/null @@ -1,66 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, OnInit, Output} from '@angular/core'; -import {IRPTDuration} from "../../../../interfaces"; -import moment from "moment/moment"; -import {ReportingService} from "../../../../reporting.service"; -import {ReportingExportApiService} from "@api/reporting-export-api.service"; -import {AuthService} from "@services/auth.service"; -import {LogHeaderService} from "../service/log-header.service"; - -@Component({ - selector: 'worklenz-single-member-duration-filter', - templateUrl: './duration-filter.component.html', - styleUrls: ['./duration-filter.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class DurationFilterComponent implements OnInit { - @Output() getResult = new EventEmitter(); - - constructor( - private readonly cdr: ChangeDetectorRef, - public readonly service: LogHeaderService, - private readonly reportingService: ReportingService, - private readonly reportingExportApi: ReportingExportApiService, - private readonly auth: AuthService - ) { - } - - ngOnInit() { - this.setMembersDrawerDuration(); - this.cdr.markForCheck(); - } - - - private setMembersDrawerDuration() { - - const globalDuration = this.reportingService.getDuration()?.key; - const globalDateRange = this.reportingService.getDateRange(); - - if (globalDateRange.length === 2) { - this.service.dateRange = globalDateRange - this.cdr.markForCheck(); - } else { - const selectedItem = this.service.durations.find(d => d.key === globalDuration); - if (selectedItem) this.service.selectedDuration = selectedItem; - this.cdr.markForCheck(); - } - } - - onDurationChange(item: IRPTDuration) { - this.service.dateRange = []; - this.service.selectedDuration = item; - this.service.emitDurationChange(); - this.cdr.markForCheck(); - } - - get durationLabel() { - const f = "yy-MM-DD"; - if (this.service.dateRange.length == 2) - return `${moment(this.service.dateRange[0]).format(f)} - ${moment(this.service.dateRange[1]).format(f)}`; - return this.service.selectedDuration ? this.service.selectedDuration.label : "Duration"; - } - - customDateChange() { - this.service.emitDurationChange(); - this.cdr.markForCheck(); - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/rpt-single-member-drawer-overview.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/rpt-single-member-drawer-overview.component.html deleted file mode 100644 index 8ef0e084..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/rpt-single-member-drawer-overview.component.html +++ /dev/null @@ -1,181 +0,0 @@ -
-
- -
    -
  • - - {{model.stats?.projects || 0}} Projects -
  • -
  • - - {{model.stats?.assigned || 0}} Assigned Tasks -
  • -
  • - - {{model.stats?.completed || 0}} Completed Tasks -
  • -
  • - - {{model.stats?.ongoing || 0}} Ongoing Tasks -
  • -
  • - - {{model.stats?.overdue || 0}} Overdue Tasks -
  • -
  • - - {{model.stats?.total_logged || 0}} Logged Hours -
  • -
-
-
- - -
- - -
-
-

Tasks By Projects

-
-
-
-
- -
-
- -
- No tasks to show. -
-
-
-
- - -
-
-
    -
  • - -
  • -
  • - - {{(project.label | ellipsis : 15) + ' (' + (project.count || 0) + ')'}} -
  • -
-
-
-
-
-
- - -
- - -
-
-

Tasks By Priority

-
-
-
-
- -
-
- -
- No tasks to show. -
-
-
-
- - -
-
-
    -
  • - -
  • -
  • - -
  • -
-
-
-
-
-
- - -
- - -
-
-

Tasks By Status

-
-
-
-
- -
-
- -
- No tasks to show. -
-
-
-
- - -
-
-
    -
  • - -
  • -
  • - -
  • -
-
-
-
-
-
- -
diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/rpt-single-member-drawer-overview.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/rpt-single-member-drawer-overview.component.scss deleted file mode 100644 index d39d7d28..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/rpt-single-member-drawer-overview.component.scss +++ /dev/null @@ -1,16 +0,0 @@ -.chart-details { - max-height: 220px; - overflow-y: auto; -} - -.no-data-img-holder { - width: 65px; -} - -.list-clickable { - cursor: pointer; - transition: 0.25s all; - &:hover { - color: #1890ff; - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/rpt-single-member-drawer-overview.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/rpt-single-member-drawer-overview.component.spec.ts deleted file mode 100644 index eb468349..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/rpt-single-member-drawer-overview.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { RptSingleMemberDrawerOverviewComponent } from './rpt-single-member-drawer-overview.component'; - -describe('RptSingleMemberDrawerOverviewComponent', () => { - let component: RptSingleMemberDrawerOverviewComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptSingleMemberDrawerOverviewComponent] - }); - fixture = TestBed.createComponent(RptSingleMemberDrawerOverviewComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/rpt-single-member-drawer-overview.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/rpt-single-member-drawer-overview.component.ts deleted file mode 100644 index 4fbbd284..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/rpt-single-member-drawer-overview.component.ts +++ /dev/null @@ -1,283 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Input, - NgZone, - OnDestroy, - OnInit, - ViewChild -} from '@angular/core'; -import {IRPTOverviewMemberInfo} from "../../../interfaces"; -import {ReportingApiService} from "../../../reporting-api.service"; -import {BaseChartDirective} from "ng2-charts"; -import {ChartConfiguration} from "chart.js"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {LogHeaderService} from "./service/log-header.service"; -import {ReportingService} from "../../../reporting.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import moment from "moment/moment"; -import {LAST_MONTH, LAST_QUARTER, LAST_WEEK, PREV_MONTH, PREV_WEEK, YESTERDAY} from "@shared/constants"; -import {ReportingDrawersService} from "../../reporting-drawers.service"; -import {merge} from "rxjs"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; - -@Component({ - selector: 'worklenz-rpt-single-member-drawer-overview', - templateUrl: './rpt-single-member-drawer-overview.component.html', - styleUrls: ['./rpt-single-member-drawer-overview.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptSingleMemberDrawerOverviewComponent implements OnInit, OnDestroy { - @Input({required: true}) teamMemberId: string | null = null; - @Input() isDurationLabelSelected = true; - @Input() isDurationLabelSelected_ = false; - - @ViewChild(BaseChartDirective) projectsTaskChart: BaseChartDirective | undefined; - @ViewChild(BaseChartDirective) priorityTaskChart: BaseChartDirective | undefined; - @ViewChild(BaseChartDirective) statusTaskChart: BaseChartDirective | undefined; - - isProjectsChartEmpty = false; - isPriorityChartEmpty = false; - isStatusChartEmpty = false; - - projectColors: string[] = []; - priorityColors: string[] = []; - statusColors: string[] = []; - - projectsChartData: ChartConfiguration<'doughnut'>['data'] = { - labels: [], - datasets: [{ - label: 'Tasks', - data: [], - backgroundColor: this.projectColors, - hoverOffset: 2 - }] - }; - - prioritiesChartData: ChartConfiguration<'doughnut'>['data'] = { - labels: [], - datasets: [{ - label: 'Tasks', - data: [], - backgroundColor: this.priorityColors, - hoverOffset: 2 - }] - }; - - statusChartData: ChartConfiguration<'doughnut'>['data'] = { - labels: [], - datasets: [{ - label: 'Tasks', - data: [], - backgroundColor: this.statusColors, - hoverOffset: 2 - }] - }; - - chartOptions: ChartConfiguration<'doughnut'>['options'] = { - plugins: { - datalabels: { - display: false - } - }, - responsive: false - } - - loading = false; - model: IRPTOverviewMemberInfo = {}; - - constructor( - protected readonly ngZone: NgZone, - private readonly cdr: ChangeDetectorRef, - private readonly api: ReportingApiService, - private readonly socket: Socket, - public readonly service: LogHeaderService, - private readonly reportingService: ReportingService, - private readonly drawerService: ReportingDrawersService, - private readonly taskView: TaskViewService, - ) { - this.reportingService.onDrawerDurationChange.pipe(takeUntilDestroyed()).subscribe(async () => { - await this.get(); - }); - this.reportingService.onDrawerDateRangeChange.pipe(takeUntilDestroyed()).subscribe(async () => { - await this.get(); - }); - - merge( - this.taskView.onRefresh - ).pipe(takeUntilDestroyed()) - .subscribe(async () => { - await this.get(); - }); - - } - - ngOnInit() { - void this.get(); - this.listenSockets(); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_STATUS_CHANGE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.TASK_PHASE_CHANGE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.TASK_TIME_ESTIMATION_CHANGE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.TASK_TIMER_STOP.toString(), this.refresh); - this.socket.removeListener(SocketEvents.QUICK_TASK.toString(), this.refresh); - this.socket.removeListener(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), this.refresh); - } - - listenSockets() { - this.socket.on(SocketEvents.TASK_STATUS_CHANGE.toString(), this.refresh); - this.socket.on(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.refresh); - this.socket.on(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.refresh); - this.socket.on(SocketEvents.TASK_PHASE_CHANGE.toString(), this.refresh); - this.socket.on(SocketEvents.TASK_TIME_ESTIMATION_CHANGE.toString(), this.refresh); - this.socket.on(SocketEvents.TASK_TIMER_STOP.toString(), this.refresh); - this.socket.on(SocketEvents.QUICK_TASK.toString(), this.refresh); - this.socket.on(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), this.refresh); - } - - private async setDatesForKeys() { - if (this.reportingService.getDrawerDuration()?.key) { - const key = this.reportingService.getDrawerDuration()?.key; - const today = moment(); - - switch (key) { - case YESTERDAY: - const yesterday = moment().subtract(1, "days"); - this.reportingService.setDrawerDateRange([yesterday.toString(), yesterday.toString()]); - break; - case LAST_WEEK: - const lastWeekStart = moment().subtract(1, "weeks"); - this.reportingService.setDrawerDateRange([lastWeekStart.toString(), today.toString()]); - break; - case LAST_MONTH: - const lastMonthStart = moment().subtract(1, "months"); - this.reportingService.setDrawerDateRange([lastMonthStart.toString(), today.toString()]); - break; - case LAST_QUARTER: - const lastQuaterStart = moment().subtract(3, "months"); - this.reportingService.setDrawerDateRange([lastQuaterStart.toString(), today.toString()]); - break; - case PREV_WEEK: - const prevWeekStart = moment().subtract(1, "weeks").startOf("week"); - const prevWeekEnd = moment().subtract(1, "weeks").endOf("week"); - this.reportingService.setDrawerDateRange([prevWeekStart.toString(), prevWeekEnd.toString()]); - break; - case PREV_MONTH: - const prevMonthStart = moment().subtract(1, "month").startOf("month"); - const prevMonthEnd = moment().subtract(1, "month").endOf("month"); - this.reportingService.setDrawerDateRange([prevMonthStart.toString(), prevMonthEnd.toString()]); - break; - } - } - } - - public async get() { - if (!this.teamMemberId) return; - try { - this.loading = true; - this.clearCharts(); - - if (this.isDurationLabelSelected) { - await this.setDatesForKeys(); - } - const res = await this.api.getMemberInfo({ - teamMemberId: this.teamMemberId, - duration: this.reportingService.getDrawerDuration()?.key, - date_range: this.reportingService.getDrawerDateRange(), - archived: this.reportingService.getIncludeToggle() - }); - if (res.done) { - this.model = res.body; - this.drawCharts(res.body); - } - this.loading = false; - } catch (e) { - this.loading = false; - } - - this.cdr.markForCheck(); - } - - private drawCharts(data: IRPTOverviewMemberInfo) { - if (data.by_project) { - for (const item of data.by_project.chart) { - this.projectsChartData.labels?.push(item.name); - this.projectsChartData.datasets[0].data.push(item.y || 0); - this.projectColors.push(item.color as string); - } - this.projectsTaskChart?.update(); - if (this.projectsChartData.datasets[0].data.length === 0) { - this.isProjectsChartEmpty = true; - this.cdr.markForCheck(); - } else { - this.isProjectsChartEmpty = false; - } - } - if (data.by_priority) { - for (const item of data.by_priority.chart) { - this.prioritiesChartData.labels?.push(item.name); - this.prioritiesChartData.datasets[0].data.push(item.y || 0); - this.priorityColors.push(item.color as string); - } - this.priorityTaskChart?.update(); - if (this.prioritiesChartData.datasets[0].data.every(value => value === 0)) { - this.isPriorityChartEmpty = true; - } else { - this.isPriorityChartEmpty = false; - } - } - if (data.by_status) { - for (const item of data.by_status.chart) { - this.statusChartData.labels?.push(item.name); - this.statusChartData.datasets[0].data.push(item.y || 0); - this.statusColors.push(item.color as string); - } - this.statusTaskChart?.update(); - if (this.statusChartData.datasets[0].data.every(value => value === 0)) { - this.isStatusChartEmpty = true; - } else { - this.isStatusChartEmpty = false; - } - - } - this.cdr.markForCheck(); - } - - openTaskStatDrawer() { - if (!this.teamMemberId) return; - this.drawerService.openSingleMemberTaskStat({team_member_id: this.teamMemberId}); - } - - openProjects() { - if (!this.teamMemberId) return; - this.drawerService.openSingleMemberProjects({team_member_id: this.teamMemberId}); - } - - openTimeLogsTab() { - if (!this.teamMemberId) return; - this.drawerService.openTimeLogsTab(); - } - - clearCharts() { - this.projectsChartData.datasets[0].data = []; - this.prioritiesChartData.datasets[0].data = []; - this.statusChartData.datasets[0].data = []; - - this.projectsChartData.labels = []; - this.prioritiesChartData.labels = []; - this.statusChartData.labels = []; - - this.cdr.markForCheck(); - } - - refresh = (response: any) => { - this.get(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/service/log-header.service.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/service/log-header.service.ts deleted file mode 100644 index 03dc7f6b..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/service/log-header.service.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {Injectable} from '@angular/core'; -import {IRPTDuration} from "../../../../interfaces"; -import {ALL_TIME, LAST_MONTH, LAST_QUARTER, LAST_WEEK, YESTERDAY} from "@shared/constants"; -import {Subject} from "rxjs"; - -@Injectable({ - providedIn: 'root' -}) -export class LogHeaderService { - - public dateRange: string[] = []; - public durations: IRPTDuration[] = [ - {label: "Yesterday", key: YESTERDAY}, - {label: "Last 7 days", key: LAST_WEEK}, - {label: "Last 30 days", key: LAST_MONTH}, - {label: "Last 3 months", key: LAST_QUARTER}, - {label: "All time", key: ALL_TIME} - ]; - public selectedDuration: IRPTDuration | null = null; - - public readonly _durationChange = new Subject(); - - get onDurationChange() { - return this._durationChange.asObservable(); - } - - constructor() { - } - - public emitDurationChange() { - this._durationChange.next(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/time-logs/single-member-time-logs.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/time-logs/single-member-time-logs.component.html deleted file mode 100644 index 411366bb..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/time-logs/single-member-time-logs.component.html +++ /dev/null @@ -1,38 +0,0 @@ - - - -
-
- -
- No time logs to show. -
-
- -
- -
- - - Logged {{log_item.time_spent_string}} for {{log_item.task_name}} in {{log_item.project_name}} - {{log_item.task_key}} - - - -
- No time logs to show. -
-
-
-
- -
- {{log.log_day | date : 'MMM dd, YYYY'}} -
-
-
- -
-
-
diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/time-logs/single-member-time-logs.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/time-logs/single-member-time-logs.component.scss deleted file mode 100644 index e93d77e7..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/time-logs/single-member-time-logs.component.scss +++ /dev/null @@ -1,18 +0,0 @@ -.no-data-img-holder { - max-width: 64px; -} - -.weight-500 { - font-weight: 500 !important; -} -.line-28 { - line-height: 28px; -} - -.time-log-text { - cursor: pointer; - transition: 0.15s all; - &:hover { - color: #1890ff; - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/time-logs/single-member-time-logs.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/time-logs/single-member-time-logs.component.spec.ts deleted file mode 100644 index 30ea6733..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/time-logs/single-member-time-logs.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { SingleMemberTimeLogsComponent } from './single-member-time-logs.component'; - -describe('SingleMemberTimeLogsComponent', () => { - let component: SingleMemberTimeLogsComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [SingleMemberTimeLogsComponent] - }); - fixture = TestBed.createComponent(SingleMemberTimeLogsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/time-logs/single-member-time-logs.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/time-logs/single-member-time-logs.component.ts deleted file mode 100644 index ac4248f0..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer-overview/time-logs/single-member-time-logs.component.ts +++ /dev/null @@ -1,139 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit} from '@angular/core'; -import {IRPTMember, ISingleMemberLogs} from "../../../../interfaces"; -import {ReportingApiService} from "../../../../reporting-api.service"; -import {AuthService} from "@services/auth.service"; -import {LogHeaderService} from "../service/log-header.service"; -import {ReportingDrawersService} from "../../../reporting-drawers.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {ReportingService} from "../../../../reporting.service"; -import moment from "moment"; -import {LAST_MONTH, LAST_QUARTER, LAST_WEEK, PREV_MONTH, PREV_WEEK, YESTERDAY} from "@shared/constants"; -import {merge} from "rxjs"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; - -@Component({ - selector: 'worklenz-single-member-time-logs', - templateUrl: './single-member-time-logs.component.html', - styleUrls: ['./single-member-time-logs.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class SingleMemberTimeLogsComponent implements OnInit { - @Input({required: true}) member: IRPTMember | null = null; - @Input() isDurationLabelSelected = true; - - loading = false; - exporting = false; - - timeLogs: ISingleMemberLogs[] = [] - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: ReportingApiService, - private readonly auth: AuthService, - public readonly service: LogHeaderService, - public readonly drawer: ReportingDrawersService, - private readonly reportingService: ReportingService, - private readonly taskView: TaskViewService, - ) { - this.reportingService.onDrawerDateRangeChange.pipe(takeUntilDestroyed()).subscribe(async () => { - await this.getTimeLogs(); - }); - - this.reportingService.onDrawerDurationChange.pipe(takeUntilDestroyed()).subscribe(async () => { - await this.getTimeLogs(); - }); - - merge( - this.taskView.onRefresh - ).pipe(takeUntilDestroyed()) - .subscribe(async () => { - await this.getTimeLogs(); - }); - - } - - async ngOnInit() { - await this.getTimeLogs(); - } - - private async setDatesForKeys() { - if (this.reportingService.getDrawerDuration()?.key) { - const key = this.reportingService.getDrawerDuration()?.key; - const today = moment(); - - switch (key) { - case YESTERDAY: - const yesterday = moment().subtract(1, "days"); - this.reportingService.setDrawerDateRange([yesterday.toString(), yesterday.toString()]); - break; - case LAST_WEEK: - const lastWeekStart = moment().subtract(1, "weeks"); - this.reportingService.setDrawerDateRange([lastWeekStart.toString(), today.toString()]); - break; - case LAST_MONTH: - const lastMonthStart = moment().subtract(1, "months"); - this.reportingService.setDrawerDateRange([lastMonthStart.toString(), today.toString()]); - break; - case LAST_QUARTER: - const lastQuaterStart = moment().subtract(3, "months"); - this.reportingService.setDrawerDateRange([lastQuaterStart.toString(), today.toString()]); - break; - case PREV_WEEK: - const prevWeekStart = moment().subtract(1, "weeks").startOf("week"); - const prevWeekEnd = moment().subtract(1, "weeks").endOf("week"); - this.reportingService.setDrawerDateRange([prevWeekStart.toString(), prevWeekEnd.toString()]); - break; - case PREV_MONTH: - const prevMonthStart = moment().subtract(1, "month").startOf("month"); - const prevMonthEnd = moment().subtract(1, "month").endOf("month"); - this.reportingService.setDrawerDateRange([prevMonthStart.toString(), prevMonthEnd.toString()]); - break; - } - } - } - - public async getTimeLogs() { - if (!this.member) return; - - try { - this.loading = true; - const teamId = this.auth.getCurrentSession()?.team_id; - - - if (this.isDurationLabelSelected) { - await this.setDatesForKeys(); - } - - const body = { - team_member_id: this.member.id, - team_id: teamId as string, - duration: this.reportingService.getDrawerDuration()?.key, - date_range: this.reportingService.getDrawerDateRange(), - archived: this.reportingService.getIncludeToggle() - } - const res = await this.api.getSingleMemberTimeLogs(body); - if (res.done) { - - res.body.sort((a, b) => { - const dateA = new Date(a.log_day); - const dateB = new Date(b.log_day); - return dateB.getTime() - dateA.getTime(); - }); - - this.timeLogs = res.body; - } - this.loading = false; - this.cdr.markForCheck(); - } catch (e) { - this.loading = false; - this.cdr.markForCheck(); - } - } - - openTask(taskId: string, projectId: string) { - if (taskId && projectId) - this.drawer.openTask({taskId: taskId, projectId: projectId}); - } - - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer.component.html deleted file mode 100644 index f1284cf1..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer.component.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - -
- - - {{member?.name}} - - -
- - - - -
    -
  • Time Logs
  • -
  • Activity Logs
  • -
  • Tasks
  • -
-
-
-
-
-
- - - - - - -
- - - - - - -
-
-
- - - -
    -
  • - {{item.label}} {{item.dates}} -
  • -
  • -
  • -
    - -
    - -
    - -
    -
  • -
-
diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer.component.spec.ts deleted file mode 100644 index ad4cf75b..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { RptSingleMemberDrawerComponent } from './rpt-single-member-drawer.component'; - -describe('RptSingleMemberDrawerComponent', () => { - let component: RptSingleMemberDrawerComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptSingleMemberDrawerComponent] - }); - fixture = TestBed.createComponent(RptSingleMemberDrawerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer.component.ts deleted file mode 100644 index e615a816..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer.component.ts +++ /dev/null @@ -1,268 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output} from '@angular/core'; -import {ReportingService} from "../../reporting.service"; -import {ReportingDrawersService} from "../reporting-drawers.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import { - IRPTDuration, - IRPTMember, - IRPTOverviewProject, - IRPTSingleMemberDrawerData, - IRPTTeam -} from "../../interfaces"; -import {ReportingExportApiService} from "@api/reporting-export-api.service"; -import {log_error} from "@shared/utils"; -import {LogHeaderService} from "./rpt-single-member-drawer-overview/service/log-header.service"; -import {AuthService} from "@services/auth.service"; -import {ALL_TIME, LAST_MONTH, LAST_QUARTER, LAST_WEEK, PREV_MONTH, PREV_WEEK, YESTERDAY} from "@shared/constants"; -import moment from "moment/moment"; - -@Component({ - selector: 'worklenz-rpt-single-member-drawer', - templateUrl: './rpt-single-member-drawer.component.html', - styleUrls: ['./rpt-single-member-drawer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptSingleMemberDrawerComponent { - @Output() selectProject = new EventEmitter(); - @Input() isDurationLabelSelected = true; - - member: IRPTMember | null = null; - team: IRPTTeam | null = null; - - show = false; - exporting = false; - isDurationLabelSelected_ = false; - - readonly tabs = [ - {label: 'Overview', tab: 'overview'}, - {label: 'Time Logs', tab: 'time-logs'}, - {label: 'Activity Logs', tab: 'activity-logs'}, - {label: 'Tasks', tab: 'tasks'} - ]; - - selectedTab = 0; - - dateRange: string[] = []; - - durations: IRPTDuration[] = [ - {label: "Yesterday", key: YESTERDAY, dates: moment().subtract(1, "days").format('MMM,DD YYYY').toString()}, - {label: "Last 7 days", key: LAST_WEEK, dates: moment().subtract(1, "weeks").format('MMM,DD YYYY').toString() + " - " + moment().format('MMM,DD YYYY').toString()}, - {label: "Last week", key: PREV_WEEK, dates: moment().subtract(1, "weeks").startOf("week").format('MMM,DD YYYY').toString() + " - " + moment().subtract(1, "weeks").endOf("week").format('MMM,DD YYYY').toString()}, - {label: "Last 30 days", key: LAST_MONTH, dates: moment().subtract(1, "month").format('MMM,DD YYYY').toString() + " - " + moment().format('MMM,DD YYYY').toString()}, - {label: "Last month", key: PREV_MONTH, dates: moment().subtract(1, "month").startOf("month").format('MMM,DD YYYY').toString() + " - " + moment().subtract(1, "month").endOf("month").format('MMM,DD YYYY').toString()}, - {label: "Last 3 months", key: LAST_QUARTER, dates: moment().subtract(3, "months").format('MMM,DD YYYY').toString() + " - " + moment().format('MMM,DD YYYY').toString()}, - {label: "All time", key: ALL_TIME, dates: ''} - ]; - - get durationLabel() { - const f = "yy-MM-DD"; - if (this.dateRange.length == 2) - return `${moment(this.dateRange[0]).format(f)} - ${moment(this.dateRange[1]).format(f)}`; - return this.selectedDuration ? this.selectedDuration.label : "Duration"; - } - - get selectedDuration() { - return this.api.getDrawerDuration(); - } - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly service: ReportingService, - private readonly drawer: ReportingDrawersService, - private readonly exportApiService: ReportingExportApiService, - private readonly headerService: LogHeaderService, - private readonly auth: AuthService, - private api: ReportingService - ) { - this.drawer.onOpenSingleMember - .pipe(takeUntilDestroyed()) - .subscribe(data => { - this.open(data); - }); - this.drawer.onOpenSingleMemberTimeLogs.pipe(takeUntilDestroyed()).subscribe(() => { - this.selectedTab = 1; - this.cdr.markForCheck(); - }) - } - - - private async open(data: IRPTSingleMemberDrawerData) { - await this.setInitialValues(); - this.team = this.service.getCurrentTeam(); - this.member = data.member; - this.show = true - this.cdr.markForCheck(); - } - - closeDrawer() { - this.show = false; - this.member = null; - this.team = null; - this.selectedTab = 0; - this.headerService.dateRange = []; - } - - async exportTimeLogs() { - if (!this.member || !this.auth.getCurrentSession()?.team_id) return; - try { - this.exporting = true; - if (this.isDurationLabelSelected) { - await this.setDatesForKeys(); - } - const body = { - team_member_id: this.member.id, - team_id: this.auth.getCurrentSession()?.team_id as string, - duration: this.service.getDrawerDuration()?.key, - date_range: this.service.getDrawerDateRange(), - member_name: this.member.name, - team_name: this.team?.name ? this.team.name : null, - archived: this.service.getIncludeToggle() - } - this.exportApiService.exportMemberTimeLogs(body); - this.exporting = false; - this.cdr.markForCheck(); - } catch (e) { - this.exporting = false; - log_error(e); - this.cdr.markForCheck(); - } - } - - async exportActivityLogs() { - if (!this.member || !this.auth.getCurrentSession()?.team_id) return; - try { - this.exporting = true; - if (this.isDurationLabelSelected) { - await this.setDatesForKeys(); - } - const body = { - team_member_id: this.member.id, - team_id: this.auth.getCurrentSession()?.team_id as string, - duration: this.service.getDrawerDuration()?.key, - date_range: this.service.getDrawerDateRange(), - member_name: this.member.name, - team_name: this.team?.name ? this.team.name : null, - archived: this.service.getIncludeToggle() - } - this.exportApiService.exportMemberActivityLogs(body); - this.exporting = false; - this.cdr.markForCheck(); - } catch (e) { - this.exporting = false; - log_error(e); - this.cdr.markForCheck(); - } - } - - async exportTasks() { - if (!this.member) return; - try { - this.exporting = true; - if (this.isDurationLabelSelected) { - await this.setDatesForKeys(); - } - const body = { - duration: this.service.getDrawerDuration()?.key, - date_range: this.service.getDrawerDateRange(), - only_single_member: true, - archived: this.service.getIncludeToggle() - } - this.exportApiService.exportMemberTasks(this.member.id, this.member.name, this.team?.name, body); - this.exporting = false; - } catch (e) { - this.exporting = false; - log_error(e); - } - } - - setInitialValues() { - - const globalDateRange = this.api.getDrawerDateRange(); - if (globalDateRange.length === 2 && !this.isDurationLabelSelected) { - this.dateRange = globalDateRange - } - - if (!this.api.getDrawerDuration()) - return this.api.setDrawerDuration(this.durations.find(d => d.key === LAST_WEEK) || null); - - this.api.setDrawerDuration(this.api.getDrawerDuration()); - - } - - selectedTabChange(index: number) { - this.selectedTab = index; - - switch (this.selectedTab) { - case 0: - this.drawer.emitGetSingleMemberOverview(); - break; - case 1: - this.drawer.emitGetSingleMemberTimeLogs(); - break; - case 2: - this.drawer.emitGetSingleMemberActivityLogs(); - break; - case 3: - this.drawer.emitGetSingleMemberProjects(); - break; - case 4: - this.drawer.emitGetSingleMemberTasks(); - break; - } - } - - onDurationChange(item: IRPTDuration) { - this.isDurationLabelSelected = true; - setTimeout( () => { - this.api.setDrawerDuration(item); - this.dateRange = []; - this.api.setDrawerDateRange(this.dateRange); - this.api.emitDrawerDurationChanged(); - }, 500); - } - - customDateChange() { - this.isDurationLabelSelected = false; - setTimeout( () => { - this.api.setDrawerDateRange(this.dateRange); - this.api.emitDrawerDateRangeChanged(); - }, 500); - } - - private async setDatesForKeys() { - if(this.service.getDrawerDuration()?.key) { - const key = this.service.getDrawerDuration()?.key; - const today = moment(); - - switch (key) { - case YESTERDAY: - const yesterday = moment().subtract(1, "days"); - this.service.setDrawerDateRange([yesterday.toString(), yesterday.toString()]); - break; - case LAST_WEEK: - const lastWeekStart = moment().subtract(1, "weeks"); - this.service.setDrawerDateRange([lastWeekStart.toString(), today.toString()]); - break; - case LAST_MONTH: - const lastMonthStart = moment().subtract(1, "months"); - this.service.setDrawerDateRange([lastMonthStart.toString(), today.toString()]); - break; - case LAST_QUARTER: - const lastQuaterStart = moment().subtract(3, "months"); - this.service.setDrawerDateRange([lastQuaterStart.toString(), today.toString()]); - break; - case PREV_WEEK: - const prevWeekStart = moment().subtract(1, "weeks").startOf("week"); - const prevWeekEnd = moment().subtract(1, "weeks").endOf("week"); - this.service.setDrawerDateRange([prevWeekStart.toString(), prevWeekEnd.toString()]); - break; - case PREV_MONTH: - const prevMonthStart = moment().subtract(1, "month").startOf("month"); - const prevMonthEnd = moment().subtract(1, "month").endOf("month"); - this.service.setDrawerDateRange([prevMonthStart.toString(), prevMonthEnd.toString()]); - break; - } - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer.module.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer.module.ts deleted file mode 100644 index 260478d5..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-drawer/rpt-single-member-drawer.module.ts +++ /dev/null @@ -1,79 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {RptSingleMemberDrawerComponent} from './rpt-single-member-drawer.component'; -import {FormsModule} from "@angular/forms"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzDatePickerModule} from "ng-zorro-antd/date-picker"; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzMenuModule} from "ng-zorro-antd/menu"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzTabsModule} from "ng-zorro-antd/tabs"; -import {NzWaveModule} from "ng-zorro-antd/core/wave"; -import {NzBreadCrumbModule} from "ng-zorro-antd/breadcrumb"; -import {RptProjectsListModule} from "../common/rpt-projects-list/rpt-projects-list.module"; -import {RptGroupedTaskListModule} from "../common/rpt-grouped-task-list/rpt-grouped-task-list.module"; -import {RptFlatTasksListModule} from "../common/rpt-flat-task-list/rpt-flat-tasks-list.module"; -import { RptSingleMemberDrawerOverviewComponent } from './rpt-single-member-drawer-overview/rpt-single-member-drawer-overview.component'; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import {NzCardModule} from "ng-zorro-antd/card"; -import {EllipsisPipe} from "@pipes/ellipsis.pipe"; -import {ReportingModule} from "../../reporting.module"; -import {NgChartsModule} from "ng2-charts"; -import { ActivityLogsComponent } from './rpt-single-member-drawer-overview/activity-logs/activity-logs.component'; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzDividerModule} from "ng-zorro-antd/divider"; -import { SingleMemberTimeLogsComponent } from './rpt-single-member-drawer-overview/time-logs/single-member-time-logs.component'; -import { DurationFilterComponent } from './rpt-single-member-drawer-overview/duration-filter/duration-filter.component'; -import {NzTimelineModule} from "ng-zorro-antd/timeline"; -import {NzAvatarModule} from "ng-zorro-antd/avatar"; -import {NzTagModule} from "ng-zorro-antd/tag"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; - -@NgModule({ - declarations: [ - RptSingleMemberDrawerComponent, - RptSingleMemberDrawerOverviewComponent, - ActivityLogsComponent, - SingleMemberTimeLogsComponent, - DurationFilterComponent - ], - exports: [ - RptSingleMemberDrawerComponent - ], - imports: [ - CommonModule, - FormsModule, - NzButtonModule, - NzDatePickerModule, - NzDrawerModule, - NzDropDownModule, - NzFormModule, - NzIconModule, - NzMenuModule, - NzSpaceModule, - NzTabsModule, - NzWaveModule, - NzBreadCrumbModule, - RptProjectsListModule, - RptGroupedTaskListModule, - RptFlatTasksListModule, - NzBadgeModule, - NzCardModule, - EllipsisPipe, - ReportingModule, - NgChartsModule, - NzSkeletonModule, - NzDividerModule, - NzTimelineModule, - NzAvatarModule, - NzTagModule, - NzTypographyModule, - NzToolTipModule - ] -}) -export class RptSingleMemberDrawerModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-projects-drawer/rpt-single-member-projects-drawer.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-projects-drawer/rpt-single-member-projects-drawer.component.html deleted file mode 100644 index 05afba94..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-projects-drawer/rpt-single-member-projects-drawer.component.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - Name - Start Date - End Date - Days Left/Overdue - Estimated Time - Actual Time - Status - Health - Category - Project Manager - - - - - - - - {{data.name}} - - - {{ (data.start_date | date: 'MMM dd,yyyy') || '-'}} - {{ (data.end_date | date: 'MMM dd,yyyy') || '-'}} - - - - {{ data.days_left }} days overdue - - Today - {{ data.days_left }} days left - - - - - {{data.estimated_time_string}} - {{data.actual_time_string}} - - - {{ data?.status_name }} - - - - {{data.project_health | ellipsis : 12}} - - - - - - {{data.category_name | ellipsis : 12}} - - - - - - - {{data.project_manager.name | ellipsis: 15}} - - - - - - - - - - - {{titleText}}'s Projects - - - - - - - - - diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-projects-drawer/rpt-single-member-projects-drawer.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-projects-drawer/rpt-single-member-projects-drawer.component.scss deleted file mode 100644 index 19776bcf..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-projects-drawer/rpt-single-member-projects-drawer.component.scss +++ /dev/null @@ -1,8 +0,0 @@ -.title-text { - font-weight: 500; - font-size: 14px; -} -nz-tag { - border-radius: 20px; - color: rgba(0, 0, 0, 0.85); -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-projects-drawer/rpt-single-member-projects-drawer.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-projects-drawer/rpt-single-member-projects-drawer.component.spec.ts deleted file mode 100644 index 197ee213..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-projects-drawer/rpt-single-member-projects-drawer.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { RptSingleMemberProjectsDrawerComponent } from './rpt-single-member-projects-drawer.component'; - -describe('RptSingleMemberProjectsDrawerComponent', () => { - let component: RptSingleMemberProjectsDrawerComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptSingleMemberProjectsDrawerComponent] - }); - fixture = TestBed.createComponent(RptSingleMemberProjectsDrawerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-projects-drawer/rpt-single-member-projects-drawer.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-projects-drawer/rpt-single-member-projects-drawer.component.ts deleted file mode 100644 index 62dec248..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-projects-drawer/rpt-single-member-projects-drawer.component.ts +++ /dev/null @@ -1,84 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component} from '@angular/core'; -import {ReportingDrawersService} from "../reporting-drawers.service"; -import {ReportingService} from "../../reporting.service"; -import {ReportingApiService} from "../../reporting-api.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {log_error} from "@shared/utils"; -import {IMemberTaskStatGroup, IRPTProject} from "../../interfaces"; -import {AvatarNamesMap} from "@shared/constants"; -import {color} from "chart.js/helpers"; - -@Component({ - selector: 'worklenz-rpt-single-member-projects-drawer', - templateUrl: './rpt-single-member-projects-drawer.component.html', - styleUrls: ['./rpt-single-member-projects-drawer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptSingleMemberProjectsDrawerComponent { - - show = false; - loading = false; - - titleText: string | null = null; - - projects: IRPTProject[] = []; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly drawer: ReportingDrawersService, - private readonly api: ReportingApiService, - private readonly reportingService: ReportingService, - ) { - this.drawer.onOpenSingleMemberProjects.pipe(takeUntilDestroyed()).subscribe(value => { - if (!value) return; - this.show = true; - this.cdr.markForCheck(); - setTimeout(async() => { - await this.get(value); - }, 50); - }) - } - - private async get(data: {team_member_id: string}) { - if(!data.team_member_id) return; - try { - this.loading = true; - const body = { - team_member_id: data.team_member_id, - archived: this.reportingService.getIncludeToggle() - } - const res = await this.api.getSingleMemberProjects(body); - if(res.done) { - this.projects = res.body.projects; - this.titleText = res.body.team_member_name; - } - this.loading = false; - this.cdr.markForCheck(); - } catch (e) { - log_error(e); - this.loading = false; - this.cdr.markForCheck(); - } - } - - trackByProject(index: number, data: IRPTProject) { - return data.id; - } - - reset() { - this.show = false; - this.projects = []; - this.loading = false; - this.titleText= null; - this.cdr.markForCheck(); - } - - close() { - this.reset(); - } - - getColor(name?: string) { - return AvatarNamesMap[name?.charAt(0).toUpperCase() || 'A']; - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-stat/rpt-single-member-stat.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-stat/rpt-single-member-stat.component.html deleted file mode 100644 index 11f96edc..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-stat/rpt-single-member-stat.component.html +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - Task - Status - Priority - Phase - Due Date - Completed On - Days Overdue - Estimated Time - Logged Time - Overlogged Time - - - - - - - - - {{ data.name }} - - - {{ data.status_name | ellipsis : 12 }} - - - {{ data.priority_name | ellipsis : 12 }} - - - {{ data.phase_name | ellipsis : 12}} - - - - {{ (data.end_date | date: 'MMM dd,yyyy') || '-'}} - {{ (data.completed_at | date: 'MMM dd,yyyy') || '-'}} - {{ data.overdue_days || '-'}} - {{ data.estimated_string }} - {{ data.time_spent_string }} - {{ data.overlogged_time_string }} - - - - - - - - - - - - - {{titleText}}'s Tasks - - - - - - - -
-
- -
- No tasks to show. -
-
diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-stat/rpt-single-member-stat.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-stat/rpt-single-member-stat.component.scss deleted file mode 100644 index d5445ab0..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-stat/rpt-single-member-stat.component.scss +++ /dev/null @@ -1,11 +0,0 @@ -.title-text { - font-weight: 500; - font-size: 14px; -} -nz-tag { - border-radius: 20px; - color: rgba(0, 0, 0, 0.85); -} -.no-data-img-holder { - max-width: 42px; -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-stat/rpt-single-member-stat.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-stat/rpt-single-member-stat.component.spec.ts deleted file mode 100644 index 22d8b385..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-stat/rpt-single-member-stat.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { RptSingleMemberStatComponent } from './rpt-single-member-stat.component'; - -describe('RptSingleMemberStatComponent', () => { - let component: RptSingleMemberStatComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptSingleMemberStatComponent] - }); - fixture = TestBed.createComponent(RptSingleMemberStatComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-stat/rpt-single-member-stat.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-stat/rpt-single-member-stat.component.ts deleted file mode 100644 index 65b46d5a..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-single-member-stat/rpt-single-member-stat.component.ts +++ /dev/null @@ -1,146 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit} from '@angular/core'; -import {IMemberTaskStatGroup} from "../../interfaces"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ReportingDrawersService} from "../reporting-drawers.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {log_error} from "@shared/utils"; -import {ReportingApiService} from "../../reporting-api.service"; -import {ReportingService} from "../../reporting.service"; -import {AuthService} from "@services/auth.service"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {merge} from "rxjs"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; - -@Component({ - selector: 'worklenz-rpt-single-member-stat', - templateUrl: './rpt-single-member-stat.component.html', - styleUrls: ['./rpt-single-member-stat.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptSingleMemberStatComponent implements OnInit, OnDestroy { - - show = false; - loading = false; - - titleText: string | null = null; - searchText: string | null = null; - - groups: IMemberTaskStatGroup[] | null = null; - teamMemberId: string | null = null; - - constructor( - private cdr: ChangeDetectorRef, - private readonly auth: AuthService, - private readonly reportingService: ReportingService, - private readonly drawer: ReportingDrawersService, - private readonly api: ReportingApiService, - private readonly socket: Socket, - private readonly taskView: TaskViewService, - ) { - this.drawer.onOpenSingleMemberTaskStat - .pipe(takeUntilDestroyed()) - .subscribe(value => { - if (!value) return; - this.teamMemberId = value.team_member_id; - this.show = true; - this.cdr.markForCheck(); - setTimeout(async () => { - await this.get(value); - }, 50); - }); - - merge( - this.taskView.onRefresh - ).pipe(takeUntilDestroyed()) - .subscribe(async () => { - await this.refresh(); - }); - - } - - ngOnInit() { - this.listenSockets(); - } - - listenSockets() { - this.socket.on(SocketEvents.TASK_NAME_CHANGE.toString(), this.refresh); - this.socket.on(SocketEvents.TASK_STATUS_CHANGE.toString(), this.refresh); - this.socket.on(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.refresh); - this.socket.on(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.refresh); - this.socket.on(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), this.refresh); - this.socket.on(SocketEvents.TASK_PHASE_CHANGE.toString(), this.refresh); - this.socket.on(SocketEvents.TASK_TIME_ESTIMATION_CHANGE.toString(), this.refresh); - this.socket.on(SocketEvents.TASK_TIMER_STOP.toString(), this.refresh); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_NAME_CHANGE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.TASK_STATUS_CHANGE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.TASK_PHASE_CHANGE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.TASK_TIME_ESTIMATION_CHANGE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.TASK_TIMER_STOP.toString(), this.refresh); - } - - async get(data: { team_member_id: string }) { - if (!data) return; - try { - this.loading = true; - const body = { - team_member_id: data.team_member_id, - team_id: this.auth.getCurrentSession()?.team_id, - duration: this.reportingService.getDrawerDuration()?.key, - date_range: this.reportingService.getDrawerDateRange(), - archived: this.reportingService.getIncludeToggle() - } - const res = await this.api.getMemberTasksStats(body); - if (res.done) { - this.groups = res.body.groups; - this.titleText = res.body.team_member_name; - } - this.loading = false; - } catch (e) { - this.loading = false; - log_error(e); - } - this.cdr.markForCheck(); - } - - trackByGroup(index: number, data: IMemberTaskStatGroup) { - return data.name; - } - - trackByTask(index: number, data: IProjectTask) { - return data.id; - } - - refresh = async () => { - if (!this.teamMemberId) return; - await this.get({team_member_id: this.teamMemberId}) - } - - reset() { - this.teamMemberId = null; - this.show = false; - this.loading = false; - this.titleText = null; - this.groups = null; - this.cdr.markForCheck(); - } - - close() { - this.reset(); - } - - openTask(data: IProjectTask) { - if (data.id && data.project_id) { - this.drawer.openTask({ - taskId: data.id, - projectId: data.project_id - }); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-task-view-drawer/rpt-task-view-drawer.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-task-view-drawer/rpt-task-view-drawer.component.html deleted file mode 100644 index 44c9f150..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-task-view-drawer/rpt-task-view-drawer.component.html +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-task-view-drawer/rpt-task-view-drawer.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-task-view-drawer/rpt-task-view-drawer.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-task-view-drawer/rpt-task-view-drawer.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-task-view-drawer/rpt-task-view-drawer.component.spec.ts deleted file mode 100644 index f552b93f..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-task-view-drawer/rpt-task-view-drawer.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptTaskViewDrawerComponent} from './rpt-task-view-drawer.component'; - -describe('RptTaskViewDrawerComponent', () => { - let component: RptTaskViewDrawerComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptTaskViewDrawerComponent] - }); - fixture = TestBed.createComponent(RptTaskViewDrawerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-task-view-drawer/rpt-task-view-drawer.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-task-view-drawer/rpt-task-view-drawer.component.ts deleted file mode 100644 index 2abec2a2..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-task-view-drawer/rpt-task-view-drawer.component.ts +++ /dev/null @@ -1,82 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component} from '@angular/core'; -import {ReportingDrawersService} from "../reporting-drawers.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {IRPTTaskDrawerData} from "../../interfaces"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {DRAWER_ANIMATION_INTERVAL} from "@shared/constants"; - -@Component({ - selector: 'worklenz-rpt-task-view-drawer', - templateUrl: './rpt-task-view-drawer.component.html', - styleUrls: ['./rpt-task-view-drawer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptTaskViewDrawerComponent { - projectId: string | null = null; - taskId: string | null = null; - - get show() { - return !!(this.projectId && this.taskId); - } - - set show(value: boolean) { - if (!value) { - this.projectId = null; - this.taskId = null; - } - } - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly drawer: ReportingDrawersService, - private readonly taskViewService: TaskViewService - ) { - this.drawer.onOpenTask - .pipe(takeUntilDestroyed()) - .subscribe(data => { - this.open(data); - }); - - this.taskViewService.onViewBackFrom.pipe(takeUntilDestroyed()).subscribe(task => { - const task_: IProjectTask = { - id: task.parent_task_id, - project_id: task.project_id, - } - this.handleTaskSelectFromView(task_); - }) - - this.taskViewService.onDelete - .pipe(takeUntilDestroyed()) - .subscribe(async task => { - if (task.parent_task_id) { - const task_: IProjectTask = { - id: task.parent_task_id, - project_id: this.projectId as string - } - this.handleTaskSelectFromView(task_); - } - }) - - } - - handleTaskSelectFromView(task: IProjectTask) { - this.show; - setTimeout(() => { - if (task && task.id && task.project_id) { - const parent: IRPTTaskDrawerData = { - taskId: task.id, - projectId: task.project_id - } - this.open(parent); - } - }, DRAWER_ANIMATION_INTERVAL); - this.cdr.detectChanges(); - } - - private open(data: IRPTTaskDrawerData) { - this.taskId = data.taskId; - this.projectId = data.projectId; - this.cdr.markForCheck(); - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-task-view-drawer/rpt-task-view-drawer.module.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-task-view-drawer/rpt-task-view-drawer.module.ts deleted file mode 100644 index 5ccc51d1..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-task-view-drawer/rpt-task-view-drawer.module.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {RptTaskViewDrawerComponent} from './rpt-task-view-drawer.component'; -import {TaskViewModule} from "@admin/components/task-view/task-view.module"; - -@NgModule({ - declarations: [ - RptTaskViewDrawerComponent - ], - exports: [ - RptTaskViewDrawerComponent - ], - imports: [ - CommonModule, - TaskViewModule - ] -}) -export class RptTaskViewDrawerModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-tasks-drawer/rpt-tasks-drawer.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-tasks-drawer/rpt-tasks-drawer.component.html deleted file mode 100644 index 1ab2b117..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-tasks-drawer/rpt-tasks-drawer.component.html +++ /dev/null @@ -1,49 +0,0 @@ - - - -
- - - - - {{member?.name}} - - -
- - - - - -
-
-
- - - - - - - - - - diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-tasks-drawer/rpt-tasks-drawer.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-tasks-drawer/rpt-tasks-drawer.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-tasks-drawer/rpt-tasks-drawer.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-tasks-drawer/rpt-tasks-drawer.component.spec.ts deleted file mode 100644 index bd4eed69..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-tasks-drawer/rpt-tasks-drawer.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptTasksDrawerComponent} from './rpt-tasks-drawer.component'; - -describe('RptTasksDrawerComponent', () => { - let component: RptTasksDrawerComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptTasksDrawerComponent] - }); - fixture = TestBed.createComponent(RptTasksDrawerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-tasks-drawer/rpt-tasks-drawer.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-tasks-drawer/rpt-tasks-drawer.component.ts deleted file mode 100644 index 56928e44..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-tasks-drawer/rpt-tasks-drawer.component.ts +++ /dev/null @@ -1,61 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component} from '@angular/core'; -import {IRPTMember, IRPTOverviewProject, IRPTTasksDrawerData} from "../../interfaces"; -import {ReportingDrawersService} from "../reporting-drawers.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {log_error} from "@shared/utils"; -import {ReportingExportApiService} from "@api/reporting-export-api.service"; - -@Component({ - selector: 'worklenz-rpt-tasks-drawer', - templateUrl: './rpt-tasks-drawer.component.html', - styleUrls: ['./rpt-tasks-drawer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptTasksDrawerComponent { - project: IRPTOverviewProject | null = null; - member: IRPTMember | null = null; - isMultiple = false; - - exporting = false; - - get show() { - return !!(this.project && this.member); - } - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly drawer: ReportingDrawersService, - private readonly exportApiService: ReportingExportApiService - ) { - this.drawer.onOpenTasks - .pipe(takeUntilDestroyed()) - .subscribe(value => { - this.open(value); - }); - } - - close() { - this.project = null; - this.member = null; - } - - private open(data: IRPTTasksDrawerData) { - this.project = data.project; - this.member = data.member; - this.cdr.markForCheck(); - } - - async export() { - if (!this.member) return; - try { - if (this.project) { - this.exportApiService.exportFlatTasks(this.member.id, this.member.name, this.project.id, this.project?.name); - } else { - this.exportApiService.exportFlatTasks(this.member.id, this.member.name, null, null); - } - } catch (e) { - log_error(e); - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-tasks-drawer/rpt-tasks-drawer.module.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-tasks-drawer/rpt-tasks-drawer.module.ts deleted file mode 100644 index 4491bd5c..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-tasks-drawer/rpt-tasks-drawer.module.ts +++ /dev/null @@ -1,37 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {RptTasksDrawerComponent} from './rpt-tasks-drawer.component'; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {RptGroupedTaskListModule} from "../common/rpt-grouped-task-list/rpt-grouped-task-list.module"; -import {RptFlatTasksListModule} from "../common/rpt-flat-task-list/rpt-flat-tasks-list.module"; -import {NzBreadCrumbModule} from "ng-zorro-antd/breadcrumb"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzWaveModule} from "ng-zorro-antd/core/wave"; -import {RptDrawerTitleComponent} from "../common/rpt-drawer-title/rpt-drawer-title.component"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; - -@NgModule({ - declarations: [ - RptTasksDrawerComponent - ], - exports: [ - RptTasksDrawerComponent - ], - imports: [ - CommonModule, - NzDrawerModule, - NzIconModule, - RptGroupedTaskListModule, - RptFlatTasksListModule, - NzBreadCrumbModule, - NzButtonModule, - NzSpaceModule, - NzWaveModule, - RptDrawerTitleComponent, - NzDropDownModule - ] -}) -export class RptTasksDrawerModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-members/rpt-team-drawer-members.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-members/rpt-team-drawer-members.component.html deleted file mode 100644 index 1e267086..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-members/rpt-team-drawer-members.component.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - Name - Email - Projects - Tasks - Overdue Tasks - Completed Tasks - Ongoing Tasks - - - - - {{ data.name }} - {{ data.email | bindNa }} - {{ data.projects }} - {{ data.tasks }} - {{ data.overdue }} - {{ data.completed }} - {{ data.ongoing }} - - - - diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-members/rpt-team-drawer-members.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-members/rpt-team-drawer-members.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-members/rpt-team-drawer-members.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-members/rpt-team-drawer-members.component.spec.ts deleted file mode 100644 index 7eecfde3..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-members/rpt-team-drawer-members.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptTeamDrawerMembersComponent} from './rpt-team-drawer-members.component'; - -describe('RptTeamDrawerMembersComponent', () => { - let component: RptTeamDrawerMembersComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptTeamDrawerMembersComponent] - }); - fixture = TestBed.createComponent(RptTeamDrawerMembersComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-members/rpt-team-drawer-members.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-members/rpt-team-drawer-members.component.ts deleted file mode 100644 index 6e7fa6c8..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-members/rpt-team-drawer-members.component.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - OnDestroy, - OnInit, - Output -} from '@angular/core'; -import {IRPTMember} from "../../../interfaces"; -import {ReportingApiService} from "../../../reporting-api.service"; -import {ReportingService} from "../../../reporting.service"; - -@Component({ - selector: 'worklenz-rpt-team-drawer-members', - templateUrl: './rpt-team-drawer-members.component.html', - styleUrls: ['./rpt-team-drawer-members.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptTeamDrawerMembersComponent implements OnInit, OnDestroy { - @Input({required: true}) teamId!: string; - - @Output() length = new EventEmitter(); - @Output() selectMember = new EventEmitter(); - - loading = false; - members: IRPTMember[] = []; - - searchText!: string; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: ReportingApiService, - private readonly service: ReportingService, - ) { - } - - ngOnInit() { - void this.getMembers(); - } - - ngOnDestroy() { - this.members = []; - } - - private async getMembers() { - if (!this.teamId) return; - try { - this.loading = true; - const res = await this.api.getOverviewMembersByTeam(this.teamId, this.service.getIncludeToggle()); - if (res.done) { - this.members = res.body; - this.length.emit(this.members.length); - } - this.loading = false; - } catch (e) { - this.loading = false; - } - - this.cdr.markForCheck(); - } - - trackBy(index: number, data: IRPTMember) { - return data.id; - } - - openMember(member: IRPTMember) { - this.selectMember.emit(member); - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-projects/rpt-team-drawer-projects.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-projects/rpt-team-drawer-projects.component.html deleted file mode 100644 index dd130661..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-projects/rpt-team-drawer-projects.component.html +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-projects/rpt-team-drawer-projects.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-projects/rpt-team-drawer-projects.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-projects/rpt-team-drawer-projects.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-projects/rpt-team-drawer-projects.component.spec.ts deleted file mode 100644 index 0e8c372e..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-projects/rpt-team-drawer-projects.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptTeamDrawerProjectsComponent} from './rpt-team-drawer-projects.component'; - -describe('RptTeamDrawerProjectsComponent', () => { - let component: RptTeamDrawerProjectsComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptTeamDrawerProjectsComponent] - }); - fixture = TestBed.createComponent(RptTeamDrawerProjectsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-projects/rpt-team-drawer-projects.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-projects/rpt-team-drawer-projects.component.ts deleted file mode 100644 index c601c585..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer-projects/rpt-team-drawer-projects.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {ChangeDetectionStrategy, Component, EventEmitter, Input, Output} from '@angular/core'; -import {IRPTOverviewProject} from "../../../interfaces"; - -@Component({ - selector: 'worklenz-rpt-team-drawer-projects', - templateUrl: './rpt-team-drawer-projects.component.html', - styleUrls: ['./rpt-team-drawer-projects.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptTeamDrawerProjectsComponent { - @Input({required: true}) teamId!: string; - @Input() teamMemberId!: string; - - @Output() length = new EventEmitter(); - @Output() selectProject = new EventEmitter(); -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer.component.html deleted file mode 100644 index 047d83a6..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer.component.html +++ /dev/null @@ -1,59 +0,0 @@ - - - -
-

- - {{team?.name}} -

-
- - - -
    -
  • Projects
  • -
  • Members
  • -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - Invalid team. Please reload the page and try again. - diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer.component.spec.ts deleted file mode 100644 index f87bc4f5..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptTeamDrawerComponent} from './rpt-team-drawer.component'; - -describe('RptTeamDrawerComponent', () => { - let component: RptTeamDrawerComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptTeamDrawerComponent] - }); - fixture = TestBed.createComponent(RptTeamDrawerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer.component.ts deleted file mode 100644 index c8468f9f..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer.component.ts +++ /dev/null @@ -1,89 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component} from '@angular/core'; -import {IRPTMember, IRPTOverviewProject, IRPTTeam} from "../../interfaces"; -import {ReportingDrawersService} from "../reporting-drawers.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {ReportingService} from "../../reporting.service"; -import {log_error} from "@shared/utils"; -import {ReportingExportApiService} from "@api/reporting-export-api.service"; - -@Component({ - selector: 'worklenz-rpt-team-drawer', - templateUrl: './rpt-team-drawer.component.html', - styleUrls: ['./rpt-team-drawer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptTeamDrawerComponent { - projectsLength = 0; - membersLength = 0; - - team: IRPTTeam | null = null; - - exporting = false; - - get show() { - return !!this.team; - } - - constructor( - private readonly drawer: ReportingDrawersService, - private readonly service: ReportingService, - private readonly cdr: ChangeDetectorRef, - private readonly exportApi: ReportingExportApiService - ) { - this.drawer.onOpenTeam - .pipe(takeUntilDestroyed()) - .subscribe((team) => { - this.open(team); - }); - } - - close() { - this.team = null; - } - - openProject(project: IRPTOverviewProject) { - this.team = this.service.getCurrentTeam(); - this.drawer.openProject(project); - } - - openMember(member: IRPTMember, project: IRPTOverviewProject | null) { - this.drawer.openMember(member, project); - } - - private open(team: IRPTTeam | null) { - this.team = team; - this.cdr.markForCheck(); - } - - // excel exports - async exportProjects() { - if(!this.team) return; - const team = this.team; - try { - this.exporting = true; - await this.exportApi.exportOverviewProjectsByTeam(team.id, team.name); - this.exporting = false; - this.cdr.markForCheck(); - } catch (e) { - log_error(e); - this.exporting = false; - this.cdr.markForCheck(); - } - } - - async exportMembers() { - if(!this.team) return; - const team = this.team; - try { - this.exporting = true; - this.exportApi.exportOverviewMembersByTeam(team.id, team.name); - this.exporting = false; - this.cdr.markForCheck(); - } catch (e) { - log_error(e); - this.exporting = false; - this.cdr.markForCheck(); - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer.module.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer.module.ts deleted file mode 100644 index 40dbfd58..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-drawer.module.ts +++ /dev/null @@ -1,63 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {RptTeamDrawerComponent} from "./rpt-team-drawer.component"; -import {RptTeamDrawerMembersComponent} from "./rpt-team-drawer-members/rpt-team-drawer-members.component"; -import {RptTeamDrawerProjectsComponent} from "./rpt-team-drawer-projects/rpt-team-drawer-projects.component"; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzTabsModule} from "ng-zorro-antd/tabs"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzTableModule} from "ng-zorro-antd/table"; -import {BindNaPipe} from "@pipes/bind-na.pipe"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {FormsModule} from "@angular/forms"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; -import {RptProjectsListModule} from "../common/rpt-projects-list/rpt-projects-list.module"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {NzMenuModule} from "ng-zorro-antd/menu"; -import { RptTeamOverviewComponent } from './rpt-team-overview/rpt-team-overview.component'; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import {NzCardModule} from "ng-zorro-antd/card"; -import {NzGridModule} from "ng-zorro-antd/grid"; -import {EllipsisPipe} from "@pipes/ellipsis.pipe"; -import {NgChartsModule} from "ng2-charts"; - -@NgModule({ - declarations: [ - RptTeamDrawerComponent, - RptTeamDrawerProjectsComponent, - RptTeamDrawerMembersComponent, - RptTeamOverviewComponent - ], - imports: [ - CommonModule, - NzDrawerModule, - NzSpaceModule, - NzButtonModule, - NzIconModule, - NzTabsModule, - NzSkeletonModule, - NzTableModule, - BindNaPipe, - NzInputModule, - FormsModule, - SearchByNamePipe, - SafeStringPipe, - NzDropDownModule, - NzMenuModule, - RptProjectsListModule, - NzBadgeModule, - NzCardModule, - NzGridModule, - EllipsisPipe, - NgChartsModule - ], - exports: [ - RptTeamDrawerComponent - ] -}) -export class RptTeamDrawerModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-overview/rpt-team-overview.component.html b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-overview/rpt-team-overview.component.html deleted file mode 100644 index d6c50857..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-overview/rpt-team-overview.component.html +++ /dev/null @@ -1,174 +0,0 @@ -
-
- - -
-
-

Projects By Status

-
-
-
-
- -
-
- -
- No projects to show. -
-
-
-
- - -
-
-
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
-
-
-
- - -
-
-

Projects By Category

-
-
-
-
- -
-
- -
- No projects to show. -
-
-
-
- - -
-
-
    -
  • - -
  • -
  • - - {{(category.label | ellipsis: 30) + ' (' + (category.count || 0) + ')'}} -
  • -
-
-
-
-
-
-
- - -
-
-

Projects By Health

-
-
-
-
- -
-
- -
- No projects to show. -
-
-
-
- - -
-
-
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-overview/rpt-team-overview.component.scss b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-overview/rpt-team-overview.component.scss deleted file mode 100644 index 88329bd9..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-overview/rpt-team-overview.component.scss +++ /dev/null @@ -1,9 +0,0 @@ -.chart-details { - height: 220px; - max-height: 220px; - overflow-y: auto; -} - -.no-data-img-holder { - width: 65px; -} diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-overview/rpt-team-overview.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-overview/rpt-team-overview.component.spec.ts deleted file mode 100644 index 51820093..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-overview/rpt-team-overview.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { RptTeamOverviewComponent } from './rpt-team-overview.component'; - -describe('RptTeamOverviewComponent', () => { - let component: RptTeamOverviewComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptTeamOverviewComponent] - }); - fixture = TestBed.createComponent(RptTeamOverviewComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-overview/rpt-team-overview.component.ts b/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-overview/rpt-team-overview.component.ts deleted file mode 100644 index b7811d96..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/drawers/rpt-team-drawer/rpt-team-overview/rpt-team-overview.component.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Input, - NgZone, - OnDestroy, - OnInit, - ViewChild -} from '@angular/core'; -import {ReportingApiService} from "../../../reporting-api.service"; -import {IRPTOverviewTeamInfo} from 'app/administrator/reporting/interfaces'; -import {BaseChartDirective} from "ng2-charts"; -import {ChartConfiguration} from "chart.js"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {ReportingService} from "../../../reporting.service"; - -@Component({ - selector: 'worklenz-rpt-team-overview', - templateUrl: './rpt-team-overview.component.html', - styleUrls: ['./rpt-team-overview.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) - -export class RptTeamOverviewComponent implements OnInit, OnDestroy { - @Input({required: true}) teamId!: string; - - @ViewChild(BaseChartDirective) statusChart: BaseChartDirective | undefined; - @ViewChild(BaseChartDirective) categoryChart: BaseChartDirective | undefined; - @ViewChild(BaseChartDirective) healthChart: BaseChartDirective | undefined; - - model: IRPTOverviewTeamInfo = {}; - - loading = false; - isStatusChartEmpty = false; - isCategoryChartEmpty = false; - isHealthChartEmpty = false; - - statusColors: string[] = []; - categoryColors: string[] = []; - healthColors: string[] = []; - - statusChartData: ChartConfiguration<'doughnut'>['data'] = { - labels: [], - datasets: [{ - label: 'Projects', - data: [], - backgroundColor: this.statusColors, - hoverOffset: 2 - }] - }; - - categoryChartData: ChartConfiguration<'doughnut'>['data'] = { - labels: [], - datasets: [{ - label: 'Projects', - data: [], - backgroundColor: this.categoryColors, - hoverOffset: 2 - }] - }; - - healthChartData: ChartConfiguration<'doughnut'>['data'] = { - labels: [], - datasets: [{ - label: 'Projects', - data: [], - backgroundColor: this.healthColors, - hoverOffset: 2, - }] - } - - chartOptions: ChartConfiguration<'doughnut'>['options'] = { - plugins: { - datalabels: { - display: false - } - }, - responsive: false - } - - constructor( - protected readonly ngZone: NgZone, - private readonly cdr: ChangeDetectorRef, - private readonly api: ReportingApiService, - private readonly socket: Socket, - private readonly service: ReportingService - ) { - - } - - ngOnInit() { - void this.get(); - this.listenSockets(); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.PROJECT_HEALTH_CHANGE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.PROJECT_STATUS_CHANGE.toString(), this.refresh); - this.socket.removeListener(SocketEvents.PROJECT_CATEGORY_CHANGE.toString(), this.refresh); - } - - listenSockets() { - this.socket.on(SocketEvents.PROJECT_HEALTH_CHANGE.toString(), this.refresh); - this.socket.on(SocketEvents.PROJECT_STATUS_CHANGE.toString(), this.refresh); - this.socket.on(SocketEvents.PROJECT_CATEGORY_CHANGE.toString(), this.refresh); - } - - public async get() { - if (!this.teamId) return; - try { - this.loading = true; - this.clearCharts(); - const res = await this.api.getTeamInfo(this.teamId, this.service.getIncludeToggle()); - if (res.done) { - this.model = res.body; - this.drawCharts(res.body); - } - this.loading = false; - this.cdr.markForCheck(); - } catch (e) { - this.loading = false; - this.cdr.markForCheck(); - } - } - - private drawCharts(data: IRPTOverviewTeamInfo) { - if (data.by_status) { - for (const item of data.by_status.chart) { - this.statusChartData.labels?.push(item.name); - this.statusChartData.datasets[0].data.push(item.y || 0); - this.statusColors.push(item.color as string); - } - this.statusChart?.update(); - if (this.statusChartData.datasets[0].data.every(value => value === 0)) - this.isStatusChartEmpty = true; - } - - if (data.by_category) { - for (const item of data.by_category.chart) { - this.categoryChartData.labels?.push(item.name); - this.categoryChartData.datasets[0].data.push(item.y || 0); - this.categoryColors.push(item.color as string); - } - this.categoryChart?.update(); - if (this.categoryChartData.datasets[0].data.every(value => value === 0)) - this.isCategoryChartEmpty = true; - } - - if (data.by_health) { - for (const item of data.by_health.chart) { - this.healthChartData.labels?.push(item.name); - this.healthChartData.datasets[0].data.push(item.y || 0); - this.healthColors.push(item.color as string); - } - this.healthChart?.update(); - if (this.healthChartData.datasets[0].data.every(value => value === 0)) - this.isHealthChartEmpty = true; - } - this.cdr.markForCheck(); - } - - clearCharts() { - this.statusChartData.datasets[0].data = []; - this.categoryChartData.datasets[0].data = []; - this.healthChartData.datasets[0].data = []; - - this.statusChartData.labels = []; - this.categoryChartData.labels = []; - this.healthChartData.labels = []; - - this.cdr.markForCheck(); - } - - refresh = (response: any) => { - this.get(); - this.cdr.markForCheck(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/interfaces.ts b/worklenz-frontend/src/app/administrator/reporting/interfaces.ts deleted file mode 100644 index ef622179..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/interfaces.ts +++ /dev/null @@ -1,422 +0,0 @@ -import {InlineMember} from "@interfaces/api-models/inline-member"; -import {IActivityLogsLabel} from "@interfaces/api-models/activity-logs-get-response"; -import {ITeamMemberViewModel} from "@interfaces/api-models/team-members-get-response"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; - -export interface IChartObject { - name: string, - color: string, - y: number -} - -export interface IRPTDuration { - label: string; - key: string; - dates?: string -} - -export interface IReportingInfo { - organization_name: string; -} - -export interface IRPTTeamStatistics { - count: number; - projects: number; - members: number; -} - -export interface IRPTProjectStatistics { - count: number; - active: number; - overdue: number; -} - -export interface IRPTMemberStatistics { - count: number; - unassigned: number; - overdue: number; -} - -export interface ITimeLogBreakdownReq { - id: string, - date_range: string[], - duration: string, - time_zone: string -} - -export interface IRPTOverviewStatistics { - teams?: IRPTTeamStatistics; - projects?: IRPTProjectStatistics; - members?: IRPTMemberStatistics; -} - -export interface IRPTTeam { - id: string; - name: string; - projects_count: number; - members: InlineMember[]; - selected: boolean; -} - -export interface IRPTOverviewTeamChartData { - chart: IChartObject[]; - total: number; - data: Array<{ - label: string; - color: string; - count: number; - }>; -} - -export interface IRPTOverviewTeamByStatus { - all: number; - in_progress: number; - in_planning: number; - completed: number; - proposed: number; - on_hold: number; - blocked: number; - cancelled: number; - chart: {name: string, color: string, y: number}[]; -} - -export interface IRPTOverviewTeamByHealth { - all: number; - not_set: number; - needs_attention: number; - at_risk: number; - good: number; - chart: IChartObject[]; -} - -export interface IRPTOverviewTeamInfo { - by_status?: IRPTOverviewTeamByStatus; - by_category?: IRPTOverviewTeamChartData; - by_health?: IRPTOverviewTeamByHealth; -} - -export interface IRPTOverviewProject { - id: string; - name: string; - client: string; - status: { - name: string; - color_code: string; - icon: string; - }; -} - -export interface IRPTOverviewProjectExt extends IRPTOverviewProject { - team_member_id: string; -} - -export interface IRPTMemberResponse { - total: number, - members: IRPTMember[], - team: { - id: string, - name: string, - } -} - -export interface IRPTMember { - color_code: string; - tasks_stat: any; - - avatar_url?: string; - completed: number; - /** Team member id */ - email: string; - id: string; - name: string; - teams: string; - projects: number; - tasks: number; - overdue: number; - ongoing: number; - todo: number; - member_teams: any; -} - -export interface ISingleMemberLogs { - log_day: string; - logs: ISingleMemberLog[]; -} - -export interface ISingleMemberLog { - time_spent_string: string - project_name: string, - task_name: string, - task_key: string, - task_id: string, - project_id: string -} - -export interface ISingleMemberActivityLogs { - log_day: string; - logs: ISingleMemberActivityLog[]; -} - -export interface ISingleMemberActivityLog { - project_id: string; - task_id: string; - project_name: string; - task_name: string; - task_key: string; - attribute_type: string; - previous: string; - current: string; - previous_status?: IActivityLogsLabel; - next_status?: IActivityLogsLabel; - previous_priority?: IActivityLogsLabel; - next_priority?: IActivityLogsLabel; - previous_phase?: IActivityLogsLabel; - next_phase?: IActivityLogsLabel; -} - -export interface IRPTOverviewProjectMember { - /** Project member id */ - id: string; - name: string; - tasks_count: number; - completed: number; - incompleted: number; - overdue: number; - contribution: number; - team_member_id: string; - progress: number; - time_logged: string -} - -export interface IRPTOverviewProjectTasksStats { - completed: number; - incompleted: number; - overdue: number; - total_allocated: number; - total_logged: number; -} - -export interface IRPTOverviewProjectTasksByStatus { - all: number; - todo: number; - doing: number; - done: number; - chart: IChartObject[]; -} - -export interface IRPTOverviewProjectTasksByPriority { - all: number; - low: number; - medium: number; - high: number; - chart: IChartObject[]; -} - -export interface IRPTOverviewProjectTasksByDue { - all: number; - completed: number; - upcoming: number; - overdue: number; - no_due: number; - chart: IChartObject[]; -} - -export interface IRPTOverviewProjectInfo { - stats?: IRPTOverviewProjectTasksStats; - by_status?: IRPTOverviewProjectTasksByStatus; - by_priority?: IRPTOverviewProjectTasksByPriority; - by_due?: IRPTOverviewProjectTasksByDue; -} - -export interface IRPTOverviewMemberStats { - teams: number; - projects: number; - completed: number; - ongoing: number; - overdue: number; - total_tasks: number; - total_logged: number; - assigned: number -} - -export interface IRPTOverviewMemberChartData { - chart: IChartObject[]; - total: number; - data: Array<{ - label: string; - color: string; - count: number; - }>; -} - -export interface IRPTOverviewMemberInfo { - stats?: IRPTOverviewMemberStats; - by_status?: IRPTOverviewMemberChartData; - by_priority?: IRPTOverviewMemberChartData; - by_project?: IRPTOverviewMemberChartData -} - -export interface IRPTReportingMemberTask { - id: string; - name: string; - project_id: string; - project_name: string; - priority_name: string; - priority_color: string; - project_color: string; - parent_task_id?:string; - status_name: string; - status_color: string; - end_date?: string; - completed_date?: string; - days_overdue?: number; - estimated_string?: string; - time_spent_string?: string; - overlogged_time?: string; - is_sub_task: boolean; -} - -export interface IRPTTasksDrawerData { - project: IRPTOverviewProject; - member: IRPTMember; -} - -export interface IRPTTaskDrawerData { - taskId: string; - projectId: string; -} - -export interface IRPTMemberDrawerData { - project: IRPTOverviewProject | null; - member: IRPTMember | null; -} - -export interface IRPTLastActivity { - assigned_user?: null; - attribute_type?: "name"; - current?: string; - done_by?: { - name?: string; - avatar_url?: string; - color_code?: string; - }; - avatar_url?: string; - color_code?: string; - name?: string; - log_text?: string; - log_type?: string; - previous?: string; - last_activity_string?: string; -} - -export interface IRPTProject extends IRPTOverviewProject { - last_activity: IRPTLastActivity; - color_code: string; - category_id: string | null; - category_name: string; - category_color: string; - team_id: string; - team_name: string; - team_color: string; - status_id: string; - status_name: string; - status_color: string; - status_icon: string; - start_date: string; - end_date: string; - estimated_time: number; - actual_time: number; - days_left: number | null; - is_overdue: boolean; - is_today: boolean; - tasks_stat: { - total: number; - todo: number; - doing: number; - done: number; - }; - comment?: string; - project_health: string; - health_color: string; - estimated_time_string?: string; - actual_time_string?: string; - project_manager: ITeamMemberViewModel -} - -export interface IDurationChangedEmitter { - selectedDuration: IRPTDuration | null, - dateRange: string[] -} - -export interface IRPTSingleMemberDrawerData { - member: IRPTMember | null; -} - -export interface IProjectLogs { - project_name: string; - task_key: string; - task_name: string; - time_spent_string: string; - user_name: string; - avatar_url: string; - created_at: string; -} - -export interface IProjectLogsBreakdown { - log_day: string; - logs: IProjectLogs[] -} - -export interface IRPTProjectsViewModel { - total?: number; - projects?: IRPTProject[] -} -export interface IRPTMembersViewModel { - total?: number; - members?: IRPTMember[] -} - -export interface IRPTMemberProject { - completed?: number; - contribution?: number; - incompleted?: number; - name: string; - overdue?: number; - project_task_count: number; - task_count: number; - team: string; - team_member_id: string; - time_logged: string; - id:string; -} - -export interface IRPTTimeProject { - id: string; - name: string; - color_code: string; - value?: number; - estimated_value?: number; - end_date?: string; -} - -export interface IRPTTimeMember { - name: string; - value?: number; - color_code: string; -} - -export interface IMemberTaskStatGroupResonse { - team_member_name: string; - groups: IMemberTaskStatGroup[] -} - -export interface IMemberProjectsResonse { - team_member_name: string; - projects: IRPTProject[] -} - -export interface IMemberTaskStatGroup { - name: string; - color_code: string; - tasks: IProjectTask[]; -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation-routing.module.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation-routing.module.ts deleted file mode 100644 index d9c411b4..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation-routing.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {RptAllocationComponent} from "./rpt-allocation/rpt-allocation.component"; - -const routes: Routes = [ - {path: "", component: RptAllocationComponent} -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class RptAllocationRoutingModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation.module.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation.module.ts deleted file mode 100644 index afb571b1..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation.module.ts +++ /dev/null @@ -1,52 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule, NgOptimizedImage} from '@angular/common'; - -import {RptAllocationRoutingModule} from './rpt-allocation-routing.module'; -import {RptAllocationComponent} from './rpt-allocation/rpt-allocation.component'; -import {RptHeaderComponent} from "../../components/rpt-header/rpt-header.component"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzProgressModule} from "ng-zorro-antd/progress"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzCheckboxModule} from "ng-zorro-antd/checkbox"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NzEmptyModule} from "ng-zorro-antd/empty"; -import {FormsModule} from "@angular/forms"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; - -@NgModule({ - declarations: [ - RptAllocationComponent - ], - imports: [ - CommonModule, - RptAllocationRoutingModule, - RptHeaderComponent, - NzIconModule, - NzProgressModule, - NzSkeletonModule, - NzToolTipModule, - NzTypographyModule, - SafeStringPipe, - NgOptimizedImage, - NzFormModule, - NzSpaceModule, - NzDropDownModule, - NzButtonModule, - NzCheckboxModule, - SearchByNamePipe, - NzInputModule, - NzEmptyModule, - FormsModule, - NzBadgeModule - ] -}) -export class RptAllocationModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation/rpt-allocation.component.html b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation/rpt-allocation.component.html deleted file mode 100644 index 88e8bede..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation/rpt-allocation.component.html +++ /dev/null @@ -1,160 +0,0 @@ - - - -
- - - - - -
- - -
    -
  • - -
  • -
  • Select all -
  • -
  • -
  • - {{item.name}} -
  • -
-
- - -
    -
  • - -
  • -
  • Select all -
  • -
  • -
  • - No Category -
  • -
  • -
    -
    - - - -
    -
    -
  • -
-
- - - -
    -
  • - -
  • - - - -
  • Select all -
  • -
  • -
  • -
    -
    - {{item.name}} -
    -
    -
  • -
-
- - -
-
-
- - - - - - -
-
-
- {{item.name}} -
-
Total
-
-
- - - -
-
- - {{item.name}} -
- -
-
-
- {{item.time_logged}} -
-
{{item.total}}
-
-
- - -
- -
Total
-
- {{item.total_time}} -
-
-
- -
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation/rpt-allocation.component.scss b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation/rpt-allocation.component.scss deleted file mode 100644 index 27722c2d..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation/rpt-allocation.component.scss +++ /dev/null @@ -1,95 +0,0 @@ -.project-name { - width: 200px; - position: sticky; - left: 0; - background: white; - padding: 16px 8px 16px 12px; - border-left: 1px solid #f5f5f5; - border-right: 1px solid #f5f5f5; - border-bottom: 1px solid #f5f5f5; -} - -.member-time, .member-name, .member-total-time { - width: 100px; - border-right: 1px solid #f5f5f5; - padding: 16px 6px 16px 6px; -} - -.member-time { - font-size: 13px; - border-bottom: 1px solid #f5f5f5; - color: hwb(0 0% 100% / 0.65); - text-align: center; - display: flex; - align-items: center; - justify-content: center; -} - -.member-name { - border-bottom: 1px solid #f5f5f5; - border-top: 1px solid #f5f5f5; - text-overflow: ellipsis; - overflow: hidden; -} - -.header-row, .table-row_ { - width: fit-content; -} - -.header-row { - position: sticky; - top: 0; - background-color: white; - z-index: 9; - - .project-name { - border-left: none !important; - } -} - -.total-time { - width: 120px; - position: sticky; - right: 0; - background: #f8f7f9; - text-align: center; - font-weight: 500; - padding: 16px 12px; - border-bottom: 1px solid #f1f1f1; - display: flex; - align-items: center; - justify-content: center; -} - -.bg-bold { - background: #f8f7f9; - font-weight: 500; -} - -.f-500 { - font-weight: 500; -} - -.member-project-table { - overflow: auto; - max-height: calc(100vh - 250px); -} - -.border-top { - border-top: 1px solid #f0f0f0 !important; -} - -.badge-custom { - margin-top: -3px !important; - margin-right: 4px; -} - -.no-data-img-holder { - width: 100px; - margin-top: 42px; -} - -.bottom-row { - position: sticky; - bottom: 0; -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation/rpt-allocation.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation/rpt-allocation.component.spec.ts deleted file mode 100644 index ccc4388d..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation/rpt-allocation.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptAllocationComponent} from './rpt-allocation.component'; - -describe('RptAllocationComponent', () => { - let component: RptAllocationComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptAllocationComponent] - }); - fixture = TestBed.createComponent(RptAllocationComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation/rpt-allocation.component.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation/rpt-allocation.component.ts deleted file mode 100644 index 23ba5794..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-allocation/rpt-allocation/rpt-allocation.component.ts +++ /dev/null @@ -1,301 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit} from '@angular/core'; -import {AppService} from "@services/app.service"; -import {IAllocationProject} from "@interfaces/allocation-view-model"; -import {ISelectableTeam} from "@interfaces/selectable-team"; -import {ISelectableProject} from "@interfaces/selectable-project"; -import {ReportingApiService} from "../../../reporting-api.service"; -import {log_error} from "@shared/utils"; -import {ReportingExportApiService} from "@api/reporting-export-api.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {ReportingService} from "../../../reporting.service"; -import {merge} from "rxjs"; -import {IProjectCategoryViewModel} from "@interfaces/project-category"; -import moment from "moment/moment"; -import {LAST_MONTH, LAST_QUARTER, LAST_WEEK, PREV_MONTH, PREV_WEEK, YESTERDAY} from "@shared/constants"; - -@Component({ - selector: 'worklenz-rpt-allocation', - templateUrl: './rpt-allocation.component.html', - styleUrls: ['./rpt-allocation.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptAllocationComponent implements OnInit { - loadingProjects = false; - loadingCategories = false; - loadingTeams = false; - loading = false; - selectAllTeams = true; - selectAllProjects = true; - selectAllCategories = true; - selectNoCategory = true; - isDurationLabelSelected = true; - - categorySearchText: string | null = null; - teamSearchText: string | null = null; - projectSearchText: string | null = null; - - projects: IAllocationProject[] = []; - members: Array<{ name: string, total_time?: string }> = []; - - teamsDropDown: ISelectableTeam[] = []; - projectsDropdown: ISelectableProject[] = []; - categoriesDropdown: IProjectCategoryViewModel[] = []; - selectAll = true; - - constructor( - private readonly app: AppService, - private readonly api: ReportingApiService, - private readonly cdr: ChangeDetectorRef, - private readonly exportApi: ReportingExportApiService, - private service: ReportingService - ) { - this.app.setTitle("Reporting - Allocation"); - merge( - this.service.onDurationChange, - this.service.onDateRangeChange, - this.service.onIncludeToggleChange - ) - .pipe(takeUntilDestroyed()) - .subscribe(() => { - void this.getAllocationData(); - }); - } - - ngOnInit() { - void this.getTeams(); - } - - private getSelectedTeamIds(): string[] { - const filter = this.teamsDropDown.filter(t => t.selected); - const ids = filter.map(t => t.id) as string[]; - return ids || []; - } - - private getSelectedCategories(): string[] { - const filter = this.categoriesDropdown.filter(c => c.selected); - const ids = filter.map(c => c.id) as string[]; - return ids || []; - } - - private getSelectedProjectIds(): string[] { - const filter = this.projectsDropdown.filter(t => t.selected); - const ids = filter.map(t => t.id) as string[]; - return ids || []; - } - - selectAllCategoriesChecked(checked: boolean) { - if (checked) { - this.selectNoCategory = true; - for (const item of this.categoriesDropdown) { - item.selected = true; - } - this.refreshProjects(); - this.cdr.markForCheck(); - } else { - this.selectNoCategory = false; - for (const item of this.categoriesDropdown) { - item.selected = false; - } - this.refreshProjects(); - this.cdr.markForCheck(); - } - } - - async getCategories(teams: string[]) { - try { - this.loadingCategories = true; - const res = await this.api.getCategories(teams); - if (res.done) { - this.categoriesDropdown = res.body; - } - this.loadingCategories = false; - this.cdr.markForCheck(); - } catch (e) { - this.loadingCategories = false; - this.cdr.markForCheck(); - } - } - - private async setDatesForKeys() { - if(this.service.getDuration()?.key) { - const key = this.service.getDuration()?.key; - const today = moment(); - - switch (key) { - case YESTERDAY: - const yesterday = moment().subtract(1, "days"); - this.service.setDateRange([yesterday.toString(), yesterday.toString()]); - break; - case LAST_WEEK: - const lastWeekStart = moment().subtract(1, "weeks"); - this.service.setDateRange([lastWeekStart.toString(), today.toString()]); - break; - case LAST_MONTH: - const lastMonthStart = moment().subtract(1, "months"); - this.service.setDateRange([lastMonthStart.toString(), today.toString()]); - break; - case LAST_QUARTER: - const lastQuaterStart = moment().subtract(3, "months"); - this.service.setDateRange([lastQuaterStart.toString(), today.toString()]); - break; - case PREV_WEEK: - const prevWeekStart = moment().subtract(1, "weeks").startOf("week"); - const prevWeekEnd = moment().subtract(1, "weeks").endOf("week"); - this.service.setDateRange([prevWeekStart.toString(), prevWeekEnd.toString()]); - break; - case PREV_MONTH: - const prevMonthStart = moment().subtract(1, "month").startOf("month"); - const prevMonthEnd = moment().subtract(1, "month").endOf("month"); - this.service.setDateRange([prevMonthStart.toString(), prevMonthEnd.toString()]); - break; - } - } - } - - async getAllocationData() { - try { - this.loading = true; - - if (this.isDurationLabelSelected) { - await this.setDatesForKeys(); - } - - const teams = this.getSelectedTeamIds(); - const projects = this.getSelectedProjectIds(); - const duration = this.service.getDuration()?.key; - - const body = { - teams, - projects, - duration, - date_range: this.service.getDateRange(), - archived: this.service.getIncludeToggle() - }; - - const res = await this.api.getAllocationData(body, this.service.getIncludeToggle()); - if (res.done) { - this.members = res.body.users; - this.projects = res.body.projects; - } - this.loading = false; - } catch (e) { - this.loading = false; - } - - this.cdr.markForCheck(); - } - - async refreshCategories() { - await this.getCategories(this.getSelectedTeamIds()); - void this.refreshProjects(); - } - - refreshProjects() { - void this.getProjects(this.getSelectedTeamIds(), this.getSelectedCategories()); - } - - async getProjects(teams: string[], categories: string[]) { - try { - this.loadingProjects = true; - const res = await this.api.getAllocationProjects(teams, categories, this.selectNoCategory); - if (res.done) { - this.projectsDropdown = res.body; - void this.getAllocationData(); - } - this.loadingProjects = false; - } catch (e) { - this.loadingProjects = false; - } - this.cdr.markForCheck(); - } - - private async getTeams() { - try { - this.loadingTeams = true; - const res = await this.api.getOverviewTeams(); - if (res.done) { - this.loadingTeams = false; - const teams = []; - for (const team of res.body) { - teams.push({selected: true, name: team.name, id: team.id}) - } - this.teamsDropDown = teams; - void this.refreshCategories() - this.cdr.markForCheck(); - } - } catch (e) { - log_error(e); - this.loadingTeams = false; - } - } - - async export() { - try { - const teams = this.getSelectedTeamIds(); - const projects = this.getSelectedProjectIds(); - if (this.isDurationLabelSelected) { - await this.setDatesForKeys(); - } - const duration = this.service.getDuration()?.key; - const body = { - teams, - projects, - duration, - date_range: this.service.getDateRange(), - archived: this.service.getIncludeToggle() - }; - - this.exportApi.exportAllocation(this.service.getIncludeToggle(), teams, projects, duration, this.service.getDateRange()); - } catch (e) { - log_error(e); - } - } - - checkTeam() { - this.selectAllTeams = false - void this.refreshCategories(); - } - - checkCategory() { - this.selectAllCategories = false - void this.refreshProjects(); - } - - checkProject() { - this.selectAllProjects = false - this.getAllocationData(); - } - - selectAllTeamsChecked(checked: boolean) { - if (checked) { - for (const item of this.teamsDropDown) { - item.selected = true; - } - this.refreshProjects(); - this.cdr.markForCheck(); - } else { - for (const item of this.teamsDropDown) { - item.selected = false; - } - this.refreshProjects(); - this.cdr.markForCheck(); - } - } - - selectAllProjectsChecked(checked: boolean) { - if (checked) { - for (const item of this.projectsDropdown) { - item.selected = true; - } - this.getAllocationData(); - this.cdr.markForCheck(); - } else { - for (const item of this.projectsDropdown) { - item.selected = false; - } - this.getAllocationData(); - this.cdr.markForCheck(); - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members-routing.module.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members-routing.module.ts deleted file mode 100644 index 60d9f466..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members-routing.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {RptMembersComponent} from "./rpt-members/rpt-members.component"; - -const routes: Routes = [ - {path: "", component: RptMembersComponent} -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class RptMembersRoutingModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members.module.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members.module.ts deleted file mode 100644 index 51829b25..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members.module.ts +++ /dev/null @@ -1,77 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; - -import {RptMembersRoutingModule} from './rpt-members-routing.module'; -import {RptMembersComponent} from './rpt-members/rpt-members.component'; -import {RptHeaderComponent} from "../../components/rpt-header/rpt-header.component"; -import {NaComponent} from "@admin/components/na/na.component"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import {NzCardModule} from "ng-zorro-antd/card"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NzLayoutModule} from "ng-zorro-antd/layout"; -import {NzTableModule} from "ng-zorro-antd/table"; -import {NzTagModule} from "ng-zorro-antd/tag"; -import {FormsModule, ReactiveFormsModule} from "@angular/forms"; -import {RptMemberDrawerModule} from "../../drawers/rpt-member-drawer/rpt-member-drawer.module"; -import {RptProjectDrawerModule} from "../../drawers/rpt-project-drawer/rpt-project-drawer.module"; -import {RptTaskViewDrawerModule} from "../../drawers/rpt-task-view-drawer/rpt-task-view-drawer.module"; -import {RptTasksDrawerModule} from "../../drawers/rpt-tasks-drawer/rpt-tasks-drawer.module"; -import {RptTeamDrawerModule} from "../../drawers/rpt-team-drawer/rpt-team-drawer.module"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {TasksProgressBarComponent} from "@admin/components/tasks-progress-bar/tasks-progress-bar.component"; -import {RptProjectsModule} from "../rpt-projects/rpt-projects.module"; -import {NzAvatarModule} from "ng-zorro-antd/avatar"; -import {FirstCharUpperPipe} from "@pipes/first-char-upper.pipe"; -import {RptSingleMemberDrawerModule} from "../../drawers/rpt-single-member-drawer/rpt-single-member-drawer.module"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzCheckboxModule} from "ng-zorro-antd/checkbox"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {NzMenuModule} from "ng-zorro-antd/menu"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {ReportingModule} from "../../reporting.module"; - - -@NgModule({ - declarations: [ - RptMembersComponent - ], - imports: [ - CommonModule, - RptMembersRoutingModule, - RptHeaderComponent, - NaComponent, - NzBadgeModule, - NzCardModule, - NzIconModule, - NzInputModule, - NzLayoutModule, - NzTableModule, - NzTagModule, - ReactiveFormsModule, - RptMemberDrawerModule, - RptProjectDrawerModule, - RptTaskViewDrawerModule, - RptTasksDrawerModule, - RptTeamDrawerModule, - SearchByNamePipe, - TasksProgressBarComponent, - FormsModule, - RptProjectsModule, - NzAvatarModule, - FirstCharUpperPipe, - RptSingleMemberDrawerModule, - NzSpaceModule, - NzCheckboxModule, - NzDropDownModule, - NzMenuModule, - NzToolTipModule, - NzButtonModule, - NzTypographyModule, - ReportingModule - ] -}) -export class RptMembersModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members/rpt-members.component.html b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members/rpt-members.component.html deleted file mode 100644 index c58bf417..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members/rpt-members.component.html +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - - - - - - - - - Member - - - - - - - - - - - - - Tasks Progress - Tasks Assigned - Overdue Tasks - Completed Tasks - Ongoing Tasks - - - - - - - - - {{data.name}} - - - - - - - - {{data.tasks}} - {{data.overdue}} - {{data.completed}} - {{data.ongoing}} - - - - - - - - - - - - - - - - - - - -
    -
  • - -
  • -
  • Select all -
  • -
  • -
  • - {{item.name}} -
  • -
-
- - -
    -
  • - -
  • -
  • Select all -
  • -
  • -
  • -
    -
    - {{item.name}} -
    -
    -
  • -
-
diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members/rpt-members.component.scss b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members/rpt-members.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members/rpt-members.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members/rpt-members.component.spec.ts deleted file mode 100644 index c5eb68ec..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members/rpt-members.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptMembersComponent} from './rpt-members.component'; - -describe('RptMembersComponent', () => { - let component: RptMembersComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptMembersComponent] - }); - fixture = TestBed.createComponent(RptMembersComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members/rpt-members.component.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members/rpt-members.component.ts deleted file mode 100644 index e2b81537..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-members/rpt-members/rpt-members.component.ts +++ /dev/null @@ -1,381 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit} from '@angular/core'; -import {AppService} from "@services/app.service"; -import {IRPTMember, IRPTMemberResponse} from "../../../interfaces"; -import {ReportingApiService} from "../../../reporting-api.service"; -import {ReportingDrawersService} from "../../../drawers/reporting-drawers.service"; -import {log_error} from "@shared/utils"; -import {AuthService} from "@services/auth.service"; -import {ReportingExportApiService} from "@api/reporting-export-api.service"; -import {NzTableQueryParams} from "ng-zorro-antd/table"; -import {IProjectCategoryViewModel} from "@interfaces/project-category"; -import {ReportingService} from "../../../reporting.service"; -import {ISelectableProject} from "@interfaces/selectable-project"; -import {merge} from "rxjs"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {LAST_MONTH, LAST_QUARTER, LAST_WEEK, PREV_MONTH, PREV_WEEK, YESTERDAY} from "@shared/constants"; -import moment from "moment"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; - - -@Component({ - selector: 'worklenz-rpt-members', - templateUrl: './rpt-members.component.html', - styleUrls: ['./rpt-members.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptMembersComponent implements OnInit, OnDestroy { - private readonly FILTER_INDEX_KEY = "worklenz.projects.filter_index"; - - loading = false; - loadingProjects = false; - loadingTeams = false; - loadingCategories = false; - filteredByTeam = false; - selectAllTeams = true; - selectAllProjects = true; - selectNoCategory = true; - isDurationLabelSelected = true; - - - teamsFilterString: string | null = null; - teamSearchText: string | null = null; - projectSearchText: string | null = null; - categorySearchText: string | null = null; - - result: IRPTMemberResponse | null = null; - members: IRPTMember[] = []; - teams: IProjectCategoryViewModel[] = []; - projectsDropdown: ISelectableProject[] = []; - categoriesDropdown: IProjectCategoryViewModel[] = []; - - searchText!: string; - - // pagination - total = 0; - pageSize = 10; - pageIndex = 1; - paginationSizes = [5, 10, 15, 20, 50, 100]; - sortField: string | null = null; - sortOrder: string | null = null; - - initial = true; - - constructor( - private readonly app: AppService, - private readonly cdr: ChangeDetectorRef, - private readonly api: ReportingApiService, - private readonly drawer: ReportingDrawersService, - private readonly auth: AuthService, - private readonly exportApi: ReportingExportApiService, - private readonly service: ReportingService, - private readonly socket: Socket, - private readonly taskView: TaskViewService - ) { - this.app.setTitle("Reporting - Members"); - - merge( - this.service.onDurationChange, - this.service.onDateRangeChange, - this.service.onIncludeToggleChange - ) - .pipe(takeUntilDestroyed()) - .subscribe(() => { - void this.get(true); - }); - - merge( - this.taskView.onRefresh - ).pipe(takeUntilDestroyed()) - .subscribe(() => { - void this.get(true); - }); - - } - - async ngOnInit() { - this.socket.on(SocketEvents.TASK_STATUS_CHANGE.toString(), this.refreshList); - this.socket.on(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), this.refreshList); - this.socket.on(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.refreshList); - this.socket.on(SocketEvents.TASK_TIME_ESTIMATION_CHANGE.toString(), this.refreshList); - this.socket.on(SocketEvents.TASK_TIMER_STOP.toString(), this.refreshList); - // await this.get(true); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_STATUS_CHANGE.toString(), this.refreshList); - this.socket.removeListener(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), this.refreshList); - this.socket.removeListener(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.refreshList); - this.socket.removeListener(SocketEvents.TASK_TIME_ESTIMATION_CHANGE.toString(), this.refreshList); - this.socket.removeListener(SocketEvents.TASK_TIMER_STOP.toString(), this.refreshList); - } - - refreshList = async () => { - await this.get(false); - } - - onQueryParamsChange(params: NzTableQueryParams) { - const {pageSize, pageIndex, sort} = params; - - this.pageIndex = pageIndex; - this.pageSize = pageSize; - - const currentSort = sort.find(item => item.value !== null); - - this.sortField = currentSort?.key ?? null; - this.sortOrder = currentSort?.value ?? null; - void this.get(true); - } - - private async setDatesForKeys() { - if (this.service.getDuration()?.key) { - const key = this.service.getDuration()?.key; - const today = moment(); - - switch (key) { - case YESTERDAY: - const yesterday = moment().subtract(1, "days"); - this.service.setDateRange([yesterday.toString(), yesterday.toString()]); - break; - case LAST_WEEK: - const lastWeekStart = moment().subtract(1, "weeks"); - this.service.setDateRange([lastWeekStart.toString(), today.toString()]); - break; - case LAST_MONTH: - const lastMonthStart = moment().subtract(1, "months"); - this.service.setDateRange([lastMonthStart.toString(), today.toString()]); - break; - case LAST_QUARTER: - const lastQuaterStart = moment().subtract(3, "months"); - this.service.setDateRange([lastQuaterStart.toString(), today.toString()]); - break; - case PREV_WEEK: - const prevWeekStart = moment().subtract(1, "weeks").startOf("week"); - const prevWeekEnd = moment().subtract(1, "weeks").endOf("week"); - this.service.setDateRange([prevWeekStart.toString(), prevWeekEnd.toString()]); - break; - case PREV_MONTH: - const prevMonthStart = moment().subtract(1, "month").startOf("month"); - const prevMonthEnd = moment().subtract(1, "month").endOf("month"); - this.service.setDateRange([prevMonthStart.toString(), prevMonthEnd.toString()]); - break; - } - } - } - - private async get(loading = true) { - try { - this.loading = loading; - const projects = this.getSelectedProjectIds(); - - if (this.isDurationLabelSelected) { - await this.setDatesForKeys(); - } - - const res = await this.api.getMembers({ - index: this.pageIndex, - size: this.pageSize, - field: this.sortField, - order: this.sortOrder, - search: this.searchText || null, - filter: this.filterIndex.toString(), - teams: this.teamsFilterString, - duration: this.service.getDuration()?.key, - date_range: this.service.getDateRange(), - archived: this.service.getIncludeToggle(), - projects - }); - if (res.done) { - this.result = res.body || null; - this.total = res.body.total || 0; - this.members = res.body.members; - } - this.loading = false; - } catch (e) { - this.loading = false; - } - - this.cdr.markForCheck(); - } - - async getTeams() { - try { - this.loadingTeams = true; - const res = await this.api.getOverviewTeams(); - if (res.done) { - for (const team of res.body) { - team.selected = true; - } - this.teams = res.body; - await this.refreshCategories(); - } - this.loadingTeams = false; - } catch (e) { - this.loadingTeams = false; - } - this.cdr.markForCheck(); - } - - get filterIndex() { - return +(localStorage.getItem(this.FILTER_INDEX_KEY) || 0); - } - - trackBy(index: number, item: IRPTMember) { - return item.id; - } - - openMember(data: IRPTMember) { - if(this.result?.team) { - this.service.setCurrentTeam({ - id: this.result?.team.id as string, - name: this.result?.team.name as string, - projects_count: 0, - members: [], - selected: false - }) - } - this.drawer.openSingleMember(data); - } - - async export() { - const session = this.auth.getCurrentSession(); - if (!session?.name) return; - if (this.isDurationLabelSelected) { - await this.setDatesForKeys(); - } - try { - this.exportApi.exportMembers( - session.team_name, - this.service.getDuration()?.key, - this.service.getDateRange(), - this.service.getIncludeToggle() - ); - } catch (e) { - log_error(e); - } - } - - searchProjects() { - void this.get(false); - } - - applyTeamsFilter() { - const selectedTeams = this.teams.filter(c => c.selected); - this.filteredByTeam = !!selectedTeams.length; - const filterString = selectedTeams.map(c => c.id).join("+"); - this.teamsFilterString = filterString || null; - void this.get(); - } - - async getProjects(teams: string[], categories: string[]) { - try { - this.loadingProjects = true; - const res = await this.api.getAllocationProjects(teams, categories, this.selectNoCategory); - if (res.done) { - this.projectsDropdown = res.body; - } - this.loadingProjects = false; - } catch (e) { - this.loadingProjects = false; - } - this.cdr.markForCheck(); - } - - async getCategories(teams: string[]) { - try { - this.loadingCategories = true; - const res = await this.api.getCategories(teams); - if (res.done) { - this.categoriesDropdown = res.body; - } - this.loadingCategories = false; - this.cdr.markForCheck(); - } catch (e) { - this.loadingCategories = false; - this.cdr.markForCheck(); - } - } - - async refreshCategories() { - await this.getCategories(this.getSelectedTeamIds()); - void this.refreshProjects(); - } - - async refreshProjects() { - await this.getProjects(this.getSelectedTeamIds(), this.getSelectedCategories()); - void this.get(); - } - - private getSelectedTeamIds(): string[] { - const filter = this.teams.filter(t => t.selected); - const ids = filter.map(t => t.id) as string[]; - return ids || []; - } - - private getSelectedCategories(): string[] { - const filter = this.categoriesDropdown.filter(c => c.selected); - const ids = filter.map(c => c.id) as string[]; - return ids || []; - } - - private getSelectedProjectIds(): string[] { - const filter = this.projectsDropdown.filter(t => t.selected); - const ids = filter.map(t => t.id) as string[]; - return ids || []; - } - - detectChanges() { - this.cdr.markForCheck(); - } - - projectsChanged() { - void this.get(); - } - - checkTeam() { - this.selectAllTeams = false - this.applyTeamsFilter(); - this.refreshProjects(); - } - - checkProject() { - this.selectAllProjects = false - this.projectsChanged(); - } - - selectAllTeamsChecked(checked: boolean) { - if (checked) { - for (const item of this.teams) { - item.selected = true; - } - this.refreshProjects(); - this.applyTeamsFilter(); - this.cdr.markForCheck(); - } else { - for (const item of this.teams) { - item.selected = false; - } - this.refreshProjects(); - this.applyTeamsFilter(); - this.cdr.markForCheck(); - } - } - - selectAllProjectsChecked(checked: boolean) { - if (checked) { - for (const item of this.projectsDropdown) { - item.selected = true; - } - this.projectsChanged(); - this.cdr.markForCheck(); - } else { - for (const item of this.projectsDropdown) { - item.selected = false; - } - this.projectsChanged(); - this.cdr.markForCheck(); - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview-routing.module.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview-routing.module.ts deleted file mode 100644 index aa489b74..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview-routing.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {RptOverviewComponent} from "./rpt-overview/rpt-overview.component"; - -const routes: Routes = [ - {path: "", component: RptOverviewComponent} -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class RptOverviewRoutingModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview.module.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview.module.ts deleted file mode 100644 index f43baa7f..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview.module.ts +++ /dev/null @@ -1,81 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; - -import {RptOverviewRoutingModule} from './rpt-overview-routing.module'; -import {RptOverviewComponent} from "./rpt-overview/rpt-overview.component"; -import {RptHeaderComponent} from "../../components/rpt-header/rpt-header.component"; -import {NzLayoutModule} from "ng-zorro-antd/layout"; -import {NzCardModule} from "ng-zorro-antd/card"; -import {NzAvatarModule} from "ng-zorro-antd/avatar"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {RptOverviewCardsComponent} from './rpt-overview/rpt-overview-cards/rpt-overview-cards.component'; -import {NzTableModule} from "ng-zorro-antd/table"; -import {AvatarsComponent} from "@admin/components/avatars/avatars.component"; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzTabsModule} from "ng-zorro-antd/tabs"; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; -import {BindNaPipe} from "@pipes/bind-na.pipe"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {FormsModule} from "@angular/forms"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {NzGridModule} from "ng-zorro-antd/grid"; -import {NzBreadCrumbModule} from "ng-zorro-antd/breadcrumb"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import {NzProgressModule} from "ng-zorro-antd/progress"; -import {NzCollapseModule} from "ng-zorro-antd/collapse"; -import {NzSelectModule} from "ng-zorro-antd/select"; -import {ReportingModule} from "../../reporting.module"; -import {RptTeamDrawerModule} from "../../drawers/rpt-team-drawer/rpt-team-drawer.module"; -import {RptProjectDrawerModule} from "../../drawers/rpt-project-drawer/rpt-project-drawer.module"; -import {RptMemberDrawerModule} from "../../drawers/rpt-member-drawer/rpt-member-drawer.module"; -import {RptTasksDrawerModule} from "../../drawers/rpt-tasks-drawer/rpt-tasks-drawer.module"; -import {RptTaskViewDrawerModule} from "../../drawers/rpt-task-view-drawer/rpt-task-view-drawer.module"; -import {NzTagModule} from "ng-zorro-antd/tag"; - -@NgModule({ - declarations: [ - RptOverviewComponent, - RptOverviewCardsComponent - ], - imports: [ - CommonModule, - RptOverviewRoutingModule, - RptHeaderComponent, - NzLayoutModule, - NzCardModule, - NzAvatarModule, - NzIconModule, - NzTypographyModule, - NzTableModule, - AvatarsComponent, - NzDrawerModule, - NzSpaceModule, - NzButtonModule, - NzTabsModule, - SafeStringPipe, - BindNaPipe, - NzSkeletonModule, - NzInputModule, - FormsModule, - SearchByNamePipe, - NzGridModule, - NzBreadCrumbModule, - NzBadgeModule, - NzProgressModule, - NzCollapseModule, - NzSelectModule, - ReportingModule, - RptTeamDrawerModule, - RptProjectDrawerModule, - RptMemberDrawerModule, - RptTasksDrawerModule, - RptTaskViewDrawerModule, - NzTagModule - ] -}) -export class RptOverviewModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview-cards/rpt-overview-cards.component.html b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview-cards/rpt-overview-cards.component.html deleted file mode 100644 index 6cfe21ec..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview-cards/rpt-overview-cards.component.html +++ /dev/null @@ -1,54 +0,0 @@ -
-
- - - - - - - - -

{{model.teams?.projects}} Projects

-

{{model.teams?.members}} Members

-
- - - - - - - - - -

{{model.projects?.active}} Active Projects

-

{{model.projects?.overdue}} Overdue - Projects

-
- - - - - - - - - -

{{model.members?.unassigned}} Unassigned Members

-

- {{model.members?.overdue}} Members with Overdue Tasks -

-
-
-
diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview-cards/rpt-overview-cards.component.scss b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview-cards/rpt-overview-cards.component.scss deleted file mode 100644 index 3b77ac27..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview-cards/rpt-overview-cards.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -.card-icon { - font-size: 42px; -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview-cards/rpt-overview-cards.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview-cards/rpt-overview-cards.component.spec.ts deleted file mode 100644 index fe7e3394..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview-cards/rpt-overview-cards.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptOverviewCardsComponent} from './rpt-overview-cards.component'; - -describe('RptOverviewCardsComponent', () => { - let component: RptOverviewCardsComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptOverviewCardsComponent] - }); - fixture = TestBed.createComponent(RptOverviewCardsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview-cards/rpt-overview-cards.component.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview-cards/rpt-overview-cards.component.ts deleted file mode 100644 index c571d247..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview-cards/rpt-overview-cards.component.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit} from '@angular/core'; -import {ReportingApiService} from "../../../../reporting-api.service"; -import {IRPTOverviewStatistics} from "../../../../interfaces"; -import {ReportingService} from "../../../../reporting.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; - -@Component({ - selector: 'worklenz-rpt-overview-cards', - templateUrl: './rpt-overview-cards.component.html', - styleUrls: ['./rpt-overview-cards.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptOverviewCardsComponent implements OnInit { - loading = false; - - model: IRPTOverviewStatistics = {}; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: ReportingApiService, - private readonly service: ReportingService - ) { - this.service.onIncludeToggleChange - .pipe(takeUntilDestroyed()) - .subscribe(() => { - void this.get(); - }); - } - - ngOnInit() { - void this.get(); - } - - async get() { - try { - this.loading = true; - const res = await this.api.getOverviewStatistics(this.service.getIncludeToggle()); - if (res.done) { - this.model = res.body; - } - this.loading = false; - } catch (e) { - this.loading = false; - } - - this.cdr.markForCheck(); - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview.component.html b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview.component.html deleted file mode 100644 index 196fd7ea..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview.component.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - -

Teams

- - - - Name - Projects - Members - - - - - {{ data.name }} - {{ data.projects_count }} - - - - - - - - - - - - -
-
- - - - - - diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview.component.scss b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview.component.spec.ts deleted file mode 100644 index e7194691..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptOverviewComponent} from './rpt-overview.component'; - -describe('RptOverviewComponent', () => { - let component: RptOverviewComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptOverviewComponent] - }); - fixture = TestBed.createComponent(RptOverviewComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview.component.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview.component.ts deleted file mode 100644 index 59555bf0..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-overview/rpt-overview/rpt-overview.component.ts +++ /dev/null @@ -1,66 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit} from '@angular/core'; -import {AppService} from "@services/app.service"; -import {IRPTTeam} from "../../../interfaces"; -import {ReportingApiService} from "../../../reporting-api.service"; -import {ReportingDrawersService} from "../../../drawers/reporting-drawers.service"; -import {ReportingService} from "../../../reporting.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; - -@Component({ - selector: 'worklenz-rpt-overview', - templateUrl: './rpt-overview.component.html', - styleUrls: ['./rpt-overview.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptOverviewComponent implements OnInit { - loading = false; - isDurationLabelSelected = true; - teams: IRPTTeam[] = []; - - selectedTeam: IRPTTeam | null = null; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: ReportingApiService, - private readonly app: AppService, - private readonly drawer: ReportingDrawersService, - private reportingApi: ReportingService, - ) { - this.app.setTitle("Reporting - Overview"); - - this.reportingApi.onIncludeToggleChange - .pipe(takeUntilDestroyed()) - .subscribe(() => { - void this.get(); - }); - } - - ngOnInit() { - void this.get(); - } - - async get() { - try { - this.loading = true; - const res = await this.api.getOverviewTeams(this.reportingApi.getIncludeToggle()); - if (res.done) { - this.teams = res.body; - } - this.loading = false; - } catch (e) { - this.loading = false; - } - - this.cdr.markForCheck(); - } - - openTeamInfo(team: IRPTTeam) { - this.selectedTeam = team; - this.drawer.openTeam(team); - } - - trackBy(index: number, data: IRPTTeam) { - return data.id; - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects-routing.module.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects-routing.module.ts deleted file mode 100644 index 37475f11..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects-routing.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {RptProjectsComponent} from "./rpt-projects/rpt-projects.component"; - -const routes: Routes = [ - {path: "", component: RptProjectsComponent} -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class RptProjectsRoutingModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects.module.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects.module.ts deleted file mode 100644 index 3a34fdff..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects.module.ts +++ /dev/null @@ -1,97 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; - -import {RptProjectsRoutingModule} from './rpt-projects-routing.module'; -import {RptProjectsComponent} from './rpt-projects/rpt-projects.component'; -import {RptHeaderComponent} from "../../components/rpt-header/rpt-header.component"; -import {AvatarsComponent} from "@admin/components/avatars/avatars.component"; -import {NzCardModule} from "ng-zorro-antd/card"; -import {NzLayoutModule} from "ng-zorro-antd/layout"; -import {NzTableModule} from "ng-zorro-antd/table"; -import {BindNaPipe} from "@pipes/bind-na.pipe"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzTagModule} from "ng-zorro-antd/tag"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {RptProjectDrawerModule} from "../../drawers/rpt-project-drawer/rpt-project-drawer.module"; -import {RptMemberDrawerModule} from "../../drawers/rpt-member-drawer/rpt-member-drawer.module"; -import {RptTaskViewDrawerModule} from "../../drawers/rpt-task-view-drawer/rpt-task-view-drawer.module"; -import {RptTasksDrawerModule} from "../../drawers/rpt-tasks-drawer/rpt-tasks-drawer.module"; -import {RptTeamDrawerModule} from "../../drawers/rpt-team-drawer/rpt-team-drawer.module"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {FormsModule, ReactiveFormsModule} from "@angular/forms"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import {NaComponent} from "@admin/components/na/na.component"; -import {TasksProgressBarComponent} from "@admin/components/tasks-progress-bar/tasks-progress-bar.component"; -import { - RptProjectsEstimatedVsActualComponent -} from './rpt-projects/rpt-projects-estimated-vs-actual/rpt-projects-estimated-vs-actual.component'; -import {NgChartsModule} from "ng2-charts"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {ReportingModule} from "../../reporting.module"; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {EllipsisPipe} from "@pipes/ellipsis.pipe"; -import {ProjectUpdatesDrawerComponent} from "@admin/components/project-updates-drawer/project-updates-drawer.component"; -import { - ProjectCategoriesAutocompleteComponent -} from "@admin/components/project-categories-autocomplete/project-categories-autocomplete.component"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzCheckboxModule} from "ng-zorro-antd/checkbox"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {NzMenuModule} from "ng-zorro-antd/menu"; -import {NzWaveModule} from "ng-zorro-antd/core/wave"; -import {FirstCharUpperPipe} from "@pipes/first-char-upper.pipe"; -import {NzAvatarModule} from "ng-zorro-antd/avatar"; - -@NgModule({ - declarations: [ - RptProjectsComponent, - RptProjectsEstimatedVsActualComponent - ], - exports: [ - RptProjectsEstimatedVsActualComponent - ], - imports: [ - CommonModule, - RptProjectsRoutingModule, - RptHeaderComponent, - AvatarsComponent, - NzCardModule, - NzLayoutModule, - NzTableModule, - BindNaPipe, - NzTypographyModule, - NzTagModule, - NzIconModule, - RptProjectDrawerModule, - RptMemberDrawerModule, - RptTaskViewDrawerModule, - RptTasksDrawerModule, - RptTeamDrawerModule, - NzInputModule, - ReactiveFormsModule, - FormsModule, - SearchByNamePipe, - NzBadgeModule, - NaComponent, - TasksProgressBarComponent, - NgChartsModule, - NzToolTipModule, - ReportingModule, - SafeStringPipe, - NzSpaceModule, - EllipsisPipe, - ProjectUpdatesDrawerComponent, - ProjectCategoriesAutocompleteComponent, - NzButtonModule, - NzCheckboxModule, - NzDropDownModule, - NzMenuModule, - NzWaveModule, - FirstCharUpperPipe, - NzAvatarModule - ] -}) -export class RptProjectsModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects-estimated-vs-actual/rpt-projects-estimated-vs-actual.component.html b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects-estimated-vs-actual/rpt-projects-estimated-vs-actual.component.html deleted file mode 100644 index c39f220e..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects-estimated-vs-actual/rpt-projects-estimated-vs-actual.component.html +++ /dev/null @@ -1,11 +0,0 @@ -
- - - Estimated :{{estimatedTimeString}} - Actual :{{actualTimeString}} -
diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects-estimated-vs-actual/rpt-projects-estimated-vs-actual.component.scss b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects-estimated-vs-actual/rpt-projects-estimated-vs-actual.component.scss deleted file mode 100644 index 4decc6d7..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects-estimated-vs-actual/rpt-projects-estimated-vs-actual.component.scss +++ /dev/null @@ -1,16 +0,0 @@ -.estimated-time-span { - position: absolute; - top: 3px; - left: 4px; - font-size: 12px; - font-weight: 600; - width: 100%; -} -.actual-time-span { - position: absolute; - bottom: 1px; - left: 4px; - font-size: 12px; - font-weight: 600; - width: 100%; -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects-estimated-vs-actual/rpt-projects-estimated-vs-actual.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects-estimated-vs-actual/rpt-projects-estimated-vs-actual.component.spec.ts deleted file mode 100644 index 3fc3e7dc..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects-estimated-vs-actual/rpt-projects-estimated-vs-actual.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { RptProjectsEstimatedVsActualComponent } from './rpt-projects-estimated-vs-actual.component'; - -describe('RptProjectsEstimatedVsActualComponent', () => { - let component: RptProjectsEstimatedVsActualComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptProjectsEstimatedVsActualComponent] - }); - fixture = TestBed.createComponent(RptProjectsEstimatedVsActualComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects-estimated-vs-actual/rpt-projects-estimated-vs-actual.component.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects-estimated-vs-actual/rpt-projects-estimated-vs-actual.component.ts deleted file mode 100644 index a45cd5f0..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects-estimated-vs-actual/rpt-projects-estimated-vs-actual.component.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { - AfterViewInit, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Input, - OnChanges, - SimpleChanges, - ViewChild -} from '@angular/core'; -import {ChartConfiguration} from 'chart.js'; -import {BaseChartDirective} from "ng2-charts"; - -@Component({ - selector: 'worklenz-rpt-projects-estimated-vs-actual', - templateUrl: './rpt-projects-estimated-vs-actual.component.html', - styleUrls: ['./rpt-projects-estimated-vs-actual.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptProjectsEstimatedVsActualComponent implements OnChanges, AfterViewInit { - @ViewChild(BaseChartDirective) barChart: BaseChartDirective | undefined; - - @Input({required: true}) actualTime: number | null = 0; - @Input({required: true}) estimatedTime: number | null = 0; - @Input() estimatedTimeString: string | null = null; - @Input() actualTimeString: string | null = null; - - visible = false; - - public barChartLegend = false; - public barChartPlugins = []; - - public barChartData: ChartConfiguration<'bar'>['data'] = { - labels: [''], - datasets: [], - }; - - public barChartOptions: ChartConfiguration<'bar'>['options'] = { - plugins: { - datalabels: { - display: false - } - }, - responsive: false, - maintainAspectRatio: false, - indexAxis: "y", - scales: { - x: { - grid: { - display: false, - }, - display: false - }, - y: { - grid: { - display: false - }, - display: false - }, - } - }; - - constructor( - private readonly cdr: ChangeDetectorRef - ) { - } - - ngOnChanges(changes: SimpleChanges) { - setTimeout(() => { - this.barChart?.update(); - this.cdr.markForCheck(); - }, 1000) - } - - ngAfterViewInit() { - this.visible = true; - setTimeout(() => { - this.barChart?.data?.datasets.push( - {data: [this.estimatedTime], label: '', backgroundColor: ['#A5AAD9'], barThickness: 30}, - {data: [this.actualTime], label: '', backgroundColor: ['#c191cc'], barThickness: 30} - ); - this.barChart?.update(); - this.cdr.markForCheck(); - }, 1000) - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects.component.html b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects.component.html deleted file mode 100644 index 3a9ab7d2..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects.component.html +++ /dev/null @@ -1,297 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Project - - Estimated vs Actual - Tasks Progress - Last Activity - Status - - Start/End dates - Days Left/Overdue - - Project Health - - Category - Project Update - Client - Team - Project Manager - - - - - - - -
- - - {{data.name}} - - -
- - - - - - - - - - - - - - - - - - - - - {{data.last_activity.last_activity_string}}. - - - - - - - - - - - - - - - - - - - - - - {{ data.days_left }} days overdue - - Today - {{ data.days_left }} days left - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ data.client }} - - - - - - - {{data.team_name}} - - - - - - - - {{data.project_manager.name | ellipsis: 15}} - - - - - - -
-
-
- - - - - - - - - - - - - -
    -
  • - {{item.name}} -
  • -
-
- - -
    -
  • - {{item.name}} -
  • -
-
- - -
    -
  • - -
  • -
  • - -
  • -
-
- - -
    -
  • - -
  • -
  • - {{item.name}} -
  • -
-
- diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects.component.scss b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects.component.scss deleted file mode 100644 index 73f70e03..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects.component.scss +++ /dev/null @@ -1,27 +0,0 @@ - -.ellipsis { - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.open-btn { - position: absolute; - right: 0; - top: 0; - bottom: 0; - opacity: 0; - margin-top: auto; - margin-bottom: auto; - padding: 0px 4px; - background: #edebf0 !important; - color: rgba(0, 0, 0, 0.85); - border: none; -} - -tr:hover { - & .open-btn { - opacity: 1; - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects.component.spec.ts deleted file mode 100644 index 527f9ba1..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {RptProjectsComponent} from './rpt-projects.component'; - -describe('RptProjectsComponent', () => { - let component: RptProjectsComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [RptProjectsComponent] - }); - fixture = TestBed.createComponent(RptProjectsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects.component.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects.component.ts deleted file mode 100644 index 99da5d5c..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-projects/rpt-projects/rpt-projects.component.ts +++ /dev/null @@ -1,400 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild} from '@angular/core'; -import {AppService} from "@services/app.service"; -import {ReportingApiService} from "../../../reporting-api.service"; -import {IRPTProject} from "../../../interfaces"; -import {ReportingDrawersService} from "../../../drawers/reporting-drawers.service"; -import {log_error} from "@shared/utils"; -import {IProjectHealth} from "@interfaces/project-health"; -import {ProjectHealthsApiService} from "@api/project-healths-api.service"; -import {ReportingExportApiService} from "@api/reporting-export-api.service"; -import {AuthService} from "@services/auth.service"; -import {NzTableQueryParams} from "ng-zorro-antd/table"; -import {SocketEvents} from "@shared/socket-events"; -import {Socket} from "ngx-socket-io"; -import {IProjectStatus} from "@interfaces/project-status"; -import {ProjectStatusesApiService} from "@api/project-statuses-api.service"; -import {ProjectUpdatesDrawerComponent} from "@admin/components/project-updates-drawer/project-updates-drawer.component"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {ProjectUpdatesService} from "@services/project-updates.service"; -import {IProjectCategoryViewModel} from "@interfaces/project-category"; -import {ProjectCategoriesApiService} from "@api/project-categories-api.service"; -import {merge} from "rxjs"; -import {ReportingService} from "../../../reporting.service"; -import {AvatarNamesMap} from "@shared/constants"; -import {ProjectManagersApiService} from "@api/project-managers-api.service"; -import {IProjectManager} from "@interfaces/project-manager"; - -@Component({ - selector: 'worklenz-rpt-projects', - templateUrl: './rpt-projects.component.html', - styleUrls: ['./rpt-projects.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RptProjectsComponent implements OnInit, OnDestroy { - @ViewChild(ProjectUpdatesDrawerComponent) updatesDrawer!: ProjectUpdatesDrawerComponent; - - private readonly FILTER_INDEX_KEY = "worklenz.projects.filter_index"; - private readonly PROJECT_LIST_COLUMNS = "worklenz.reporting.projects.column_list"; - - loading = false; - loadingStatuses = false; - loadingHealths = false; - loadingCategories = false; - loadingProjectManagers = false; - - projects: IRPTProject[] = []; - projHealths: IProjectHealth[] = []; - projectStatuses: IProjectStatus[] = []; - projCategories: IProjectCategoryViewModel[] = []; - projectManagers: IProjectManager[] = []; - searchText!: string; - - // pagination - total:number | null = null; - pageSize = 10; - pageIndex = 1; - paginationSizes = [5, 10, 15, 20, 50, 100]; - sortField: string | null = null; - sortOrder: string | null = null; - categorySearchText: string | null = null; - projectManagerSearchText: string | null = null; - pageTitle: string = "Projects" - - public COLUMN_KEYS = { - CLIENT: "CLIENT", - ESTIMATED_VS_ACTUAL: "ESTIMATED_VS_ACTUAL", - TASKS_PROGRESS: "TASKS_PROGRESS", - LAST_ACTIVITY: "LAST_ACTIVITY", - STATUS: "STATUS", - START_END_DATE: "START_END_DATE", - DAYS_LEFT_OVERDUE: "DAYS_LEFT_OVERDUE", - CATEGORY: "CATEGORY", - HEALTH: "HEALTH", - UPDATE: "UPDATE", - TEAM: "TEAM", - }; - - public columns: { key: string; label: string; pinned: boolean }[] = [ - {key: "ESTIMATED_VS_ACTUAL", label: "Estimated vs Actual", pinned: true}, - {key: "TASKS_PROGRESS", label: "Tasks Progress", pinned: true}, - {key: "LAST_ACTIVITY", label: "Last Activity", pinned: true}, - {key: "STATUS", label: "Status", pinned: true}, - {key: "START_END_DATE", label: "Start/End dates", pinned: true}, - {key: "DAYS_LEFT_OVERDUE", label: "Days Left/Overdue", pinned: true}, - {key: "HEALTH", label: "Project Health", pinned: true}, - {key: "CATEGORY", label: "Category", pinned: true}, - {key: "UPDATE", label: "Project Update", pinned: true}, - {key: "CLIENT", label: "Client", pinned: true}, - {key: "TEAM", label: "Team", pinned: true}, - ]; - - statusActive = true; - categoryActive = true; - startEndDateActive = true; - daysLeftActive = true; - estimatedActive = true; - progressActive = true; - lastActivityActive = true; - healthActive = true; - updateActive = true; - clientActive = false; - teamActive = false; - projectManagerActive = true; - - get filterIndex() { - return +(localStorage.getItem(this.FILTER_INDEX_KEY) || 0); - } - - get columnList() { - if (localStorage.hasOwnProperty(this.PROJECT_LIST_COLUMNS)) - return JSON.parse(localStorage.getItem(this.PROJECT_LIST_COLUMNS) || ''); - } - - private getSelectedStatusIds(): string[] { - const filter = this.projectStatuses.filter(t => t.selected); - const ids = filter.map(t => t.id) as string[]; - return ids || []; - } - - private getSelectedHealthIds(): string[] { - const filter = this.projHealths.filter(t => t.selected); - const ids = filter.map(t => t.id) as string[]; - return ids || []; - } - - private getSelectedCategoryIds(): string[] { - const filter = this.projCategories.filter(t => t.selected); - const ids = filter.map(t => t.id) as string[]; - return ids || []; - - } - - private getSelectedProjectManager(): string[] { - const filter = this.projectManagers.filter(pm => pm.selected); - const ids = filter.map(pm => pm.id) as string[]; - return ids || []; - } - - constructor( - private readonly app: AppService, - private readonly cdr: ChangeDetectorRef, - private readonly api: ReportingApiService, - private readonly drawer: ReportingDrawersService, - private readonly projectHealthsApi: ProjectHealthsApiService, - private readonly exportApi: ReportingExportApiService, - private readonly auth: AuthService, - private readonly socket: Socket, - private readonly statusesApi: ProjectStatusesApiService, - private readonly projectUpdatesService: ProjectUpdatesService, - private readonly categoriesApi: ProjectCategoriesApiService, - private readonly projectManagersApi: ProjectManagersApiService, - private readonly service: ReportingService - ) { - this.app.setTitle("Reporting - Projects"); - - this.projectUpdatesService.onGetLatestUpdate - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.get(false); - }); - - merge( - this.service.onDurationChange, - this.service.onDateRangeChange, - this.service.onIncludeToggleChange - ) - .pipe(takeUntilDestroyed()) - .subscribe(() => { - void this.get(); - }); - - this.socket.on(SocketEvents.CREATE_PROJECT_CATEGORY.toString(), this.newCategoryReceived); - } - - ngOnInit() { - void this.getProjectHealths(); - void this.getProjectStatuses(); - void this.getProjectCategories(); - void this.getProjectManagers(); - this.socket.on(SocketEvents.PROJECT_START_DATE_CHANGE.toString(), this.refreshWithoutLoading); - this.socket.on(SocketEvents.PROJECT_END_DATE_CHANGE.toString(), this.refreshWithoutLoading); - - if (this.columnList) { - this.columns = this.columnList; - this.updateState(); - } - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.PROJECT_START_DATE_CHANGE.toString(), this.refreshWithoutLoading); - this.socket.removeListener(SocketEvents.PROJECT_END_DATE_CHANGE.toString(), this.refreshWithoutLoading); - } - - searchProjects() { - this.get(false); - } - - onQueryParamsChange(params: NzTableQueryParams) { - const {pageSize, pageIndex, sort} = params; - this.pageIndex = pageIndex; - this.pageSize = pageSize; - - const currentSort = sort.find(item => item.value !== null); - - this.sortField = currentSort?.key ?? null; - this.sortOrder = currentSort?.value ?? null; - void this.get(); - } - - public async get(loading = true) { - try { - this.loading = loading; - const statuses = this.getSelectedStatusIds(); - const healths = this.getSelectedHealthIds(); - const categories = this.getSelectedCategoryIds(); - const projectManagers = this.getSelectedProjectManager(); - - const res = await this.api.getProjects({ - index: this.pageIndex, - size: this.pageSize, - field: this.sortField, - order: this.sortOrder, - search: this.searchText || null, - filter: this.filterIndex.toString(), - statuses: statuses, - healths: healths, - categories: categories, - project_managers: projectManagers, - archived: this.service.getIncludeToggle() - }); - if (res.done) { - this.total = res.body.total || 0; - this.projects = res.body.projects || []; - this.pageTitle = this.total + " Projects" - } - this.loading = false; - } catch (e) { - this.loading = false; - } - this.cdr.markForCheck(); - } - - private async getProjectManagers() { - try { - this.loadingProjectManagers = true; - - const res = await this.projectManagersApi.get(); - - if (res.body) { - this.projectManagers = res.body; - } - - this.loadingProjectManagers = false; - } catch (e) { - log_error(e); - this.loadingProjectManagers = false; - } - this.cdr.markForCheck(); - } - - private async getProjectHealths() { - try { - this.loadingHealths = true; - const res = await this.projectHealthsApi.get(); - if (res) { - for (const health of res.body) { - health.selected = false; - } - this.projHealths = res.body; - } - this.loadingHealths = false; - } catch (e) { - log_error(e); - this.loadingHealths = false; - } - this.cdr.markForCheck(); - } - - async getProjectStatuses() { - try { - this.loadingStatuses = true; - const res = await this.statusesApi.get(); - if (res.done) { - for (const status of res.body) { - status.selected = false; - } - this.projectStatuses = res.body; - } - this.loadingStatuses = false; - } catch (e) { - log_error(e); - this.loadingStatuses = false; - } - this.cdr.markForCheck(); - } - - async getProjectCategories() { - try { - this.loadingCategories = true; - const res = await this.categoriesApi.getByOrg(); - if (res) { - for (const category of res.body) { - category.selected = false; - } - this.projCategories = res.body; - } - this.loadingCategories = false; - } catch (e) { - log_error(e); - this.loadingCategories = false; - } - this.cdr.markForCheck(); - } - - private newCategoryReceived = (response: { - id: string; - category_id: string; - category_name: string; - category_color: string - }) => { - if (response) { - const body = { - id: response.category_id, - name: response.category_name, - color_code: response.category_color, - selected: false, - } - - this.projCategories.push(body); - this.cdr.markForCheck(); - } - } - - trackBy(index: number, item: IRPTProject) { - return item.id; - } - - openProject(data: IRPTProject) { - this.drawer.openProject(data); - } - - async export() { - const session = this.auth.getCurrentSession(); - if (!session?.name) return; - try { - const res = await this.exportApi.exportProjects(session.team_name); - } catch (e) { - log_error(e); - } - } - - refreshWithoutLoading = () => { - void this.get(false); - } - - openUpdates(data: IRPTProject) { - this.updatesDrawer.open(data.id); - } - - protected async onColumnsToggle(checked: boolean, item: any) { - try { - item.pinned = checked; - this.updateState(); - } catch (e) { - // ignored - } - } - - private updateState() { - this.clientActive = this.canActive(this.COLUMN_KEYS.CLIENT); - this.teamActive = this.canActive(this.COLUMN_KEYS.TEAM); - this.categoryActive = this.canActive(this.COLUMN_KEYS.CATEGORY); - this.statusActive = this.canActive(this.COLUMN_KEYS.STATUS); - this.startEndDateActive = this.canActive(this.COLUMN_KEYS.START_END_DATE); - this.daysLeftActive = this.canActive(this.COLUMN_KEYS.DAYS_LEFT_OVERDUE); - this.estimatedActive = this.canActive(this.COLUMN_KEYS.ESTIMATED_VS_ACTUAL); - this.progressActive = this.canActive(this.COLUMN_KEYS.TASKS_PROGRESS); - this.lastActivityActive = this.canActive(this.COLUMN_KEYS.LAST_ACTIVITY); - this.healthActive = this.canActive(this.COLUMN_KEYS.HEALTH); - this.updateActive = this.canActive(this.COLUMN_KEYS.UPDATE); - this.cdr.markForCheck(); - this.setColumnList(); - } - - public canActive(key: string) { - return !!this.columns.find(c => c.key === key)?.pinned; - } - - setColumnList() { - localStorage.setItem(this.PROJECT_LIST_COLUMNS, JSON.stringify(this.columns)); - } - - detectChanges() { - this.cdr.markForCheck(); - } - - getColor(name?: string) { - return AvatarNamesMap[name?.charAt(0).toUpperCase() || 'A']; - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/rpt-time-estimation-vs-actual-routing.module.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/rpt-time-estimation-vs-actual-routing.module.ts deleted file mode 100644 index cfcfdd83..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/rpt-time-estimation-vs-actual-routing.module.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {RouterModule, Routes} from "@angular/router"; -import {NgModule} from "@angular/core"; -import { - TimeEstimationVsActualProjectsComponent -} from "./time-estimation-vs-actual-projects/time-estimation-vs-actual-projects.component"; - -const routes: Routes = [ - {path: "", component: TimeEstimationVsActualProjectsComponent} -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) - -export class RptTimeEstimationVsActualRoutingModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/rpt-time-estimation-vs-actual.module.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/rpt-time-estimation-vs-actual.module.ts deleted file mode 100644 index 6e1433cc..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/rpt-time-estimation-vs-actual.module.ts +++ /dev/null @@ -1,53 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {RptTimeEstimationVsActualRoutingModule} from "./rpt-time-estimation-vs-actual-routing.module"; -import { - TimeEstimationVsActualProjectsComponent -} from './time-estimation-vs-actual-projects/time-estimation-vs-actual-projects.component'; -import {NgChartsModule} from "ng2-charts"; -import {RptHeaderComponent} from "../../../components/rpt-header/rpt-header.component"; -import {NzLayoutModule} from "ng-zorro-antd/layout"; -import {NzCardModule} from "ng-zorro-antd/card"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {FormsModule} from "@angular/forms"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzCheckboxModule} from "ng-zorro-antd/checkbox"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzEmptyModule} from "ng-zorro-antd/empty"; -import {NzSegmentedModule} from "ng-zorro-antd/segmented"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; - - -@NgModule({ - declarations: [ - TimeEstimationVsActualProjectsComponent - ], - imports: [ - CommonModule, - RptTimeEstimationVsActualRoutingModule, - NgChartsModule, - RptHeaderComponent, - NzLayoutModule, - NzCardModule, - NzSpaceModule, - NzInputModule, - FormsModule, - NzDropDownModule, - NzButtonModule, - NzCheckboxModule, - SearchByNamePipe, - NzIconModule, - NzTypographyModule, - NzEmptyModule, - NzSegmentedModule, - NzToolTipModule, - NzBadgeModule, - ] -}) -export class RptTimeEstimationVsActualModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/time-estimation-vs-actual-projects/time-estimation-vs-actual-projects.component.html b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/time-estimation-vs-actual-projects/time-estimation-vs-actual-projects.component.html deleted file mode 100644 index 914d03c9..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/time-estimation-vs-actual-projects/time-estimation-vs-actual-projects.component.html +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - - - - - - -
-
-
- - -
-
-
- - - - - - - - - -
-
- - -
    -
  • - -
  • -
  • Select all -
  • -
  • -
  • - {{item.name}} -
  • -
-
- - - - -
    -
  • - -
  • -
  • Select all -
  • -
  • -
  • -
    -
    - {{item.name}} -
    -
    -
  • -
-
- - - -
    -
  • - -
  • -
  • Select all -
  • -
  • -
  • - No Category -
  • -
  • -
    -
    - - - -
    -
    -
  • -
-
diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/time-estimation-vs-actual-projects/time-estimation-vs-actual-projects.component.scss b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/time-estimation-vs-actual-projects/time-estimation-vs-actual-projects.component.scss deleted file mode 100644 index 8e1402d4..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/time-estimation-vs-actual-projects/time-estimation-vs-actual-projects.component.scss +++ /dev/null @@ -1,11 +0,0 @@ -.chartWrapper { - max-width: calc(100vw - 250px); - min-width: calc(100vw - 250px); - height: calc(100vh - 300px); - overflow: auto; -} - -canvas { - width: 100% !important; - height: 100% !important; -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/time-estimation-vs-actual-projects/time-estimation-vs-actual-projects.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/time-estimation-vs-actual-projects/time-estimation-vs-actual-projects.component.spec.ts deleted file mode 100644 index 9c525a23..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/time-estimation-vs-actual-projects/time-estimation-vs-actual-projects.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TimeEstimationVsActualProjectsComponent } from './time-estimation-vs-actual-projects.component'; - -describe('TimeEstimationVsActualProjectsComponent', () => { - let component: TimeEstimationVsActualProjectsComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TimeEstimationVsActualProjectsComponent] - }); - fixture = TestBed.createComponent(TimeEstimationVsActualProjectsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/time-estimation-vs-actual-projects/time-estimation-vs-actual-projects.component.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/time-estimation-vs-actual-projects/time-estimation-vs-actual-projects.component.ts deleted file mode 100644 index ec5a664e..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-estimation-vs-actual/time-estimation-vs-actual-projects/time-estimation-vs-actual-projects.component.ts +++ /dev/null @@ -1,431 +0,0 @@ -import {ChangeDetectorRef, Component, ElementRef, OnChanges, OnInit, SimpleChanges, ViewChild} from '@angular/core'; -import {BaseChartDirective} from "ng2-charts"; -import {IProjectCategoryViewModel} from "@interfaces/project-category"; -import {Chart, ChartConfiguration} from "chart.js"; -import {log_error} from "@shared/utils"; -import {ReportingService} from "../../../../reporting.service"; -import {ReportingApiService} from "../../../../reporting-api.service"; -import {merge} from "rxjs"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {IRPTTimeProject} from "../../../../interfaces"; -import {ISelectableProject} from "@interfaces/selectable-project"; -import moment from "moment"; -import {LAST_MONTH, LAST_QUARTER, LAST_WEEK, PREV_MONTH, PREV_WEEK, YESTERDAY} from "@shared/constants"; - -enum IToggleOptions { - 'WORKING_DAYS', 'MAN_DAYS' -} - -@Component({ - selector: 'worklenz-time-estimation-vs-actual-projects', - templateUrl: './time-estimation-vs-actual-projects.component.html', - styleUrls: ['./time-estimation-vs-actual-projects.component.scss'] -}) -export class TimeEstimationVsActualProjectsComponent implements OnInit, OnChanges { - @ViewChild(BaseChartDirective) barChart: BaseChartDirective | undefined; - @ViewChild('chartContainer') chartContainer!: ElementRef; - @ViewChild('exportChartCanvas') exportChartCanvas!: ElementRef; - - visible = false; - loading = false; - loadingTeams = false; - loadingCategories = false; - loadingProjects = false; - selectAllTeams = true; - selectAllCategories = true; - selectAllProjects = true; - selectNoCategory = true; - isDurationLabelSelected = true; - - chartHeight = 600; - chartWidth = 1080; - - teamSearchText: string | null = null; - categorySearchText: string | null = null; - projectSearchText: string | null = null; - tabTitle: string | null = "Working Days"; - - teamsDropdown: IProjectCategoryViewModel[] = []; - categoriesDropdown: IProjectCategoryViewModel[] = []; - projectsDropdown: ISelectableProject[] = []; - - barChartPlugins = []; - projects: IRPTTimeProject[] = []; - toggleOptions = ['Working Days', 'Man Days']; - type = 0; - - exportChart: Chart | null = null; - baseChart: Chart | null = null; - - barChartData: ChartConfiguration<'bar'>['data'] = { - labels: [], - datasets: [ - {data: [], label: 'Estimated Days ', backgroundColor: '#A5AAD9', barThickness: 50}, - {data: [], label: 'Actual Days ', backgroundColor: '#c191cc', barThickness: 50}, - ] - }; - - barChartOptions: ChartConfiguration<'bar'>['options'] = { - maintainAspectRatio: false, - scales: { - y: { - title: { - display: true, - text: 'Days', - align: "end", - font: { - family: 'Helvetica' - } - } - }, - x: { - title: { - display: true, - text: 'Project', - align: "end", - font: { - family: 'Helvetica' - } - }, - ticks: { - callback: function (value) { - return this.getLabelForValue(parseFloat(value)).substr(0, 30); - } - } - }, - }, - plugins: { - tooltip: { - callbacks: { - footer: (items) => { - if (items.length > 0) { - const project = this.projects[items[0].dataIndex]; - if (project.end_date) return 'Ends On: ' + moment(project.end_date).format("MMM, DD YYYY"); - } - return ''; - }, - } - }, - datalabels: { - color: 'white', - anchor: 'start', - align: 'start', - offset: -30, - borderColor: "#000", - textStrokeColor: 'black', - textStrokeWidth: 4, - } - } - }; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly service: ReportingService, - private readonly api: ReportingApiService, - ) { - merge( - this.service.onDurationChange, - this.service.onDateRangeChange, - this.service.onIncludeToggleChange - ) - .pipe(takeUntilDestroyed()) - .subscribe(() => { - void this.get(); - }); - } - - ngOnInit() { - this.chartHeight = window.innerHeight - 300; - void this.getTeams(); - this.cdr.markForCheck(); - } - - private async getTeams() { - try { - this.loadingTeams = true; - const res = await this.api.getOverviewTeams(); - if (res.done) { - this.loadingTeams = false; - const teams = []; - for (const team of res.body) { - teams.push({selected: true, name: team.name, id: team.id}) - } - this.teamsDropdown = teams; - await this.refreshCategories(); - this.cdr.markForCheck(); - } - } catch (e) { - log_error(e); - this.loadingTeams = false; - this.cdr.markForCheck(); - } - } - - async getCategories(teams: string[]) { - try { - this.loadingCategories = true; - const res = await this.api.getCategories(teams); - if (res.done) { - this.categoriesDropdown = res.body; - } - this.loadingCategories = false; - this.cdr.markForCheck(); - } catch (e) { - this.loadingCategories = false; - this.cdr.markForCheck(); - } - } - - private async setDatesForKeys() { - if(this.service.getDuration()?.key) { - const key = this.service.getDuration()?.key; - const today = moment(); - - switch (key) { - case YESTERDAY: - const yesterday = moment().subtract(1, "days"); - this.service.setDateRange([yesterday.toString(), yesterday.toString()]); - break; - case LAST_WEEK: - const lastWeekStart = moment().subtract(1, "weeks"); - this.service.setDateRange([lastWeekStart.toString(), today.toString()]); - break; - case LAST_MONTH: - const lastMonthStart = moment().subtract(1, "months"); - this.service.setDateRange([lastMonthStart.toString(), today.toString()]); - break; - case LAST_QUARTER: - const lastQuaterStart = moment().subtract(3, "months"); - this.service.setDateRange([lastQuaterStart.toString(), today.toString()]); - break; - case PREV_WEEK: - const prevWeekStart = moment().subtract(1, "weeks").startOf("week"); - const prevWeekEnd = moment().subtract(1, "weeks").endOf("week"); - this.service.setDateRange([prevWeekStart.toString(), prevWeekEnd.toString()]); - break; - case PREV_MONTH: - const prevMonthStart = moment().subtract(1, "month").startOf("month"); - const prevMonthEnd = moment().subtract(1, "month").endOf("month"); - this.service.setDateRange([prevMonthStart.toString(), prevMonthEnd.toString()]); - break; - } - } - } - - async get() { - try { - this.loading = true; - const teams = this.getSelectedTeamIds(); - const categories = this.getSelectedCategories(); - const projects = this.getSelectedProjectIds(); - - if (this.isDurationLabelSelected) { - await this.setDatesForKeys(); - } - - const body = { - type: IToggleOptions[this.type], - teams, - categories, - selectNoCategory: this.selectNoCategory, - projects, - duration: this.service.getDuration()?.key, - date_range: this.service.getDateRange() - } - const res = await this.api.getProjectEstimatedVsActual(body, this.service.getIncludeToggle()); - if (res.done) { - if (this.barChartData) { - this.barChartData.datasets[0].data = []; - this.barChartData.datasets[1].data = []; - this.barChartData.labels = []; - } - this.loading = false; - this.projects = res.body; - - this.createChart(); - this.cdr.markForCheck(); - } - this.loading = false; - this.cdr.markForCheck(); - } catch (e) { - this.loading = false; - log_error(e); - this.cdr.markForCheck(); - } - } - - ngOnChanges(changes: SimpleChanges) { - setTimeout(() => { - this.barChart?.update(""); - this.cdr.markForCheck(); - }, 1000) - } - - createChart() { - this.visible = true; - for (const project of this.projects) { - this.barChartData.labels?.push(project.name); - this.barChartData.datasets[0].data.push(project.estimated_value || 0); - this.barChartData.datasets[1].data.push(project.value || 0); - } - if (this.projects.length) { - const containerWidth = window.innerWidth - 300; - const virtualWidth = this.projects.length * 120; - if (virtualWidth > containerWidth) { - this.chartWidth = virtualWidth; - } else { - this.chartWidth = window.innerWidth - 250; - } - } - this.barChart?.update("none"); - this.createExportChart(); - this.cdr.markForCheck(); - } - - private getSelectedTeamIds(): string[] { - const filter = this.teamsDropdown.filter(t => t.selected); - const ids = filter.map(t => t.id) as string[]; - return ids || []; - } - - private getSelectedCategories(): string[] { - const filter = this.categoriesDropdown.filter(c => c.selected); - const ids = filter.map(c => c.id) as string[]; - return ids || []; - } - - private getSelectedProjectIds(): string[] { - const filter = this.projectsDropdown.filter(t => t.selected); - const ids = filter.map(t => t.id) as string[]; - return ids || []; - } - - checkTeam() { - this.selectAllTeams = false - void this.refreshCategories(); - } - - checkCategory() { - this.selectAllCategories = false - void this.refreshProjects(); - } - - checkProject() { - this.selectAllProjects = false - this.onFilterChange(); - } - - async refreshCategories() { - await this.getCategories(this.getSelectedTeamIds()); - void this.refreshProjects(); - } - - async refreshProjects() { - await this.getProjects(this.getSelectedTeamIds(), this.getSelectedCategories()); - void this.get(); - } - - async getProjects(teams: string[], categories: string[]) { - try { - this.loadingProjects = true; - const res = await this.api.getAllocationProjects(teams, categories, this.selectNoCategory); - if (res.done) { - this.projectsDropdown = res.body; - } - this.loadingProjects = false; - this.cdr.markForCheck(); - } catch (e) { - this.loadingProjects = false; - this.cdr.markForCheck(); - } - this.cdr.markForCheck(); - } - - detectChanges() { - this.cdr.markForCheck(); - } - - createExportChart() { - this.exportChart?.destroy(); - const chartElement = this.exportChartCanvas.nativeElement; - this.exportChart = new Chart(chartElement, { - type: 'bar', - data: this.barChartData, - options: this.barChartOptions, - }); - this.exportChart?.update(); - this.cdr.markForCheck(); - } - - async export() { - const chartElement = this.exportChartCanvas.nativeElement; - const image = chartElement.toDataURL("image/png").replace("image/png", "image/octet-stream") - const a = document.createElement('a'); - const filename = 'Estimated vs Actual.png'; - a.setAttribute("href", image); - a.setAttribute('download', filename); - a.click(); - } - - onFilterChange() { - void this.get(); - } - - handleIndexChange(e: number): void { - this.type = e; - this.get(); - } - - selectAllTeamsChecked(checked: boolean) { - if (checked) { - for (const item of this.teamsDropdown) { - item.selected = true; - } - this.refreshCategories(); - this.cdr.markForCheck(); - } else { - for (const item of this.teamsDropdown) { - item.selected = false; - } - this.refreshCategories(); - this.cdr.markForCheck(); - } - } - - selectAllCategoriesChecked(checked: boolean) { - if (checked) { - - this.selectNoCategory = true; - for (const item of this.categoriesDropdown) { - item.selected = true; - } - this.refreshProjects(); - this.cdr.markForCheck(); - } else { - this.selectNoCategory = false; - for (const item of this.categoriesDropdown) { - item.selected = false; - } - this.refreshProjects(); - this.cdr.markForCheck(); - } - } - - selectAllProjectsChecked(checked: boolean) { - if (checked) { - for (const item of this.projectsDropdown) { - item.selected = true; - } - this.onFilterChange(); - this.cdr.markForCheck(); - } else { - for (const item of this.projectsDropdown) { - item.selected = false; - } - this.onFilterChange(); - this.cdr.markForCheck(); - } - } - -} - diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/rpt-time-members-routing.module.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/rpt-time-members-routing.module.ts deleted file mode 100644 index f46781e0..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/rpt-time-members-routing.module.ts +++ /dev/null @@ -1,15 +0,0 @@ -import {RouterModule, Routes} from "@angular/router"; -import {NgModule} from "@angular/core"; -import {TimeMembersComponent} from "./time-members/time-members.component"; - -const routes: Routes = [ - {path: "", component: TimeMembersComponent} -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) - -export class RptTimeMembersRoutingModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/rpt-time-members.module.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/rpt-time-members.module.ts deleted file mode 100644 index 9960dab9..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/rpt-time-members.module.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { TimeMembersComponent } from './time-members/time-members.component'; -import {RptTimeMembersRoutingModule} from "./rpt-time-members-routing.module"; -import {NgChartsModule} from "ng2-charts"; -import {RptHeaderComponent} from "../../../components/rpt-header/rpt-header.component"; -import {NzLayoutModule} from "ng-zorro-antd/layout"; -import {NzCardModule} from "ng-zorro-antd/card"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {FormsModule} from "@angular/forms"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzCheckboxModule} from "ng-zorro-antd/checkbox"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzEmptyModule} from "ng-zorro-antd/empty"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; - -@NgModule({ - declarations: [ - TimeMembersComponent - ], - imports: [ - CommonModule, - RptTimeMembersRoutingModule, - NgChartsModule, - RptHeaderComponent, - NzLayoutModule, - NzCardModule, - NzSpaceModule, - NzInputModule, - FormsModule, - NzDropDownModule, - NzButtonModule, - NzCheckboxModule, - SearchByNamePipe, - NzIconModule, - NzEmptyModule, - NzBadgeModule, - ] -}) -export class RptTimeMembersModule { } diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/time-members/time-members.component.html b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/time-members/time-members.component.html deleted file mode 100644 index 17ba1d9f..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/time-members/time-members.component.html +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - - - - - -
-
-
- - -
-
-
- - - -
-
- - -
    -
  • - -
  • -
  • Select all -
  • -
  • -
  • - {{item.name}} -
  • -
-
- - -
    -
  • - -
  • -
  • Select all -
  • -
  • -
  • -
    -
    - {{item.name}} -
    -
    -
  • -
-
- - -
    -
  • - -
  • -
  • Select all -
  • -
  • -
  • - No Category -
  • -
  • -
    -
    - - - -
    -
    -
  • -
-
- - - diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/time-members/time-members.component.scss b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/time-members/time-members.component.scss deleted file mode 100644 index 02d0c603..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/time-members/time-members.component.scss +++ /dev/null @@ -1,6 +0,0 @@ -.chartWrapper { - max-width: calc(100vw - 220px); - min-width: calc(100vw - 220px); - height: calc(100vh - 300px); - overflow: auto; -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/time-members/time-members.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/time-members/time-members.component.spec.ts deleted file mode 100644 index df0dd31a..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/time-members/time-members.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TimeMembersComponent } from './time-members.component'; - -describe('TimeMembersComponent', () => { - let component: TimeMembersComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TimeMembersComponent] - }); - fixture = TestBed.createComponent(TimeMembersComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/time-members/time-members.component.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/time-members/time-members.component.ts deleted file mode 100644 index a20d379b..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-members/time-members/time-members.component.ts +++ /dev/null @@ -1,417 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - OnChanges, - OnInit, - SimpleChanges, - ViewChild -} from '@angular/core'; -import {BaseChartDirective} from "ng2-charts"; -import {Chart, ChartConfiguration} from "chart.js"; -import {log_error} from "@shared/utils"; -import {ISelectableProject} from "@interfaces/selectable-project"; -import {ISelectableTeam} from "@interfaces/selectable-team"; -import {ReportingApiService} from "../../../../reporting-api.service"; -import {ReportingService} from "../../../../reporting.service"; -import {merge} from "rxjs"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {IRPTTimeMember} from "../../../../interfaces"; -import {IProjectCategoryViewModel} from "@interfaces/project-category"; -import moment from "moment"; -import {LAST_MONTH, LAST_QUARTER, LAST_WEEK, PREV_MONTH, PREV_WEEK, YESTERDAY} from "@shared/constants"; - -@Component({ - selector: 'worklenz-time-members', - templateUrl: './time-members.component.html', - styleUrls: ['./time-members.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TimeMembersComponent implements OnInit, OnChanges { - @ViewChild(BaseChartDirective) barChart: BaseChartDirective | undefined; - @ViewChild('exportChartCanvas') exportChartCanvas!: ElementRef; - - visible = false; - loading = false; - loadingTeams = false; - loadingCategories = false; - loadingProjects = false; - selectAllTeams = true; - selectAllProjects = true; - selectAllCategories = true; - selectNoCategory = true; - isDurationLabelSelected = true; - - chartHeight = 600; - chartWidth = 1080; - - teamSearchText: string | null = null; - categorySearchText: string | null = null; - projectSearchText: string | null = null; - - teams: ISelectableTeam[] = []; - projects: ISelectableProject[] = []; - categoriesDropdown: IProjectCategoryViewModel[] = []; - - barChartPlugins = []; - memberColors: string[] = []; - members: IRPTTimeMember[] = []; - - exportChart: Chart | null = null; - - barChartData: ChartConfiguration<'bar'>['data'] = { - labels: [], - datasets: [{data: [], label: 'Logged Time (hours) ', backgroundColor: this.memberColors, barThickness: 40}] - }; - - barChartOptions: ChartConfiguration<'bar'>['options'] = { - // responsive: true, - maintainAspectRatio: false, - plugins: { - datalabels: { - color: 'white', - font: { - weight: 'bold' - }, - anchor: 'start', - align: 'right', - offset: 20, - borderColor: "#000", - textStrokeColor: 'black', - textStrokeWidth: 4, - } - }, - indexAxis: "y", - scales: { - y: { - title: { - display: true, - text: 'Member', - align: "end", - font: { - family: 'Helvetica' - } - }, - ticks: { - callback: function (value) { - return this.getLabelForValue(parseFloat(value)).substr(0, 30); - } - } - }, - x: { - title: { - display: true, - text: 'Logged Time(hours)', - align: "end", - font: { - family: 'Helvetica' - } - } - } - } - }; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly service: ReportingService, - private readonly api: ReportingApiService, - ) { - merge( - this.service.onDurationChange, - this.service.onDateRangeChange, - this.service.onIncludeToggleChange - ) - .pipe(takeUntilDestroyed()) - .subscribe(() => { - if (this.barChartData) - this.barChartData.datasets[0].data = []; - this.barChartData.labels = []; - void this.get(); - }); - } - - ngOnInit() { - this.chartWidth = window.innerWidth - 250; - void this.getTeams(); - this.cdr.markForCheck(); - } - - private getSelectedTeamIds(): string[] { - const filter = this.teams.filter(t => t.selected); - const ids = filter.map(t => t.id) as string[]; - return ids || []; - } - - private getSelectedCategories(): string[] { - const filter = this.categoriesDropdown.filter(c => c.selected); - const ids = filter.map(c => c.id) as string[]; - return ids || []; - } - - private getSelectedProjectIds(): string[] { - const filter = this.projects.filter(t => t.selected); - const ids = filter.map(t => t.id) as string[]; - return ids || []; - } - - private async setDatesForKeys() { - if(this.service.getDuration()?.key) { - const key = this.service.getDuration()?.key; - const today = moment(); - - switch (key) { - case YESTERDAY: - const yesterday = moment().subtract(1, "days"); - this.service.setDateRange([yesterday.toString(), yesterday.toString()]); - break; - case LAST_WEEK: - const lastWeekStart = moment().subtract(1, "weeks"); - this.service.setDateRange([lastWeekStart.toString(), today.toString()]); - break; - case LAST_MONTH: - const lastMonthStart = moment().subtract(1, "months"); - this.service.setDateRange([lastMonthStart.toString(), today.toString()]); - break; - case LAST_QUARTER: - const lastQuaterStart = moment().subtract(3, "months"); - this.service.setDateRange([lastQuaterStart.toString(), today.toString()]); - break; - case PREV_WEEK: - const prevWeekStart = moment().subtract(1, "weeks").startOf("week"); - const prevWeekEnd = moment().subtract(1, "weeks").endOf("week"); - this.service.setDateRange([prevWeekStart.toString(), prevWeekEnd.toString()]); - break; - case PREV_MONTH: - const prevMonthStart = moment().subtract(1, "month").startOf("month"); - const prevMonthEnd = moment().subtract(1, "month").endOf("month"); - this.service.setDateRange([prevMonthStart.toString(), prevMonthEnd.toString()]); - break; - } - } - } - - async get() { - try { - this.loading = true; - - const teams = this.getSelectedTeamIds(); - const projects = this.getSelectedProjectIds(); - - if (this.isDurationLabelSelected) { - await this.setDatesForKeys(); - } - - const body = { - teams, - projects, - duration: this.service.getDuration()?.key, - date_range: this.service.getDateRange() - } - const res = await this.api.getMemberTimeSheets(body, this.service.getIncludeToggle()); - if (res.done) { - if (this.barChartData) - this.barChartData.datasets[0].data = []; - this.barChartData.labels = []; - - this.members = res.body; - if (res.body.length) { - const containerHeight = window.innerHeight - 300; - const virtualHeight = res.body.length * 60; - if (virtualHeight > containerHeight) { - this.chartHeight = virtualHeight; - } else { - this.chartHeight = window.innerHeight - 300; - } - } - this.createChart(); - this.loading = false; - this.cdr.markForCheck(); - } - this.loading = false; - this.cdr.markForCheck(); - } catch (e) { - this.loading = false; - log_error(e); - this.cdr.markForCheck(); - } - } - - private async getTeams() { - try { - this.loadingTeams = true; - const res = await this.api.getOverviewTeams(); - if (res.done) { - this.loadingTeams = false; - const teams = []; - for (const team of res.body) { - teams.push({selected: true, name: team.name, id: team.id}) - } - this.teams = teams; - await this.refreshCategories(); - this.cdr.markForCheck(); - } - } catch (e) { - log_error(e); - this.loadingTeams = false; - this.cdr.markForCheck(); - } - } - - async refreshCategories() { - await this.getCategories(this.getSelectedTeamIds()); - void this.refreshProjects(); - } - - async refreshProjects() { - await this.getProjects(this.getSelectedTeamIds(), this.getSelectedCategories()); - void this.get(); - } - - async getCategories(teams: string[]) { - try { - this.loadingCategories = true; - const res = await this.api.getCategories(teams); - if (res.done) { - this.categoriesDropdown = res.body; - } - this.loadingCategories = false; - this.cdr.markForCheck(); - } catch (e) { - this.loadingCategories = false; - this.cdr.markForCheck(); - } - } - - async getProjects(teams: string[], categories: string[]) { - try { - this.loadingProjects = true; - const res = await this.api.getAllocationProjects(teams, categories, this.selectNoCategory); - if (res.done) { - this.projects = res.body; - } - this.loadingProjects = false; - this.cdr.markForCheck(); - } catch (e) { - this.loadingProjects = false; - this.cdr.markForCheck(); - } - this.cdr.markForCheck(); - } - - ngOnChanges(changes: SimpleChanges) { - setTimeout(() => { - this.barChart?.update(); - this.cdr.markForCheck(); - }, 1000) - } - - createChart() { - this.visible = true; - for (const member of this.members) { - this.barChartData.labels?.push(member.name); - this.barChartData.datasets[0].data.push(member.value || 0); - this.memberColors.push(member.color_code); - } - this.barChart?.update(); - this.createExportChart(); - this.cdr.markForCheck(); - } - - onTeamsFilterChange() { - void this.get(); - } - - detectChanges() { - this.cdr.markForCheck(); - } - - createExportChart() { - this.exportChart?.destroy(); - const chartElement = this.exportChartCanvas.nativeElement; - this.exportChart = new Chart(chartElement, { - type: 'bar', - data: this.barChartData, - options: this.barChartOptions, - }); - this.exportChart?.update(); - this.cdr.markForCheck(); - } - - async export() { - const chartElement = this.exportChartCanvas.nativeElement; - const image = chartElement.toDataURL("image/png").replace("image/png", "image/octet-stream") - const a = document.createElement('a'); - const filename = 'Members time sheet.png'; - a.setAttribute("href", image); - a.setAttribute('download', filename); - a.click(); - } - - checkTeam() { - this.selectAllTeams = false - this.refreshCategories(); - } - - checkCategory() { - this.selectAllCategories = false - void this.refreshProjects(); - } - - checkProject() { - this.selectAllProjects = false - this.onTeamsFilterChange(); - } - - selectAllTeamsChecked(checked: boolean) { - if (checked) { - for (const item of this.teams) { - item.selected = true; - } - this.refreshProjects(); - this.cdr.markForCheck(); - } else { - for (const item of this.teams) { - item.selected = false; - } - this.refreshProjects(); - this.cdr.markForCheck(); - } - } - - selectAllCategoriesChecked(checked: boolean) { - if (checked) { - - this.selectNoCategory = true; - for (const item of this.categoriesDropdown) { - item.selected = true; - } - void this.refreshProjects(); - this.cdr.markForCheck(); - } else { - this.selectNoCategory = false; - for (const item of this.categoriesDropdown) { - item.selected = false; - } - void this.refreshProjects(); - this.cdr.markForCheck(); - } - } - - selectAllProjectsChecked(checked: boolean) { - if (checked) { - for (const item of this.projects) { - item.selected = true; - } - this.onTeamsFilterChange(); - this.cdr.markForCheck(); - } else { - for (const item of this.projects) { - item.selected = false; - } - this.onTeamsFilterChange(); - this.cdr.markForCheck(); - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/rpt-time-projects-routing.module.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/rpt-time-projects-routing.module.ts deleted file mode 100644 index a4d7b211..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/rpt-time-projects-routing.module.ts +++ /dev/null @@ -1,15 +0,0 @@ -import {RouterModule, Routes} from "@angular/router"; -import {NgModule} from "@angular/core"; -import {TimeProjectsComponent} from "./time-projects/time-projects.component"; - -const routes: Routes = [ - {path: "", component: TimeProjectsComponent} -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) - -export class RptTimeProjectsRoutingModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/rpt-time-projects.module.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/rpt-time-projects.module.ts deleted file mode 100644 index c7373697..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/rpt-time-projects.module.ts +++ /dev/null @@ -1,52 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {RptTimeProjectsRoutingModule} from "./rpt-time-projects-routing.module"; -import {TimeProjectsComponent} from './time-projects/time-projects.component'; -import {FormsModule} from "@angular/forms"; -import {NgChartsModule} from "ng2-charts"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzCardModule} from "ng-zorro-antd/card"; -import {NzCheckboxModule} from "ng-zorro-antd/checkbox"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NzLayoutModule} from "ng-zorro-antd/layout"; -import {NzMenuModule} from "ng-zorro-antd/menu"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzWaveModule} from "ng-zorro-antd/core/wave"; -import {RptHeaderComponent} from "../../../components/rpt-header/rpt-header.component"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzEmptyModule} from "ng-zorro-antd/empty"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import {ReportingModule} from "../../../reporting.module"; - -@NgModule({ - declarations: [ - TimeProjectsComponent - ], - imports: [ - CommonModule, - RptTimeProjectsRoutingModule, - FormsModule, - NgChartsModule, - NzButtonModule, - NzCardModule, - NzCheckboxModule, - NzDropDownModule, - NzInputModule, - NzLayoutModule, - NzMenuModule, - NzSpaceModule, - NzWaveModule, - RptHeaderComponent, - SearchByNamePipe, - NzIconModule, - NzTypographyModule, - NzEmptyModule, - NzBadgeModule, - ReportingModule, - ] -}) -export class RptTimeProjectsModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/time-projects/time-projects.component.html b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/time-projects/time-projects.component.html deleted file mode 100644 index 547ad2b7..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/time-projects/time-projects.component.html +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - -
-
-
- - -
-
-
- - - -
-
- - - - -
    -
  • - -
  • -
  • Select all -
  • -
  • -
  • - {{item.name}} -
  • -
-
- - -
    -
  • - -
  • -
  • Select all -
  • -
  • -
  • - No Category -
  • -
  • -
    -
    - - - -
    -
    -
  • -
-
- - -
    -
  • - -
  • -
  • Select all -
  • -
  • -
  • -
    -
    - {{item.name}} -
    -
    -
  • -
-
- diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/time-projects/time-projects.component.scss b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/time-projects/time-projects.component.scss deleted file mode 100644 index 02d0c603..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/time-projects/time-projects.component.scss +++ /dev/null @@ -1,6 +0,0 @@ -.chartWrapper { - max-width: calc(100vw - 220px); - min-width: calc(100vw - 220px); - height: calc(100vh - 300px); - overflow: auto; -} diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/time-projects/time-projects.component.spec.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/time-projects/time-projects.component.spec.ts deleted file mode 100644 index 9194cc55..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/time-projects/time-projects.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TimeProjectsComponent } from './time-projects.component'; - -describe('TimeProjectsComponent', () => { - let component: TimeProjectsComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TimeProjectsComponent] - }); - fixture = TestBed.createComponent(TimeProjectsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/time-projects/time-projects.component.ts b/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/time-projects/time-projects.component.ts deleted file mode 100644 index 6900fa41..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/modules/rpt-time-reports/rpt-time-projects/time-projects/time-projects.component.ts +++ /dev/null @@ -1,420 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - NgZone, - OnChanges, - OnInit, - SimpleChanges, - ViewChild -} from '@angular/core'; -import {BaseChartDirective} from "ng2-charts"; -import {IProjectCategoryViewModel} from "@interfaces/project-category"; -import {ActiveElement, Chart, ChartConfiguration} from "chart.js"; -import {log_error} from "@shared/utils"; -import {ReportingApiService} from "../../../../reporting-api.service"; -import {merge} from "rxjs"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {ReportingService} from "../../../../reporting.service"; -import {IRPTTimeProject} from "../../../../interfaces"; -import {ISelectableProject} from "@interfaces/selectable-project"; -import moment from "moment/moment"; -import {LAST_MONTH, LAST_QUARTER, LAST_WEEK, PREV_MONTH, PREV_WEEK, YESTERDAY} from "@shared/constants"; - -@Component({ - selector: 'worklenz-time-projects', - templateUrl: './time-projects.component.html', - styleUrls: ['./time-projects.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TimeProjectsComponent implements OnInit, OnChanges { - @ViewChild(BaseChartDirective) barChart: BaseChartDirective | undefined; - @ViewChild('exportChartCanvas') exportChartCanvas!: ElementRef; - - visible = false; - loading = false; - loadingTeams = false; - loadingCategories = false; - loadingProjects = false; - selectAllTeams = true; - selectAllCategories = true; - selectAllProjects = true; - selectNoCategory = true; - isDurationLabelSelected = true; - - chartHeight = 600; - chartWidth = 1080; - - teamSearchText: string | null = null; - projectSearchText: string | null = null; - categorySearchText: string | null = null; - selectedProject: IRPTTimeProject | null = null; - - showLogsModal = false; - - teamsDropdown: IProjectCategoryViewModel[] = []; - categoriesDropdown: IProjectCategoryViewModel[] = []; - projectsDropdown: ISelectableProject[] = []; - - projectColors: string[] = []; - - projects: IRPTTimeProject[] = []; - - barChartData: ChartConfiguration<'bar'>['data'] = { - labels: [], - datasets: [{data: [], label: 'Logged Time (hours) ', backgroundColor: this.projectColors, barThickness: 40}] - }; - - barChartOptions: ChartConfiguration<'bar'>['options'] = { - maintainAspectRatio: false, - plugins: { - datalabels: { - color: 'white', - anchor: 'start', - align: 'right', - offset: 20, - textStrokeColor: 'black', - textStrokeWidth: 4, - } - }, - backgroundColor: "black", - indexAxis: "y", - scales: { - y: { - title: { - display: true, - text: 'Projects', - align: "end", - font: { - family: 'Helvetica' - } - }, - ticks: { - callback: function (value) { - return this.getLabelForValue(parseFloat(value)).substr(0, 30); - } - } - }, - x: { - title: { - display: true, - text: 'Logged Time(hours)', - align: "end", - font: { - family: 'Helvetica' - } - }, - } - }, - onClick: (event: any, elements: ActiveElement[], chart: Chart) => { - this.ngZone.run(() => { - const bars = chart.getElementsAtEventForMode( - event, - 'nearest', - {intersect: true}, - true - ); - - if (bars.length === 0) return; - const bar = bars[0]; - const index = bar.index; - this.openTimeLogs(index); - }); - } - }; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly service: ReportingService, - private readonly api: ReportingApiService, - private readonly ngZone: NgZone, - ) { - merge( - this.service.onDurationChange, - this.service.onDateRangeChange, - this.service.onIncludeToggleChange - ) - .pipe(takeUntilDestroyed()) - .subscribe(() => { - void this.get(); - }); - } - - ngOnInit() { - this.chartWidth = window.innerWidth - 250; - void this.getTeams(); - this.cdr.markForCheck(); - } - - async refreshCategories() { - await this.getCategories(this.getSelectedTeamIds()); - void this.refreshProjects(); - } - - async refreshProjects() { - await this.getAllocationProjects(this.getSelectedTeamIds(), this.getSelectedCategories()); - void this.get(); - } - - private getSelectedTeamIds(): string[] { - const filter = this.teamsDropdown.filter(t => t.selected); - const ids = filter.map(t => t.id) as string[]; - return ids || []; - } - - private getSelectedCategories(): string[] { - const filter = this.categoriesDropdown.filter(c => c.selected); - const ids = filter.map(c => c.id) as string[]; - return ids || []; - } - - private getSelectedProjectIds(): string[] { - const filter = this.projectsDropdown.filter(t => t.selected); - const ids = filter.map(t => t.id) as string[]; - return ids || []; - } - - async getCategories(teams: string[]) { - try { - this.loadingCategories = true; - const res = await this.api.getCategories(teams); - if (res.done) { - this.categoriesDropdown = res.body; - } - this.loadingCategories = false; - this.cdr.markForCheck(); - } catch (e) { - this.loadingCategories = false; - this.cdr.markForCheck(); - } - } - - async getAllocationProjects(teams: string[], categories: string[]) { - try { - this.loadingProjects = true; - const res = await this.api.getAllocationProjects(teams, categories, this.selectNoCategory); - if (res.done) { - this.projectsDropdown = res.body; - } - this.loadingProjects = false; - this.cdr.markForCheck(); - } catch (e) { - this.loadingProjects = false; - this.cdr.markForCheck(); - } - } - - private async getTeams() { - try { - this.loadingTeams = true; - const res = await this.api.getOverviewTeams(); - if (res.done) { - this.loadingTeams = false; - const teams = []; - for (const team of res.body) { - teams.push({selected: true, name: team.name, id: team.id}) - } - this.teamsDropdown = teams; - void this.refreshCategories(); - this.cdr.markForCheck(); - } - } catch (e) { - log_error(e); - this.loadingTeams = false; - this.cdr.markForCheck(); - } - } - - private async setDatesForKeys() { - if(this.service.getDuration()?.key) { - const key = this.service.getDuration()?.key; - const today = moment(); - - switch (key) { - case YESTERDAY: - const yesterday = moment().subtract(1, "days"); - this.service.setDateRange([yesterday.toString(), yesterday.toString()]); - break; - case LAST_WEEK: - const lastWeekStart = moment().subtract(1, "weeks"); - this.service.setDateRange([lastWeekStart.toString(), today.toString()]); - break; - case LAST_MONTH: - const lastMonthStart = moment().subtract(1, "months"); - this.service.setDateRange([lastMonthStart.toString(), today.toString()]); - break; - case LAST_QUARTER: - const lastQuaterStart = moment().subtract(3, "months"); - this.service.setDateRange([lastQuaterStart.toString(), today.toString()]); - break; - case PREV_WEEK: - const prevWeekStart = moment().subtract(1, "weeks").startOf("week"); - const prevWeekEnd = moment().subtract(1, "weeks").endOf("week"); - this.service.setDateRange([prevWeekStart.toString(), prevWeekEnd.toString()]); - break; - case PREV_MONTH: - const prevMonthStart = moment().subtract(1, "month").startOf("month"); - const prevMonthEnd = moment().subtract(1, "month").endOf("month"); - this.service.setDateRange([prevMonthStart.toString(), prevMonthEnd.toString()]); - break; - } - } - } - - async get() { - try { - this.loading = true; - - const teams = this.getSelectedTeamIds(); - const categories = this.getSelectedCategories(); - const projects = this.getSelectedProjectIds(); - - if (this.isDurationLabelSelected) { - await this.setDatesForKeys(); - } - - const body = { - teams, - categories, - projects, - duration: this.service.getDuration()?.key, - date_range: this.service.getDateRange() - } - const res = await this.api.getProjectTimeSheets(body, this.service.getIncludeToggle()); - if (res.done) { - if (this.barChartData) { - this.barChartData.datasets[0].data = []; - this.barChartData.labels = []; - } - this.projects = res.body; - if (res.body.length) { - const containerHeight = window.innerHeight - 300; - const virtualHeight = res.body.length * 60; - if (virtualHeight > containerHeight) { - this.chartHeight = virtualHeight; - } else { - this.chartHeight = window.innerHeight - 300; - } - } - this.createChart(); - this.loading = false; - } - this.loading = false; - this.cdr.markForCheck(); - } catch (e) { - this.loading = false; - log_error(e); - this.cdr.markForCheck(); - } - } - - ngOnChanges(changes: SimpleChanges) { - setTimeout(() => { - this.barChart?.update(); - this.cdr.markForCheck(); - }, 1000) - } - - createChart() { - this.visible = true; - for (const project of this.projects) { - this.projectColors.push(project.color_code); - this.barChartData.labels?.push(project.name); - this.barChartData.datasets[0].data.push(project.value || 0); - } - this.barChart?.update(); - this.cdr.markForCheck(); - } - - onFilterChange() { - void this.get(); - } - - detectChanges() { - this.cdr.markForCheck(); - } - - async export() { - const chartElement = this.exportChartCanvas.nativeElement; - const image = chartElement.toDataURL("image/png").replace("image/png", "image/octet-stream") - const a = document.createElement('a'); - const filename = 'Projects time sheet.png'; - a.setAttribute("href", image); - a.setAttribute('download', filename); - a.click(); - } - - checkTeam() { - this.selectAllTeams = false - void this.refreshCategories(); - } - - checkCategory() { - this.selectAllCategories = false - void this.refreshProjects(); - } - - checkProject() { - this.selectAllProjects = false - this.onFilterChange(); - } - - selectAllTeamsChecked(checked: boolean) { - if (checked) { - for (const item of this.teamsDropdown) { - item.selected = true; - } - void this.refreshCategories(); - this.cdr.markForCheck(); - } else { - for (const item of this.teamsDropdown) { - item.selected = false; - } - void this.refreshCategories(); - this.cdr.markForCheck(); - } - } - - selectAllCategoriesChecked(checked: boolean) { - if (checked) { - this.selectNoCategory = true; - for (const item of this.categoriesDropdown) { - item.selected = true; - } - void this.refreshProjects(); - this.cdr.markForCheck(); - } else { - this.selectNoCategory = false; - for (const item of this.categoriesDropdown) { - item.selected = false; - } - void this.refreshProjects(); - this.cdr.markForCheck(); - } - } - - selectAllProjectsChecked(checked: boolean) { - if (checked) { - for (const item of this.projectsDropdown) { - item.selected = true; - } - this.onFilterChange(); - this.cdr.markForCheck(); - } else { - for (const item of this.projectsDropdown) { - item.selected = false; - } - this.onFilterChange(); - this.cdr.markForCheck(); - } - } - - openTimeLogs(index: number) { - const project = this.projects[index]; - this.selectedProject = project; - this.showLogsModal = true; - this.cdr.detectChanges(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/pipes/with-count.pipe.spec.ts b/worklenz-frontend/src/app/administrator/reporting/pipes/with-count.pipe.spec.ts deleted file mode 100644 index 0e7f2926..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/pipes/with-count.pipe.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {WithCountPipe} from './with-count.pipe'; - -describe('WithCountPipe', () => { - it('create an instance', () => { - const pipe = new WithCountPipe(); - expect(pipe).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/reporting/pipes/with-count.pipe.ts b/worklenz-frontend/src/app/administrator/reporting/pipes/with-count.pipe.ts deleted file mode 100644 index 19f27d79..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/pipes/with-count.pipe.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; - -@Pipe({ - name: 'withCount' -}) -export class WithCountPipe implements PipeTransform { - transform(label: string, count?: number): string { - return `${label} (${count || 0})`; - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/reporting-api.service.ts b/worklenz-frontend/src/app/administrator/reporting/reporting-api.service.ts deleted file mode 100644 index 75a5de64..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/reporting-api.service.ts +++ /dev/null @@ -1,179 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import { - IMemberProjectsResonse, - IMemberTaskStatGroup, IMemberTaskStatGroupResonse, - IProjectLogsBreakdown, - IReportingInfo, - IRPTMember, - IRPTMemberProject, IRPTMemberResponse, - IRPTMembersViewModel, - IRPTOverviewMemberInfo, - IRPTOverviewProjectInfo, - IRPTOverviewProjectMember, - IRPTOverviewStatistics, - IRPTOverviewTeamInfo, - IRPTProject, - IRPTProjectsViewModel, - IRPTReportingMemberTask, - IRPTTeam, - IRPTTimeMember, - IRPTTimeProject, ISingleMemberActivityLogs, ISingleMemberLogs, - ITimeLogBreakdownReq -} from "./interfaces"; -import {ITaskListGroup} from "../modules/task-list-v2/interfaces"; -import {toQueryString} from "@shared/utils"; -import {ISelectableProject} from "@interfaces/selectable-project"; -import {IAllocationViewModel} from "@interfaces/allocation-view-model"; -import {ISelectableCategory} from "@interfaces/selectable-category"; -import {IProject} from "@interfaces/project"; - -@Injectable({ - providedIn: 'root' -}) -export class ReportingApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/reporting`; - - constructor( - private readonly http: HttpClient - ) { - super(); - } - - getInfo(): Promise> { - return this._get(this.http, `${this.root}/info`); - } - - getOverviewStatistics(includeArchived = false): Promise> { - const q = toQueryString({archived: includeArchived}); - return this._get(this.http, `${this.root}/overview/statistics${q}`); - } - - getOverviewTeams(includeArchived = true): Promise> { - const q = toQueryString({archived: includeArchived}); - return this._get(this.http, `${this.root}/overview/teams${q}`); - } - - getOverviewProjects(body: any | null = null): Promise> { - const q = toQueryString(body); - return this._get(this.http, `${this.root}/overview/projects${q}`); - } - - getOverviewProjectsByTeam(teamId: string, teamMemberId?: string): Promise> { - const q = toQueryString({member: teamMemberId || null}); - return this._get(this.http, `${this.root}/overview/projects/${teamId}${q}`); - } - - getOverviewMembersByTeam(teamId: string, archived: boolean): Promise> { - const q = toQueryString({archived}); - return this._get(this.http, `${this.root}/overview/members/${teamId}${q}`); - } - - getTeamInfo(teamId: string, archived = false): Promise> { - const q = toQueryString({archived}); - return this._get(this.http, `${this.root}/overview/team/info/${teamId}${q}`); - } - - getProjectInfo(projectId: string): Promise> { - return this._get(this.http, `${this.root}/overview/project/info/${projectId}`); - } - - getMemberInfo(body: any | null = null): Promise> { - const q = toQueryString(body); - return this._get(this.http, `${this.root}/overview/member/info/${q}`); - } - - getTeamMemberInfo(body: any | null = null): Promise> { - const q = toQueryString(body); - return this._get(this.http, `${this.root}/overview/team-member/info/${q}`); - } - - getProjectMembers(projectId: string): Promise> { - return this._get(this.http, `${this.root}/overview/project/members/${projectId}`); - } - - getTasks(projectId: string, groupBy: string): Promise> { - const q = toQueryString({group: groupBy}) - return this._get(this.http, `${this.root}/overview/project/tasks/${projectId}${q}`); - } - - getTasksByMember(teamMemberId: string, projectId: string | null = null, isMultiple: boolean, teamId: string | null = null, additionalBody: any | null = null ): Promise> { - const q = toQueryString({project: projectId || null, is_multiple: isMultiple, teamId, only_single_member: additionalBody.only_single_member, duration: additionalBody.duration, date_range: additionalBody.date_range, archived: additionalBody.archived}); - return this._get(this.http, `${this.root}/overview/member/tasks/${teamMemberId}${q}`); - } - - getProjects(body: any | null = null): Promise> { - const q = toQueryString(body); - return this._get(this.http, `${this.root}/projects${q}`); - } - - getProjectTimeLogs(body: ITimeLogBreakdownReq): Promise> { - return this._post(this.http, `${this.root}/project-timelogs`, body) - } - - // Allocation APIs - getCategories(selectedTeams: string[]): Promise> { - return this._post(this.http, `${this.root}/allocation/categories`, selectedTeams); - } - - getAllocationProjects(selectedTeams: string[], categories: string[], isNoCategory: boolean): Promise> { - const body = { - selectedTeams: selectedTeams, - selectedCategories: categories, - noCategoryIncluded: isNoCategory - } - return this._post(this.http, `${this.root}/allocation/projects`, body); - } - - getAllocationData(body = {}, archived = false): Promise> { - const q = toQueryString({archived}); - return this._post(this.http, `${this.root}/allocation${q}`, body); - } - - // Members APIs - getMembers(body: any | null = null): Promise> { - const q = toQueryString(body); - return this._get(this.http, `${this.root}/members${q}`); - } - - getMemberProjects(body: any | null = null): Promise> { - const q = toQueryString(body); - return this._get(this.http, `${this.root}/member-projects${q}`); - } - - getProjectTimeSheets(body = {}, archived = false): Promise> { - const q = toQueryString({archived}); - return this._post(this.http, `${this.root}/time-reports/projects${q}`, body); - } - - getProjectEstimatedVsActual(body = {}, archived = false): Promise> { - const q = toQueryString({archived}); - return this._post(this.http, `${this.root}/time-reports/estimated-vs-actual${q}`, body); - } - - getMemberTimeSheets(body = {}, archived = false): Promise> { - const q = toQueryString({archived}); - return this._post(this.http, `${this.root}/time-reports/members${q}`, body); - } - - getSingleMemberActivities(body: any | null = null): Promise> { - return this._post(this.http, `${this.root}/members/single-member-activities`, body); - } - - getSingleMemberTimeLogs(body: any | null = null): Promise> { - return this._post(this.http, `${this.root}/members/single-member-timelogs`, body); - } - - getMemberTasksStats(body: any | null = null): Promise> { - const q = toQueryString(body); - return this._get(this.http, `${this.root}/members/single-member-task-stats${q}`); - } - - getSingleMemberProjects(body: any | null = null): Promise> { - const q = toQueryString(body); - return this._get(this.http, `${this.root}/members/single-member-projects${q}`); - } - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/reporting-routing.module.ts b/worklenz-frontend/src/app/administrator/reporting/reporting-routing.module.ts deleted file mode 100644 index 2afc7bb1..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/reporting-routing.module.ts +++ /dev/null @@ -1,58 +0,0 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {RptLayoutComponent} from "./components/rpt-layout/rpt-layout.component"; -import {RptTimeMembersModule} from "./modules/rpt-time-reports/rpt-time-members/rpt-time-members.module"; -import { - RptTimeEstimationVsActualModule -} from "./modules/rpt-time-reports/rpt-time-estimation-vs-actual/rpt-time-estimation-vs-actual.module"; - -const routes: Routes = [ - { - path: '', - component: RptLayoutComponent, - children: [ - {path: "", redirectTo: "overview", pathMatch: "full"}, - { - path: "overview", - loadChildren: () => import("./modules/rpt-overview/rpt-overview.module").then(m => m.RptOverviewModule) - }, - { - path: "projects", - loadChildren: () => import("./modules/rpt-projects/rpt-projects.module").then(m => m.RptProjectsModule) - }, - { - path: "members", - loadChildren: () => import("./modules/rpt-members/rpt-members.module").then(m => m.RptMembersModule) - }, - { - path: "time-sheet-overview", - loadChildren: () => import("./modules/rpt-allocation/rpt-allocation.module").then(m => m.RptAllocationModule) - }, - { - path: "time-sheet-projects", - loadChildren: () => import("./modules/rpt-time-reports/rpt-time-projects/rpt-time-projects.module").then(m => m.RptTimeProjectsModule) - }, - { - path: "time-sheet-members", - loadChildren: () => import("./modules/rpt-time-reports/rpt-time-members/rpt-time-members.module").then(m => m.RptTimeMembersModule) - }, - { - path: "time-sheet-estimated-vs-actual", - loadChildren: () => import("./modules/rpt-time-reports/rpt-time-estimation-vs-actual/rpt-time-estimation-vs-actual.module").then(m => m.RptTimeEstimationVsActualModule) - } - // {path: "allocation", component: AllocationComponent}, - // {path: "projects", component: ReportingProjectsComponent}, - // {path: "members", component: ReportingMembersComponent}, - // {path: "member/:id", component: MemberInsightsComponent}, - // {path: "project/:id", component: ProjectInsightsComponent} - ] - } -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class ReportingRoutingModule { - -} diff --git a/worklenz-frontend/src/app/administrator/reporting/reporting-service.service.ts b/worklenz-frontend/src/app/administrator/reporting/reporting-service.service.ts deleted file mode 100644 index 0ec78e24..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/reporting-service.service.ts +++ /dev/null @@ -1,36 +0,0 @@ -import {Injectable} from '@angular/core'; -import {Subject} from "rxjs"; - -@Injectable({ - providedIn: 'root' -}) -export class ReportingService { - - private readonly _teamDrawerCloseSbj$ = new Subject(); - private readonly _projectDrawerCloseSbj$ = new Subject(); - private readonly _memberDrawerCloseSbj$ = new Subject(); - - get onProjectDrawerClose() { - return this._projectDrawerCloseSbj$.asObservable(); - } - - get onMemberDrawerClose() { - return this._memberDrawerCloseSbj$.asObservable(); - } - - get onTeamDrawerClose() { - return this._teamDrawerCloseSbj$.asObservable(); - } - - public emitProjectDrawerClose() { - this._projectDrawerCloseSbj$.next(); - } - - public emitMemberDrawerClose() { - this._memberDrawerCloseSbj$.next(); - } - - public emitTeamDrawerClose() { - this._teamDrawerCloseSbj$.next(); - } -} diff --git a/worklenz-frontend/src/app/administrator/reporting/reporting.module.ts b/worklenz-frontend/src/app/administrator/reporting/reporting.module.ts deleted file mode 100644 index fb2342a7..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/reporting.module.ts +++ /dev/null @@ -1,127 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {FormsModule, ReactiveFormsModule} from '@angular/forms'; -import {NzIconModule} from 'ng-zorro-antd/icon'; -import {NzButtonModule} from 'ng-zorro-antd/button'; -import {NzDropDownModule} from 'ng-zorro-antd/dropdown'; -import {NzCheckboxModule} from 'ng-zorro-antd/checkbox'; -import {NzSpaceModule} from 'ng-zorro-antd/space'; -import {NzPageHeaderModule} from 'ng-zorro-antd/page-header'; -import {RptLayoutComponent} from './components/rpt-layout/rpt-layout.component'; -import {NzLayoutModule} from "ng-zorro-antd/layout"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {RouterLink, RouterLinkActive, RouterOutlet} from "@angular/router"; -import {ReportingRoutingModule} from "./reporting-routing.module"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {WithCountPipe} from './pipes/with-count.pipe'; -import { ProjectHealthComponent } from './components/project-health/project-health.component'; -import {NzSelectModule} from "ng-zorro-antd/select"; -import { ProjectStartEndDatesComponent } from './components/project-start-end-dates/project-start-end-dates.component'; -import {NzDatePickerModule} from "ng-zorro-antd/date-picker"; -import {TaskListV2Module} from "../modules/task-list-v2/task-list-v2.module"; -import { ProjectStatusComponent } from './components/project-status/project-status.component'; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import { ProjectCategoryComponent } from './components/project-category/project-category.component'; -import {NzTagModule} from "ng-zorro-antd/tag"; -import {NaComponent} from "@admin/components/na/na.component"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzSpinModule} from "ng-zorro-antd/spin"; -import { RptMemberProjectsListComponent } from './drawers/common/rpt-member-projects-list/rpt-member-projects-list.component'; -import {BindNaPipe} from "@pipes/bind-na.pipe"; -import {NzTableModule} from "ng-zorro-antd/table"; -import {TasksProgressBarComponent} from "@admin/components/tasks-progress-bar/tasks-progress-bar.component"; -import {NzProgressModule} from "ng-zorro-antd/progress"; -import { EstimatedVsActualChartComponent } from './components/estimated-vs-actual-chart/estimated-vs-actual-chart.component'; -import {NgChartsModule} from "ng2-charts"; -import { ProjectLogsBreakdownComponent } from './components/project-logs-breakdown/project-logs-breakdown.component'; -import { MemberLogsBreakdownComponent } from './components/member-logs-breakdown/member-logs-breakdown.component'; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzTimelineModule} from "ng-zorro-antd/timeline"; -import {FirstCharUpperPipe} from "@pipes/first-char-upper.pipe"; -import {NzAvatarModule} from "ng-zorro-antd/avatar"; -import {NzCardModule} from "ng-zorro-antd/card"; -import {NzDividerModule} from "ng-zorro-antd/divider"; -import { RptSingleMemberStatComponent } from './drawers/rpt-single-member-stat/rpt-single-member-stat.component'; -import {RptDrawerTitleComponent} from "./drawers/common/rpt-drawer-title/rpt-drawer-title.component"; -import {NzCollapseModule} from "ng-zorro-antd/collapse"; -import { RptSingleMemberProjectsDrawerComponent } from './drawers/rpt-single-member-projects-drawer/rpt-single-member-projects-drawer.component'; -import {EllipsisPipe} from "@pipes/ellipsis.pipe"; - -@NgModule({ - declarations: [ - RptLayoutComponent, - WithCountPipe, - ProjectHealthComponent, - ProjectStartEndDatesComponent, - ProjectStatusComponent, - ProjectCategoryComponent, - RptMemberProjectsListComponent, - EstimatedVsActualChartComponent, - ProjectLogsBreakdownComponent, - MemberLogsBreakdownComponent, - RptSingleMemberStatComponent, - RptSingleMemberProjectsDrawerComponent - ], - exports: [ - WithCountPipe, - ProjectHealthComponent, - ProjectStartEndDatesComponent, - ProjectStatusComponent, - ProjectCategoryComponent, - RptMemberProjectsListComponent, - EstimatedVsActualChartComponent, - ProjectLogsBreakdownComponent, - RptSingleMemberStatComponent, - RptSingleMemberProjectsDrawerComponent - ], - imports: [ - CommonModule, - NzSpaceModule, - NzDropDownModule, - NzButtonModule, - NzIconModule, - NzPageHeaderModule, - FormsModule, - NzCheckboxModule, - NzLayoutModule, - NzTypographyModule, - RouterLink, - RouterOutlet, - RouterLinkActive, - ReportingRoutingModule, - NzToolTipModule, - NzSkeletonModule, - NzSelectModule, - NzDatePickerModule, - TaskListV2Module, - SafeStringPipe, - NzBadgeModule, - NzTagModule, - NaComponent, - SearchByNamePipe, - ReactiveFormsModule, - NzInputModule, - NzFormModule, - NzSpinModule, - BindNaPipe, - NzTableModule, - TasksProgressBarComponent, - NzProgressModule, - NgChartsModule, - NzDrawerModule, - NzTimelineModule, - FirstCharUpperPipe, - NzAvatarModule, - NzCardModule, - NzDividerModule, - RptDrawerTitleComponent, - NzCollapseModule, - EllipsisPipe - ] -}) -export class ReportingModule { -} diff --git a/worklenz-frontend/src/app/administrator/reporting/reporting.service.ts b/worklenz-frontend/src/app/administrator/reporting/reporting.service.ts deleted file mode 100644 index cc208047..00000000 --- a/worklenz-frontend/src/app/administrator/reporting/reporting.service.ts +++ /dev/null @@ -1,122 +0,0 @@ -import {Injectable} from '@angular/core'; -import {IRPTDuration, IRPTTeam} from "./interfaces"; -import {Subject} from "rxjs"; - -@Injectable({ - providedIn: 'root' -}) -export class ReportingService { - private _currentOrganization: string | null = null; - private _currentTeam: IRPTTeam | null = null; - - private _duration: IRPTDuration | null = null; - private _dateRange: string[] = []; - - private _drawerDuration: IRPTDuration | null = null; - private _drawerDateRange: string[] = []; - - private _includeArchived = false; - - private readonly _durationChangedSbj$ = new Subject(); - private readonly _dateRangeChangedSbj$ = new Subject(); - private readonly _drawerDurationChangedSbj$ = new Subject(); - private readonly _drawerDateRangeChangedSbj$ = new Subject(); - private readonly _archivedToggleChangedSbj$ = new Subject(); - - get currentOrganization(): string | null { - return this._currentOrganization; - } - - set currentOrganization(value: string | null) { - this._currentOrganization = value; - } - - public setCurrentTeam(team: IRPTTeam | null) { - this._currentTeam = team; - } - - public getCurrentTeam() { - return this._currentTeam; - } - - // Reporting Filters - public setDuration(duration: IRPTDuration | null) { - this._duration = duration; - } - - public setDrawerDuration(duration: IRPTDuration | null) { - this._drawerDuration = duration; - } - - public setDateRange(range: string[]) { - this._dateRange = range; - } - - public setDrawerDateRange(range: string[]) { - this._drawerDateRange = range; - } - - public getDuration() { - return this._duration; - } - - public getDrawerDuration() { - return this._drawerDuration; - } - - public getDateRange() { - return this._dateRange; - } - - public getDrawerDateRange() { - return this._drawerDateRange; - } - - public setIncludeToggle(status: boolean) { - this._includeArchived = status; - } - - public getIncludeToggle() { - return this._includeArchived; - } - - get onDurationChange() { - return this._durationChangedSbj$.asObservable(); - } - - public emitDurationChanged() { - this._durationChangedSbj$.next(); - } - - get onDrawerDurationChange() { - return this._drawerDurationChangedSbj$.asObservable(); - } - - public emitDrawerDurationChanged() { - this._drawerDurationChangedSbj$.next(); - } - - get onDateRangeChange() { - return this._dateRangeChangedSbj$.asObservable(); - } - - public emitDateRangeChanged() { - this._dateRangeChangedSbj$.next(); - } - - get onDrawerDateRangeChange() { - return this._drawerDateRangeChangedSbj$.asObservable(); - } - - public emitDrawerDateRangeChanged() { - this._drawerDateRangeChangedSbj$.next(); - } - - get onIncludeToggleChange() { - return this._archivedToggleChangedSbj$.asObservable(); - } - - public emitIncludeToggleChanged() { - this._archivedToggleChangedSbj$.next(); - } -} diff --git a/worklenz-frontend/src/app/administrator/schedule/project-schedule/project-schedule.component.html b/worklenz-frontend/src/app/administrator/schedule/project-schedule/project-schedule.component.html deleted file mode 100644 index 1d7042b4..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/project-schedule/project-schedule.component.html +++ /dev/null @@ -1,164 +0,0 @@ -
-
- - -
-
-
- -
- The selected team has no scheduled projects. -
-
- -
- -
- -
-
-
-
-
- - - - W{{month.week_index || 0 - 1 }} - {{month.month_name}} - - - - -
-
-
-
-
{{date.date | date: 'dd'}} -
- -
-
- Subtasks -
- {{resource.name || resource.invitee_email}} -
-
-
    -
  • -
  • -
-
-
-
- - - - -
-
- -
-
- {{element.name}} - - {{element.invitee_email}} -
- - (Invitation Pending) - -
-
-
-
-
    -
  • -
  • -
-
-
-
- - - - -
-
- -
-
Unassigned tasks
-
-
-
    -
  • -
  • -
-
-
-
- - -
-
-
-
-
-
-
- - - - - - - - - - -
-
    -
  • - - - {{task.name}} - - -
  • -
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/schedule/project-schedule/project-schedule.component.scss b/worklenz-frontend/src/app/administrator/schedule/project-schedule/project-schedule.component.scss deleted file mode 100644 index 7fc84c70..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/project-schedule/project-schedule.component.scss +++ /dev/null @@ -1,220 +0,0 @@ -$column_count: var(--column_count); -$column_width: var(--column_width); -$top_margin: var(--top-margin); - -body { - margin: 0; -} - -.grid-scroller { - position: relative; - overflow: auto; - height: calc(100vh - $top_margin); -} - -.grid { - display: grid; - grid-template-columns: 250px 0 repeat($column_count, $column_width); - width: calc(250px + var(--column_count) * var(--column_width)); -} - -.grid-entry { - padding: 12px 7px; - background-color: white; - border-left: 1px solid #f1f1f1; - border-bottom: 1px solid #f1f1f1; -} - -.grid-entry.last, -.grid-header.last { - border-right: none; -} - -.grid-header { - background-color: #fafafa; - padding: 2px 2px; - font-weight: 500; - font-size: 11px; - color: #202125; - position: sticky; - top: 0; - z-index: 1; - border: 1px solid #ededed; - text-align: center; - line-height: 13px; -} - -.grid-header.fixed-left { - z-index: 5; -} - -.fixed-left { - position: sticky; - left: 0; - z-index: 2; - border-bottom: 1px solid #f1f1f1; - border-left: 1px solid #f1f1f1; - border-right: 1.4px solid #B5B4B4; -} - -.placeholder { - grid-column-start: 1; - grid-column-end: 95; - border-right: none; -} - -mwlResizable { - box-sizing: border-box; // required for the enableGhostResize option to work -} - -.resize-handle-top, -.resize-handle-bottom { - position: absolute; - height: 5px; - cursor: row-resize; - width: 100%; -} - -.resize-handle-top { - top: 0; -} - -.resize-handle-bottom { - bottom: 0; -} - -.resize-handle-left, -.resize-handle-right { - position: absolute; - cursor: col-resize; - width: 9px; - height: 18px; - background: #ffffff; - border: none; - border-radius: 1px; - top: 0; - bottom: 0; - margin: auto 1px; -} - -.resize-handle-right::after, -.resize-handle-left::after { - content: "||"; - position: absolute; - display: inline-block; - top: 0; - bottom: 0; - left: 0; - right: 0; - color: #929292; - line-height: 19px; - font-weight: 400; - margin: auto; - width: max-content; -} - -.gantt__row-bars { - li { - user-select: none; - - & > div { - background-color: transparent !important; - } - - .resize-handle-left, - .resize-handle-right { - box-shadow: 0 0 2px rgb(0 0 0 / 45%); - } - } -} - -.member { - color: darkslategrey; -} - -.resize-handle-left { - left: 0; -} - -.resize-handle-right { - right: 0; -} - -.project-collapse-icon { - font-size: 12px; - line-height: 15px; - height: 15px; - width: 15px; - color: #47474791 !important -} - -.today { - border-left: 2px solid #1890ff; - background-repeat: no-repeat; - background-position: center center; -} - -.today-header { - background-color: #1890ff; - border-color: #1890ff; - color: white; -} - -.top-grid-header { - border-top: none; -} - -.grid-entry-top { - border-bottom: none; -} - -.month-grid { - border-top: 1px solid #f1f1f1; -} - -.parent-relative { - position: absolute; -} - -.px-3.py-0.ng-star-inserted.resize-active.resize-ghost-element { - margin-top: 0 !important; -} - -.dropdown-highlight:hover { - background-color: rgba(208, 238, 250, 0.33); - border: #5587f5 1px solid; - border-radius: 3px; -} - -.expanded { - transform: rotate(-90deg); -} - -.sub-tasks-arrow { - position: relative; - cursor: pointer; - left: 3px; - width: 16px; - padding: 2px; - border: 1px solid transparent; - z-index: 1; -} - -.resource-cell { - height: 22px; - margin: 7px 0 1px 0; - border-radius: 0; -} - -.child-background-cell { - background-color: #f5f5f5; -} - -.child-background-color { - background-color: #f5f5f58a; -} - -.no-data-img-holder { - width: 100px; - margin-top: 42px; -} diff --git a/worklenz-frontend/src/app/administrator/schedule/project-schedule/project-schedule.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/project-schedule/project-schedule.component.spec.ts deleted file mode 100644 index 6b4fc893..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/project-schedule/project-schedule.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ProjectScheduleComponent} from './project-schedule.component'; - -describe('ProjectResourcesComponent', () => { - let component: ProjectScheduleComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ProjectScheduleComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ProjectScheduleComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/project-schedule/project-schedule.component.ts b/worklenz-frontend/src/app/administrator/schedule/project-schedule/project-schedule.component.ts deleted file mode 100644 index c885ba04..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/project-schedule/project-schedule.component.ts +++ /dev/null @@ -1,116 +0,0 @@ -import {Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges} from '@angular/core'; -import {endOfWeek, format, startOfWeek} from "date-fns"; -import {IResource, IScheduledTask} from "@interfaces/project-wise-resources-view-model"; -import {EGanttColumnWidth, IGanttDateRange, IGanttWeekRange} from "@interfaces/gantt-chart"; -import {ResourceAllocationService} from "@api/resource-allocation.service"; -import {AvatarNamesMap} from "@shared/constants"; -import {log_error} from "@shared/utils"; -import {TaskViewService} from "../../components/task-view/task-view.service"; -import {Subscription} from "rxjs"; - -@Component({ - selector: 'worklenz-project-schedule', - templateUrl: './project-schedule.component.html', - styleUrls: ['./project-schedule.component.scss'] -}) -export class ProjectScheduleComponent implements OnChanges, OnInit, OnDestroy { - @Input() selectedWeek: { start: Date, end: Date } = {start: startOfWeek(new Date()), end: endOfWeek(new Date())}; - - loading: boolean = false; - visible = false; - showTaskModal = false; - - resourceData: IResource[] = []; - - months: IGanttWeekRange[] = []; - dates: IGanttDateRange[] = []; - scheduledTasks: IScheduledTask[] = []; - selectedTaskId: string | null = ''; - selectedResourceId: string | null = ''; - selectedDate: string | null = ''; - - projectId: string = ''; - - private tvRefreshSubscription!: Subscription; - - constructor( - private api: ResourceAllocationService, - private tvService: TaskViewService - ) { - } - - get title() { - return `Scheduled tasks - ${this.selectedDate}` - }; - - open(): void { - this.visible = true; - } - - close(): void { - this.visible = false; - } - - ngOnInit(): void { - this.tvRefreshSubscription = this.tvService.onRefresh.subscribe(() => { - this.getResources(); - }); - } - - ngOnDestroy() { - this.tvRefreshSubscription?.unsubscribe(); - } - - getColor(name?: string) { - return AvatarNamesMap[name?.charAt(0).toUpperCase() || 'A']; - } - - ngOnChanges(changes: SimpleChanges): void { - const selectedWeek = changes['selectedWeek']; - if (selectedWeek.currentValue !== selectedWeek.previousValue) { - void this.getResources(); - } - } - - async getResources() { - try { - this.loading = true; - const res = await this.api.getProjectWiseResources(this.selectedWeek); - - if (res.done) { - this.months = res.body.months; - this.dates = res.body.dates; - this.resourceData = res.body.projects; - document.documentElement.style.setProperty('--column_count', this.dates.length.toString()); - document.documentElement.style.setProperty('--column_width', `${EGanttColumnWidth.DAYS}px`); - document.documentElement.style.setProperty('--top-margin', '180px'); - } - - this.loading = false; - } catch (e) { - this.loading = false; - log_error(e); - } - } - - onTaskCreateOrUpdate() { - this.getResources(); - } - - taskSelected(id: string = '', projectId: string = '') { - this.projectId = projectId; - this.selectedTaskId = id; - this.showTaskModal = true; - } - - scheduleClicked(scheduledTasks: IScheduledTask[], resourceId: string = '', date: string = '') { - this.visible = true; - this.selectedDate = format(new Date(date), 'yyyy-MM-dd'); - this.selectedResourceId = resourceId; - this.scheduledTasks = scheduledTasks; - } - - onVisibilityChange(visible: boolean) { - if (visible) document.body.classList.add("task-form-drawer-opened"); - } -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-routing.module.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-routing.module.ts deleted file mode 100644 index b52c5459..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-routing.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {ScheduleViewComponent} from "./schedule-view/schedule-view.component"; - -const routes: Routes = [ - {path: '', component: ScheduleViewComponent}, -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class ScheduleRoutingModule { -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/add-member-allocation/add-member-allocation.component.html b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/add-member-allocation/add-member-allocation.component.html deleted file mode 100644 index a80ebc9f..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/add-member-allocation/add-member-allocation.component.html +++ /dev/null @@ -1,3 +0,0 @@ -
-
-
diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/add-member-allocation/add-member-allocation.component.scss b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/add-member-allocation/add-member-allocation.component.scss deleted file mode 100644 index 1472bf07..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/add-member-allocation/add-member-allocation.component.scss +++ /dev/null @@ -1,14 +0,0 @@ -.indicator { - height: 25px; - background: rgb(217, 227, 238); - border: none; - border-radius: 4px; - position: absolute; - z-index: 9; - transition: 0.15s all; -} - -.h-default { - min-height: 45px; - max-height: 45px; -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/add-member-allocation/add-member-allocation.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/add-member-allocation/add-member-allocation.component.spec.ts deleted file mode 100644 index 8f8623e3..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/add-member-allocation/add-member-allocation.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { AddMemberAllocationComponent } from './add-member-allocation.component'; - -describe('AddMemberAllocationComponent', () => { - let component: AddMemberAllocationComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [AddMemberAllocationComponent] - }); - fixture = TestBed.createComponent(AddMemberAllocationComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/add-member-allocation/add-member-allocation.component.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/add-member-allocation/add-member-allocation.component.ts deleted file mode 100644 index 6542aab0..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/add-member-allocation/add-member-allocation.component.ts +++ /dev/null @@ -1,105 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {Socket} from "ngx-socket-io"; -import {AuthService} from "@services/auth.service"; -import {ProjectScheduleService} from "../../service/project-schedule-service.service"; -import {SchedulerCommonService} from "../../../service/scheduler-common.service"; -import {SocketEvents} from "@shared/socket-events"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; - -@Component({ - selector: 'worklenz-add-member-allocation', - templateUrl: './add-member-allocation.component.html', - styleUrls: ['./add-member-allocation.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class AddMemberAllocationComponent implements OnInit, OnDestroy { - @Input({required: true}) projectId: string | null = null; - @Input({required: true}) teamMemberId: string | null = null; - - left: number = 0; - width: number = 0; - - private readonly _session: ILocalSession | null = null; - - constructor( - private readonly socket: Socket, - private readonly auth: AuthService, - private readonly cdr: ChangeDetectorRef, - private readonly service: ProjectScheduleService, - private readonly common: SchedulerCommonService - ) { - this._session = this.auth.getCurrentSession(); - - this.service.onResetAllocator.pipe(takeUntilDestroyed()).subscribe(() => { - this.reset(); - }) - } - - ngOnInit() { - this.socket.on(SocketEvents.SCHEDULE_MEMBER_ALLOCATION_CREATE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.SCHEDULE_MEMBER_ALLOCATION_CREATE.toString(), this.handleResponse); - } - - onDragStart(event: MouseEvent) { - this.left = event.offsetX - (event.offsetX % 35); - const pageX = event.pageX; - const onMouseMove = (e: MouseEvent) => { - const deltaX = e.pageX - pageX; - requestAnimationFrame(() => { - this.width = deltaX; - this.service.highlighterWidth = this.width; - this.service.highlighterLeft = this.left; - this.cdr.markForCheck(); - }); - }; - const onMouseUp = (event: MouseEvent) => { - document.removeEventListener('mousemove', onMouseMove); - document.removeEventListener('mouseup', onMouseUp); - if (pageX > event.pageX) { - this.width = 0; - this.left = 0; - this.cdr.markForCheck(); - return; - } - requestAnimationFrame(() => { - this.width = this.width + (35 - (this.width % 35)); - this.service.highlighterWidth = this.width; - this.service.highlighterLeft = this.left; - this.createAllocation(); - this.cdr.markForCheck(); - }) - }; - document.addEventListener('mousemove', onMouseMove); - document.addEventListener('mouseup', onMouseUp); - } - - createAllocation() { - this.socket.emit(SocketEvents.SCHEDULE_MEMBER_ALLOCATION_CREATE.toString(), JSON.stringify({ - project_id: this.projectId, - team_member_id: this.teamMemberId, - offset: this.left, - width: this.width, - chart_start: this.common.startDate, - time_zone: this._session?.timezone_name - })); - - } - - private handleResponse = (response: { team_member_id: string; project_id: string }) => { - if (this.teamMemberId === response.team_member_id && this.projectId === response.project_id) { - this.service.emitMemberIndicatorChange(this.projectId as string, this.teamMemberId as string); - } - }; - - private reset() { - this.width = 0; - this.left = 0; - this.service.highlighterWidth = 0; - this.service.highlighterLeft = 0; - this.cdr.markForCheck(); - } -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/context-menu/context-menu.component.html b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/context-menu/context-menu.component.html deleted file mode 100644 index fee68625..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/context-menu/context-menu.component.html +++ /dev/null @@ -1,8 +0,0 @@ - -
    -
  • - - Delete -
  • -
-
diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/context-menu/context-menu.component.scss b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/context-menu/context-menu.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/context-menu/context-menu.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/context-menu/context-menu.component.spec.ts deleted file mode 100644 index 0837cb70..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/context-menu/context-menu.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ContextMenuComponent } from './context-menu.component'; - -describe('ContextMenuComponent', () => { - let component: ContextMenuComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ContextMenuComponent] - }); - fixture = TestBed.createComponent(ContextMenuComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/context-menu/context-menu.component.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/context-menu/context-menu.component.ts deleted file mode 100644 index 58fd039b..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/context-menu/context-menu.component.ts +++ /dev/null @@ -1,67 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, ViewChild} from '@angular/core'; -import {ProjectScheduleService} from "../../service/project-schedule-service.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {Subject} from "rxjs"; -import {NzContextMenuService, NzDropdownMenuComponent} from "ng-zorro-antd/dropdown"; -import {IMemberIndicatorContextMenuEvent} from "@interfaces/schedular"; -import {ScheduleApiService} from "@api/schedule-api.service"; -import {log_error} from "@shared/utils"; - -@Component({ - selector: 'worklenz-context-menu', - templateUrl: './context-menu.component.html', - styleUrls: ['./context-menu.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ContextMenuComponent implements OnDestroy { - @ViewChild('contextMenuDropdown', {static: false}) contextMenuDropdown!: NzDropdownMenuComponent; - - private projectId: string | null = null; - private teamMemberId: string | null = null; - private idsToDelete: string[] = []; - - protected deleting = false; - private readonly destroy$ = new Subject(); - - constructor( - private readonly service: ProjectScheduleService, - private readonly contextMenuService: NzContextMenuService, - private readonly api: ScheduleApiService, - private readonly cdr: ChangeDetectorRef - ) { - this.service.onContextMenu$.pipe(takeUntilDestroyed()).subscribe((value) => { - this.onContextMenu(value); - this.cdr.markForCheck(); - }) - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } - - private onContextMenu(value: IMemberIndicatorContextMenuEvent) { - this.idsToDelete = value.ids; - this.projectId = value.projectId; - this.teamMemberId = value.teamMemberId; - this.contextMenuService.create(value.event, this.contextMenuDropdown); - this.cdr.markForCheck(); - } - - async delete() { - try { - this.deleting = true; - const res = await this.api.bulkDeleteMemberAllocations(this.idsToDelete); - if (res.done) { - this.service.emitMemberProjectIndicatorChange(this.projectId as string, this.teamMemberId as string, true); - this.deleting = false; - this.cdr.markForCheck(); - } - } catch (e) { - log_error(e); - this.deleting = false; - this.cdr.markForCheck(); - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/end-date/end-date.component.html b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/end-date/end-date.component.html deleted file mode 100644 index db9a6085..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/end-date/end-date.component.html +++ /dev/null @@ -1,17 +0,0 @@ -
- {{task.end_date | dateFormatter}} - - - -
diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/end-date/end-date.component.scss b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/end-date/end-date.component.scss deleted file mode 100644 index 1ffe7e49..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/end-date/end-date.component.scss +++ /dev/null @@ -1,36 +0,0 @@ -nz-date-picker { - max-width: 150px; - border-color: transparent !important; - top: 0; - bottom: 0; - left: 0; - right: 0; - - &:hover { - border-color: #d9d9d9 !important; - } -} - -.editable { - position: relative; -} - -.date-text { - display: flex; - align-items: center; - padding-left: 15px; -} - -.past-date { - color: #ff4d4f; -} - -.soon-date { - color: #52c41a; -} - -.def-g-height { - max-height: 32px; - min-height: 32px; - line-height: 32px; -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/end-date/end-date.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/end-date/end-date.component.spec.ts deleted file mode 100644 index a7e2121c..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/end-date/end-date.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { EndDateComponent } from './end-date.component'; - -describe('EndDateComponent', () => { - let component: EndDateComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [EndDateComponent] - }); - fixture = TestBed.createComponent(EndDateComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/end-date/end-date.component.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/end-date/end-date.component.ts deleted file mode 100644 index 51f6a22f..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/end-date/end-date.component.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - HostBinding, - Input, - NgZone, - OnDestroy, - OnInit, - Renderer2 -} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {Socket} from "ngx-socket-io"; -import {ScheduleMemberTasksService} from "../../service/schedule-member-tasks-service.service"; -import {AuthService} from "@services/auth.service"; -import {SocketEvents} from "@shared/socket-events"; -import moment from "moment"; -import {formatGanttDate} from "@shared/utils"; - -@Component({ - selector: 'worklenz-schedule-end-date', - templateUrl: './end-date.component.html', - styleUrls: ['./end-date.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class EndDateComponent implements OnInit, OnDestroy { - @Input() task: IProjectTask = {}; - @HostBinding("class") cls = "flex-row task-due-date"; - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly service: ScheduleMemberTasksService, - private readonly renderer: Renderer2, - private readonly auth: AuthService, - ) { - } - - ngOnInit() { - this.socket.on(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_END_DATE_CHANGE.toString(), this.handleResponse); - } - - private handleResponse = (response: { - id: string; - parent_task: string | null; - end_date: string; - }) => { - if (response.id === this.task.id && this.task.end_date !== response.end_date) { - this.task.end_date = response.end_date; - this.cdr.markForCheck(); - } - }; - - handleEndDateChange(date: string, task: IProjectTask) { - this.socket.emit( - SocketEvents.TASK_END_DATE_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - end_date: formatGanttDate(date) || null, - parent_task: task.parent_task_id, - time_zone: this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone - })); - } - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - checkForPastDate(endDate: any) { - const formattedEndDate = moment(endDate).format('YYYY-MM-DD'); - return formattedEndDate < moment().format('YYYY-MM-DD'); - } - - checkForSoonDate(endDate: any) { - const formattedEndDate = moment(endDate).format('YYYY-MM-DD'); - const tomorrow = moment().add(1, 'day').format('YYYY-MM-DD'); - return formattedEndDate === moment().format('YYYY-MM-DD') || formattedEndDate === tomorrow; - } - -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-indicator/member-indicator.component.html b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-indicator/member-indicator.component.html deleted file mode 100644 index 9bdacda9..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-indicator/member-indicator.component.html +++ /dev/null @@ -1,13 +0,0 @@ - -
-
-
-
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-indicator/member-indicator.component.scss b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-indicator/member-indicator.component.scss deleted file mode 100644 index da91d58a..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-indicator/member-indicator.component.scss +++ /dev/null @@ -1,54 +0,0 @@ -.duration-indicator { - position: absolute; - border-radius: 4px; - top: 10px; - bottom: 10px; - overflow: hidden; - z-index: 0; - transition: 0.125s all; - border: none; - background: #d9e3ee; - &:hover .resize-handle { - opacity: 1; - } -} - -.resize-handle { - position: absolute; - width: 6px; - height: 100%; - cursor: col-resize; - background-color: #7cb8fb; - transition: all 0.15s ease; - z-index: 1; - opacity: 0; - - &:hover { - width: 8px; - } - - &::before { - position: absolute; - content: ':'; - left: 1px; - color: white; - } -} - -.left-handle { - left: 0; -} - -.right-handle { - right: 0; -} - -.log-indicator { - background-color: #8fd688; - border: none; - border-radius: 4px; - position: absolute; - top: 0; - bottom: 0; - z-index: 0; -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-indicator/member-indicator.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-indicator/member-indicator.component.spec.ts deleted file mode 100644 index a237e1b4..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-indicator/member-indicator.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { MemberIndicatorComponent } from './member-indicator.component'; - -describe('MemberIndicatorComponent', () => { - let component: MemberIndicatorComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [MemberIndicatorComponent] - }); - fixture = TestBed.createComponent(MemberIndicatorComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-indicator/member-indicator.component.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-indicator/member-indicator.component.ts deleted file mode 100644 index 7dbc9d05..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-indicator/member-indicator.component.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - HostListener, - Input, - OnDestroy, - OnInit -} from '@angular/core'; -import {IMemberAllocation} from "@interfaces/schedular"; -import moment from "moment/moment"; -import {SchedulerCommonService} from "../../../service/scheduler-common.service"; -import {ProjectScheduleService} from "../../service/project-schedule-service.service"; -import {Moment} from "moment"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; - -@Component({ - selector: 'worklenz-member-indicator', - templateUrl: './member-indicator.component.html', - styleUrls: ['./member-indicator.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class MemberIndicatorComponent implements OnInit, OnDestroy { - @Input({required: true}) teamMemberId: string | null = null; - @Input({required: true}) projectId: string | null = null; - @Input({required: true}) allocation: IMemberAllocation | null = null; - - isResized = false; - - constructor( - private readonly service: ProjectScheduleService, - private readonly common: SchedulerCommonService, - private readonly cdr: ChangeDetectorRef, - private readonly socket: Socket, - ) { - } - - @HostListener("contextmenu", ["$event"]) - private onContextMenu(event: MouseEvent) { - if (this.allocation?.ids) this.service.emitOnContextMenu(event, this.projectId as string, this.teamMemberId as string, this.allocation.ids); - this.cdr.markForCheck(); - } - - ngOnInit() { - this.socket.on(SocketEvents.SCHEDULE_MEMBER_START_DATE_CHANGE.toString(), this.handleResponse); - this.socket.on(SocketEvents.SCHEDULE_MEMBER_END_DATE_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.SCHEDULE_MEMBER_START_DATE_CHANGE.toString(), this.handleResponse); - this.socket.removeListener(SocketEvents.SCHEDULE_MEMBER_END_DATE_CHANGE.toString(), this.handleResponse); - } - - private handleResponse = (response: { id: string; date: string }) => { - const checkAvailablity = this.allocation?.ids.find(a => a === response.id); - if (checkAvailablity) { - - if (moment(response.date).isSameOrAfter(this.common.endDate) || moment(response.date).isSameOrBefore(this.common.startDate)) { - this.service.emitReload(); - } else { - this.service.emitMemberIndicatorChange(this.projectId as string, this.teamMemberId as string); - } - } - }; - - - onResizeStart(event: MouseEvent, allocation: IMemberAllocation, direction: 'left' | 'right', indicator: HTMLDivElement): void { - if (!allocation || !allocation.indicator_width || !allocation.indicator_offset || event.button == 2) { - return; - } - - const startX = event.clientX; - const startWidth = allocation.indicator_width; - const startLeft = allocation.indicator_offset; - const fChartStart = moment(this.common.startDate).format("YYYY-MM-DD"); - const chartStart = moment(fChartStart); - - indicator.style.zIndex = '11'; - - const onMouseMove = (e: MouseEvent) => { - const deltaX = e.clientX - startX; - - let newWidth = startWidth; - let newLeft = startLeft; - - if (direction === 'left') { - newWidth = startWidth - deltaX; - newLeft = startLeft + deltaX; - } else if (direction === 'right') { - newWidth = startWidth + deltaX; - } - - this.cdr.markForCheck(); - - if (newWidth >= this.common.GANNT_COLUMN_WIDTH - 3) { - allocation.indicator_width = newWidth; - allocation.indicator_offset = newLeft; - this.isResized = true; - this.cdr.markForCheck(); - } - }; - - const onMouseUp = () => { - - document.removeEventListener('mousemove', onMouseMove); - document.removeEventListener('mouseup', onMouseUp); - - if (direction === 'left') { - const diff = allocation.indicator_offset ? allocation.indicator_offset % this.common.GANNT_COLUMN_WIDTH : 0; - const fromStart = allocation.indicator_offset ? (allocation.indicator_offset - diff) / this.common.GANNT_COLUMN_WIDTH : 0; - const allocationStartDate = chartStart.add(fromStart, "days"); - - this.changeAllocationStart(allocationStartDate) - - } - if (direction === 'right') { - const diff = allocation.indicator_width ? allocation.indicator_width % this.common.GANNT_COLUMN_WIDTH : 0; - let allocationWidth = allocation.indicator_width; - if (diff > 0) { - allocationWidth = allocation.indicator_width ? allocation.indicator_width + (this.common.GANNT_COLUMN_WIDTH - diff) : 0; - } - if (!allocationWidth) return; - - const duration = allocation.indicator_offset ? (allocation.indicator_offset + allocationWidth) / this.common.GANNT_COLUMN_WIDTH : 0; - const allocationEndDate = moment(fChartStart).add(duration - 1, "days") - - this.changeAllocationEnd(allocationEndDate); - - - } - }; - document.addEventListener('mousemove', onMouseMove); - document.addEventListener('mouseup', onMouseUp); - } - - changeAllocationStart(startDate: Moment) { - this.socket.emit( - SocketEvents.SCHEDULE_MEMBER_START_DATE_CHANGE.toString(), JSON.stringify({ - project_id: this.projectId, - team_member_is: this.teamMemberId, - allocation_ids: this.allocation?.ids || [], - allocated_from: startDate - }) - ) - } - - changeAllocationEnd(endDate: Moment) { - this.socket.emit( - SocketEvents.SCHEDULE_MEMBER_END_DATE_CHANGE.toString(), JSON.stringify({ - project_id: this.projectId, - team_member_is: this.teamMemberId, - allocation_ids: this.allocation?.ids || [], - allocated_to: endDate - }) - ) - } - -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-task-add-container/member-task-add-container.component.html b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-task-add-container/member-task-add-container.component.html deleted file mode 100644 index 2bd6c591..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-task-add-container/member-task-add-container.component.html +++ /dev/null @@ -1,21 +0,0 @@ - -
- -
-
- - {{label}} -
-
diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-task-add-container/member-task-add-container.component.scss b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-task-add-container/member-task-add-container.component.scss deleted file mode 100644 index e5663a78..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-task-add-container/member-task-add-container.component.scss +++ /dev/null @@ -1,14 +0,0 @@ -.input-icon { - font-size: 11px; -} - -.task-name { - border: 1px solid transparent; - - &:hover { - cursor: text; - background: #fff; - border: 1px solid #bfbfbf; - border-radius: 4px; - } -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-task-add-container/member-task-add-container.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-task-add-container/member-task-add-container.component.spec.ts deleted file mode 100644 index 7c01efbf..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-task-add-container/member-task-add-container.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { MemberTaskAddContainerComponent } from './member-task-add-container.component'; - -describe('MemberTaskAddContainerComponent', () => { - let component: MemberTaskAddContainerComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [MemberTaskAddContainerComponent] - }); - fixture = TestBed.createComponent(MemberTaskAddContainerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-task-add-container/member-task-add-container.component.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-task-add-container/member-task-add-container.component.ts deleted file mode 100644 index f6d2af2b..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/member-task-add-container/member-task-add-container.component.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - Input, - NgZone, - OnInit, - Output, - ViewChild -} from '@angular/core'; -import {IScheduleProjectMember} from "@interfaces/schedular"; -import {FormBuilder, FormGroup, Validators} from "@angular/forms"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {smallId} from "@shared/utils"; -import {Socket} from "ngx-socket-io"; -import {AuthService} from "@services/auth.service"; -import {ScheduleMemberTasksService} from "../../service/schedule-member-tasks-service.service"; -import {ScheduleMemberTasksHashmapService} from "../../service/schedule-member-tasks-hashmap-service.service"; -import {ITaskCreateRequest} from "@interfaces/api-models/task-create-request"; -import {SocketEvents} from "@shared/socket-events"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITaskAssigneesUpdateResponse} from "@interfaces/task-assignee-update-response"; -import {DRAWER_ANIMATION_INTERVAL} from "@shared/constants"; - -@Component({ - selector: 'worklenz-schedule-member-task-add-container', - templateUrl: './member-task-add-container.component.html', - styleUrls: ['./member-task-add-container.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class MemberTaskAddContainerComponent implements OnInit { - @ViewChild('taskInput', {static: false}) taskInput!: ElementRef; - readonly form!: FormGroup; - - @Input({required: true}) teamMember: IScheduleProjectMember | null = null; - @Input() projectId: string | null = null; - @Input() groupId: string | null = null; - @Input() label = "Add Task"; - - @Output() focusChange: EventEmitter = new EventEmitter(); - - taskInputVisible = false; - creating = false; - private session: ILocalSession | null = null; - - protected readonly id = smallId(4); - - private readonly _session: ILocalSession | null = null; - - get profile() { - return this.auth.getCurrentSession(); - } - - constructor( - private readonly socket: Socket, - private readonly auth: AuthService, - private readonly fb: FormBuilder, - private readonly service: ScheduleMemberTasksService, - private readonly ngZone: NgZone, - private readonly cdr: ChangeDetectorRef, - private readonly map: ScheduleMemberTasksHashmapService - ) { - this.form = this.fb.group({ - name: [null, [Validators.required, Validators.pattern(/^(\s+\S+\s*)*(?!\s).*$/)]] - }); - this._session = this.auth.getCurrentSession(); - } - - ngOnInit() { - this.session = this.auth.getCurrentSession(); - } - - focusTaskInput() { - this.taskInputVisible = true; - this.focusChange.emit(this.taskInputVisible); - - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.taskInput?.nativeElement.focus(); - this.taskInput?.nativeElement.select(); - }, 100); // wait for the animation end - }); -} - -addTaskInputBlur() { - this.taskInputVisible = false; - this.focusChange.emit(this.taskInputVisible); -} - -async onInputBlur() { - if (this.isValidInput()) { - await this.addInstantTask(); - return; - } - - this.addTaskInputBlur(); -} - -private createRequest() { - if (!this.projectId || !this._session) return null; - - const sess = this._session; - const body: ITaskCreateRequest = { - name: this.form.value.name, - project_id: this.projectId, - reporter_id: sess.id, - team_id: sess.team_id - }; - - const groupBy = this.service.getCurrentGroup(); - - if (groupBy.value === this.service.GROUP_BY_STATUS_VALUE) { - body.status_id = this.groupId || undefined; - } else if (groupBy.value === this.service.GROUP_BY_PRIORITY_VALUE) { - body.priority_id = this.groupId || undefined; - } else if (groupBy.value === this.service.GROUP_BY_PHASE_VALUE) { - body.phase_id = this.groupId || undefined; - } - - return body; -} - -private isValidInput() { - return this.form.valid && this.form.value.name.trim().length; -} - -async addInstantTask() { - if (this.creating) return; - if (!this.projectId || !this._session) return; - - if (this.isValidInput()) { - try { - const req = this.createRequest(); - if (!req) return; - - this.creating = true; - - this.socket.emit(SocketEvents.QUICK_TASK.toString(), JSON.stringify(req)); - this.socket.once(SocketEvents.QUICK_TASK.toString(), (task: IProjectTask) => { - const task_assign_body = { - team_member_id: this.teamMember?.team_member_id, - project_id: task.project_id, - task_id: task.id, - reporter_id: this.session?.id, - mode: 0, - }; - this.socket.emit(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), JSON.stringify(task_assign_body)); - this.socket.once(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), (response: ITaskAssigneesUpdateResponse) => { - this.creating = false; - this.onNewTaskReceived(task); - this.cdr.markForCheck(); - }); - - }); - - } catch (e) { - this.creating = false; - } - - this.cdr.markForCheck(); - } -} - -public reset(scroll = true) { - this.creating = false; - - this.form.controls["name"].setValue(null); - this.taskInputVisible = true; - - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.taskInput?.nativeElement.focus(); - if (scroll) - window.scrollTo(0, document.body.scrollHeight); - }, DRAWER_ANIMATION_INTERVAL); // wait for the animation end - }); - - this.cdr.markForCheck(); -} - -private onNewTaskReceived(task: IProjectTask) { - if (this.groupId && task.id) { - if (this.map.has(task.id)) return; - this.service.addTask(task, this.groupId); - this.service.emitRefreshMembers(); - this.reset(false); - } -} - - -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/phase/phase.component.html b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/phase/phase.component.html deleted file mode 100644 index 80d87dda..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/phase/phase.component.html +++ /dev/null @@ -1,20 +0,0 @@ -
- - - - - -
diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/phase/phase.component.scss b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/phase/phase.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/phase/phase.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/phase/phase.component.spec.ts deleted file mode 100644 index e9acc999..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/phase/phase.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { PhaseComponent } from './phase.component'; - -describe('PhaseComponent', () => { - let component: PhaseComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [PhaseComponent] - }); - fixture = TestBed.createComponent(PhaseComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/phase/phase.component.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/phase/phase.component.ts deleted file mode 100644 index dacbecaa..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/phase/phase.component.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostBinding, - Input, - NgZone, OnDestroy, OnInit, - Renderer2 -} from '@angular/core'; -import {Socket} from "ngx-socket-io"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ALPHA_CHANNEL} from "@shared/constants"; -import {ITaskPhase} from "@interfaces/api-models/task-phase"; -import {ScheduleMemberTasksService} from "../../service/schedule-member-tasks-service.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {ITaskPhaseChangeResponse} from "@interfaces/task-phase-change-response"; - -@Component({ - selector: 'worklenz-schedule-phase', - templateUrl: './phase.component.html', - styleUrls: ['./phase.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class PhaseComponent implements OnInit, OnDestroy{ - @Input({required: true}) task: IProjectTask = {}; - @HostBinding("class") cls = "flex-row task-phase"; - - readonly PHASE_COLOR = "#a9a9a9" + ALPHA_CHANNEL; - readonly PLACEHOLDER_COLOR = 'rgba(0, 0, 0, 0.85) !important'; - - phases: ITaskPhase[] = []; - - loading = false; - - constructor( - private readonly service: ScheduleMemberTasksService, - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly element: ElementRef, - private readonly renderer: Renderer2 - ) { - this.service.onPhaseChange$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.updatePhases(); - this.cdr.markForCheck(); - }); - } - - ngOnInit() { - this.updatePhases(); - this.socket.on(SocketEvents.TASK_PHASE_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_PHASE_CHANGE.toString(), this.handleResponse); - } - - private isGroupByPhase() { - return this.service.getCurrentGroup().value === this.service.GROUP_BY_PHASE_VALUE; - } - - trackById(index: number, item: ITaskStatusViewModel) { - return item.id; - } - - handleChange(phaseId: string, taskId?: string) { - if (!taskId) return; - this.socket.emit(SocketEvents.TASK_PHASE_CHANGE.toString(), { - task_id: taskId, - phase_id: phaseId, - parent_task: this.task.parent_task_id - }); - } - - private handleResponse = (response: ITaskPhaseChangeResponse) => { - if (response && response.task_id === this.task.id) { - this.task.phase_color = response.color_code || undefined; - this.task.phase_id = response.id; - - if (this.isGroupByPhase()) { - this.service.updateTaskGroup(this.task, false); - if (this.service.isSubtasksIncluded) { - this.service.emitRefreshSubtasksIncluded(); - } - } - this.cdr.markForCheck(); - } - } - - private updatePhases() { - this.phases = this.service.phases; - } - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - handleOpen(open: boolean) { - this.toggleHighlightCls(open, this.element.nativeElement); - } - - -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/priority/priority.component.html b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/priority/priority.component.html deleted file mode 100644 index e7adb32e..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/priority/priority.component.html +++ /dev/null @@ -1,15 +0,0 @@ -
- - - - - -
diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/priority/priority.component.scss b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/priority/priority.component.scss deleted file mode 100644 index dd984827..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/priority/priority.component.scss +++ /dev/null @@ -1,5 +0,0 @@ -nz-select { - max-width: 96px; - width: 100%; - text-overflow: ellipsis; -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/priority/priority.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/priority/priority.component.spec.ts deleted file mode 100644 index a3e58b13..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/priority/priority.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { PriorityComponent } from './priority.component'; - -describe('PriorityComponent', () => { - let component: PriorityComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [PriorityComponent] - }); - fixture = TestBed.createComponent(PriorityComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/priority/priority.component.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/priority/priority.component.ts deleted file mode 100644 index bb29fd70..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/priority/priority.component.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostBinding, - Input, - NgZone, - OnDestroy, - OnInit, - Renderer2 -} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITaskPrioritiesGetResponse} from "@interfaces/api-models/task-priorities-get-response"; -import {ScheduleMemberTasksService} from "../../service/schedule-member-tasks-service.service"; -import {Socket} from "ngx-socket-io"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; - -@Component({ - selector: 'worklenz-schedule-priority', - templateUrl: './priority.component.html', - styleUrls: ['./priority.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class PriorityComponent implements OnInit, OnDestroy { - @Input({required: true}) task: IProjectTask = {}; - @HostBinding("class") cls = "flex-row task-priority"; - - priorities: ITaskPrioritiesGetResponse[] = []; - - loading = false; - - constructor( - private readonly service: ScheduleMemberTasksService, - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly element: ElementRef, - private readonly renderer: Renderer2 - ) { - this.service.onPrioritiesChange$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.updatePriorities(); - this.cdr.markForCheck(); - }); - } - - ngOnInit() { - this.updatePriorities(); - this.socket.on(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.priorities = []; - this.socket.removeListener(SocketEvents.TASK_PRIORITY_CHANGE.toString(), this.handleResponse); - } - - private isGroupByPriority() { - return this.service.getCurrentGroup().value === this.service.GROUP_BY_PRIORITY_VALUE; - } - - trackById(index: number, item: ITaskStatusViewModel) { - return item.id; - } - - handlePriorityChange(priorityId: string, data: IProjectTask) { - this.socket.emit(SocketEvents.TASK_PRIORITY_CHANGE.toString(), JSON.stringify({ - task_id: data.id, - priority_id: priorityId, - parent_task: this.task.parent_task_id - })); - } - - private handleResponse = (response: { - priority_id: string | undefined; - name: string | undefined; id: string; parent_task: string; color_code: string; - }) => { - if (response && response.id === this.task.id) { - this.task.priority_color = response.color_code; - this.task.priority = response.priority_id; - - if (this.isGroupByPriority()) { - this.service.updateTaskGroup(this.task, false); - if (this.service.isSubtasksIncluded) { - this.service.emitRefreshSubtasksIncluded(); - } - } - this.cdr.markForCheck(); - } - } - - private updatePriorities() { - this.priorities = this.service.priorities; - } - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - handleOpen(open: boolean) { - this.toggleHighlightCls(open, this.element.nativeElement); - } - - -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-indicator/project-indicator.component.html b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-indicator/project-indicator.component.html deleted file mode 100644 index deb415cf..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-indicator/project-indicator.component.html +++ /dev/null @@ -1,8 +0,0 @@ - -
-
-
diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-indicator/project-indicator.component.scss b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-indicator/project-indicator.component.scss deleted file mode 100644 index 6f91b4b8..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-indicator/project-indicator.component.scss +++ /dev/null @@ -1,10 +0,0 @@ -.duration-indicator { - position: absolute; - border-radius: 4px; - top: 10px; - bottom: 10px; - overflow: hidden; - z-index: 0; - transition: 0.125s all; - border: none; -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-indicator/project-indicator.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-indicator/project-indicator.component.spec.ts deleted file mode 100644 index c71e334b..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-indicator/project-indicator.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ProjectIndicatorComponent } from './project-indicator.component'; - -describe('ProjectIndicatorComponent', () => { - let component: ProjectIndicatorComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ProjectIndicatorComponent] - }); - fixture = TestBed.createComponent(ProjectIndicatorComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-indicator/project-indicator.component.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-indicator/project-indicator.component.ts deleted file mode 100644 index a9f5198c..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-indicator/project-indicator.component.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from '@angular/core'; -import {IScheduleProject} from "@interfaces/schedular"; -import {ProjectScheduleService} from "../../service/project-schedule-service.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; - -@Component({ - selector: 'worklenz-project-indicator', - templateUrl: './project-indicator.component.html', - styleUrls: ['./project-indicator.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectIndicatorComponent { - @Input({required: true}) project: IScheduleProject | null = null; - - constructor( - private readonly service: ProjectScheduleService, - private readonly cdr: ChangeDetectorRef - ) { - this.service.onProjectIndicatorChange.pipe(takeUntilDestroyed()).subscribe((response) => { - if (this.project?.id === response.projectId) { - this.project.indicator_offset = response.indicatorOffset; - this.project.indicator_width = response.indicatorWidth; - this.cdr.markForCheck(); - } - }), - this.service.onProjectToggle$.pipe(takeUntilDestroyed()).subscribe((response) => { - if (this.project?.id === response) { - this.cdr.markForCheck(); - } - }) - } - -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-member-tasks-drawer/project-member-tasks-drawer.component.html b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-member-tasks-drawer/project-member-tasks-drawer.component.html deleted file mode 100644 index 29639355..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-member-tasks-drawer/project-member-tasks-drawer.component.html +++ /dev/null @@ -1,92 +0,0 @@ - -
- - - - - -
    -
  • - {{item.label}} -
  • -
-
-
- - -
-
- - {{group.name}} ({{group.tasks.length}}) -
-
- - - - -
- No tasks available -
-
- - -
-
-
-
-
-
-
-
- - - - {{teamMember?.name}} - - - - - - - - - diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-member-tasks-drawer/project-member-tasks-drawer.component.scss b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-member-tasks-drawer/project-member-tasks-drawer.component.scss deleted file mode 100644 index a62efa84..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-member-tasks-drawer/project-member-tasks-drawer.component.scss +++ /dev/null @@ -1,40 +0,0 @@ -.group-heading { - max-width: max-content; - width: max-content; - padding: 6px 16px 6px 6px; - border-top-right-radius: 4px; - border-top-left-radius: 4px; -} - -.p-skel { - padding-left: 24px; -} - -.tasks-container { - max-width: 880px; - overflow-x: auto; -} - -.new-task-input { - padding-left: 4px; - padding-top: 4px; - padding-bottom: 4px; - border-bottom: 1px solid #f0f0f0; - border-right: 1px solid #f0f0f0; - border-left: 1px solid #f0f0f0; - max-width: 880px; - overflow-x: auto; - position: sticky; - left: 0; -} - -.tasks-empty-placeholder { - width: 100%; - height: 42px; - background: #fafafa; - border-left: 1px solid #f0f0f0; - border-right: 1px solid #f0f0f0; - left: 0; - position: sticky; -} - diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-member-tasks-drawer/project-member-tasks-drawer.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-member-tasks-drawer/project-member-tasks-drawer.component.spec.ts deleted file mode 100644 index 47ed3434..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-member-tasks-drawer/project-member-tasks-drawer.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ProjectMemberTasksDrawerComponent } from './project-member-tasks-drawer.component'; - -describe('ProjectMemberTasksDrawerComponent', () => { - let component: ProjectMemberTasksDrawerComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ProjectMemberTasksDrawerComponent] - }); - fixture = TestBed.createComponent(ProjectMemberTasksDrawerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-member-tasks-drawer/project-member-tasks-drawer.component.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-member-tasks-drawer/project-member-tasks-drawer.component.ts deleted file mode 100644 index aa8f60b4..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/project-member-tasks-drawer/project-member-tasks-drawer.component.ts +++ /dev/null @@ -1,282 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - NgZone, - Output, - Renderer2 -} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITaskStatusCategory} from "@interfaces/task-status-category"; -import {Socket} from "ngx-socket-io"; -import {UtilsService} from "@services/utils.service"; -import {TaskStatusesApiService} from "@api/task-statuses-api.service"; -import {TaskPrioritiesService} from "@api/task-priorities.service"; -import {TaskPhasesApiService} from "@api/task-phases-api.service"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {ScheduleMemberTasksHashmapService} from "../../service/schedule-member-tasks-hashmap-service.service"; -import {ScheduleMemberTasksService} from "../../service/schedule-member-tasks-service.service"; -import {IMemberTaskListGroup, IScheduleProjectMember, IScheduleTasksConfig} from "@interfaces/schedular"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {deepClone} from "@shared/utils"; -import {AvatarNamesMap} from "@shared/constants"; -import {ScheduleApiService} from "@api/schedule-api.service"; -import {IGroupByOption} from "../../../../../modules/task-list-v2/interfaces"; -import {ProjectsService} from "../../../../../projects/projects.service"; - -@Component({ - selector: 'worklenz-schedule-project-member-tasks-drawer', - templateUrl: './project-member-tasks-drawer.component.html', - styleUrls: ['./project-member-tasks-drawer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectMemberTasksDrawerComponent { - private _show = false; - get show(): boolean { - return this._show; - } - - @Input() set show(value: boolean) { - if (value === this._show) return; - this._show = value; - } - - @Input({required: true}) teamMember: IScheduleProjectMember | null = null; - @Input({required: true}) projectId: string | null = null; - @Output() showChange: EventEmitter = new EventEmitter(); - @Output() onOpenTask = new EventEmitter(); - - private readonly DRAWER_CLOSE_TIME = 100; - - readonly BODY_STYLE = { - padding: 0, - overflowX: 'hidden', - overflowY: 'auto' - }; - - loading = false; - protected loadingGroups = false; - protected loadingStatuses = false; - protected loadingPriorities = false; - protected loadingPhases = false; - protected loadingCategories = false; - - protected groupIds: string[] = []; - - protected selectedTask: IProjectTask | null = null; - protected categories: ITaskStatusCategory[] = []; - - protected get groups() { - return this.service.groups; - } - - get selectedGroup() { - return this.service.getCurrentGroup(); - } - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly api: ScheduleApiService, - private readonly ngZone: NgZone, - private readonly map: ScheduleMemberTasksHashmapService, - private readonly socket: Socket, - private readonly renderer: Renderer2, - public readonly service: ScheduleMemberTasksService, - public readonly utils: UtilsService, - private readonly statusesApi: TaskStatusesApiService, - private readonly prioritiesApi: TaskPrioritiesService, - private readonly phasesApi: TaskPhasesApiService, - private readonly taskViewService: TaskViewService, - private readonly projectService: ProjectsService - ) { - - this.taskViewService.onSingleMemberChange.pipe(takeUntilDestroyed()) - .subscribe(async (teamMemberId: string) => { - if (teamMemberId === this.teamMember?.team_member_id) { - await this.getGroups(false); - } - this.cdr.markForCheck(); - }); - - this.taskViewService.onDelete - .pipe(takeUntilDestroyed()) - .subscribe(async task => { - this.init(); - }) - - this.service.onRemoveMembersTask.pipe(takeUntilDestroyed()).subscribe((taskId: string) => { - this.service.deleteTask(taskId); - }) - - } - - private init() { - this.service.isSubtasksIncluded = true; - void Promise.all([ - this.getGroups(true), - this.getStatuses(), - this.getPriorities(), - this.getCategories(), - this.getPhases() - ]) - ; - } - - private getConf(parentTaskId?: string): IScheduleTasksConfig { - const config: IScheduleTasksConfig = { - id: this.projectId as string, - members: this.teamMember?.team_member_id as string, - archived: false, - group: this.service.getCurrentGroup().value, - isSubtasksInclude: false, - }; - - if (parentTaskId) - config.parent_task = parentTaskId; - - return config; - } - - private async getGroups(loading = true) { - if (!this.projectId || !this.teamMember?.team_member_id) return; - try { - this.map.deselectAll(); - this.loadingGroups = loading; - const config = this.getConf(); - config.isSubtasksInclude = this.service.isSubtasksIncluded; - const res = await this.api.getTasksByMember(config) as IServerResponse; - if (res.done) { - const groups = deepClone(res.body); - this.groupIds = groups.map((g: IMemberTaskListGroup) => g.id); - this.mapTasks(groups); - this.service.groups = groups; - } - this.loadingGroups = false; - } catch (e) { - this.loadingGroups = false; - } - this.cdr.markForCheck(); - } - - private mapTasks(groups: IMemberTaskListGroup[]) { - for (const group of groups) { - this.map.registerGroup(group); - for (const task of group.tasks) { - if (task.start_date) task.start_date = new Date(task.start_date) as any; - if (task.end_date) task.end_date = new Date(task.end_date) as any; - } - } - } - - private async getStatuses() { - if (!this.projectId) return; - try { - this.loadingStatuses = true; - const res = await this.statusesApi.get(this.projectId); - if (res.done) - this.service.statuses = res.body; - this.loadingStatuses = false; - } catch (e) { - this.loadingStatuses = false; - } - } - - private async getPriorities() { - try { - this.loadingPriorities = true; - const res = await this.prioritiesApi.get(); - if (res.done) - this.service.priorities = res.body; - this.loadingPriorities = false; - } catch (e) { - this.loadingPriorities = false; - } - } - - private async getCategories() { - try { - this.loadingCategories = true; - const res = await this.statusesApi.getCategories(); - if (res.done) - this.categories = res.body; - this.loadingCategories = false; - } catch (e) { - this.loadingCategories = false; - } - } - - private async getPhases() { - if (!this.projectId) return; - try { - const res = await this.phasesApi.get(this.projectId); - if (res.done) - this.service.phases = res.body; - } catch (e) { - } - } - - handleCancel() { - if (this._show) { - this._show = false; - this.showChange.emit(this._show); - } - } - - onVisibilityChange(visible: boolean) { - if (visible) { - this.service.setCurrentGroup(this.service.GROUP_BY_OPTIONS[0]); - setTimeout(() => this.init(), this.DRAWER_CLOSE_TIME); - } - } - - getColor(name?: string) { - return AvatarNamesMap[name?.charAt(0).toUpperCase() || 'A']; - } - - protected trackById(index: number, item: any) { - return item.id; - } - - protected openTask(task: IProjectTask) { - if(task.project_id) this.projectService.id = task.project_id; - this.onOpenTask.emit(task) - this.cdr.markForCheck(); - } - - async toggleCollapse(group: IMemberTaskListGroup | IProjectTask) { - if (this.isTaskListGroup(group)) { - group.isExpand = !group.isExpand; - } - this.cdr.detectChanges(); - } - - isTaskListGroup(group: IMemberTaskListGroup | IProjectTask): group is IMemberTaskListGroup { - return (group as IMemberTaskListGroup).tasks !== undefined; - } - - private toggleFocusCls(focused: boolean, element: HTMLElement) { - if (focused) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - } - - changeGroup(item: IGroupByOption) { - this.service.setCurrentGroup(item); - this.init(); - } - - protected handleFocusChange(focused: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - this.toggleFocusCls(focused, element); - }); - } - - unuseFunc(e: any, row: any, group: any) { - return; - } -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/start-date/start-date.component.html b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/start-date/start-date.component.html deleted file mode 100644 index 1c125973..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/start-date/start-date.component.html +++ /dev/null @@ -1,17 +0,0 @@ -
- - - - {{task.start_date | dateFormatter}} - - -
diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/start-date/start-date.component.scss b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/start-date/start-date.component.scss deleted file mode 100644 index 17785765..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/start-date/start-date.component.scss +++ /dev/null @@ -1,28 +0,0 @@ -nz-date-picker { - max-width: 150px; - border-color: transparent !important; - top: 0; - bottom: 0; - left: 0; - right: 0; - - &:hover { - border-color: #d9d9d9 !important; - } -} - -.editable { - position: relative; -} - -.date-text { - display: flex; - align-items: center; - padding-left: 15px; -} - -.def-g-height { - max-height: 32px; - min-height: 32px; - line-height: 32px; -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/start-date/start-date.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/start-date/start-date.component.spec.ts deleted file mode 100644 index f6df4a45..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/start-date/start-date.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { StartDateComponent } from './start-date.component'; - -describe('StartDateComponent', () => { - let component: StartDateComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [StartDateComponent] - }); - fixture = TestBed.createComponent(StartDateComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/start-date/start-date.component.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/start-date/start-date.component.ts deleted file mode 100644 index 5b2fdd7f..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/start-date/start-date.component.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - HostBinding, - Input, - NgZone, - OnDestroy, - OnInit, - Renderer2 -} from '@angular/core'; -import {Socket} from "ngx-socket-io"; -import {AuthService} from "@services/auth.service"; -import {ScheduleMemberTasksService} from "../../service/schedule-member-tasks-service.service"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {SocketEvents} from "@shared/socket-events"; -import {formatGanttDate} from "@shared/utils"; - -@Component({ - selector: 'worklenz-schedule-start-date', - templateUrl: './start-date.component.html', - styleUrls: ['./start-date.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class StartDateComponent implements OnInit, OnDestroy { - @Input({required: true}) task: IProjectTask | null = null; - @HostBinding("class") cls = "flex-row task-due-date"; - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly service: ScheduleMemberTasksService, - private readonly renderer: Renderer2, - private readonly auth: AuthService, - ) { - } - - ngOnInit() { - this.socket.on(SocketEvents.TASK_START_DATE_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_START_DATE_CHANGE.toString(), this.handleResponse); - } - - private handleResponse = (response: { - id: string; - parent_task: string | null; - start_date: string; - }) => { - if (!this.task) return; - if (response.id === this.task.id && this.task.start_date !== response.start_date) { - this.task.start_date = response.start_date; - this.cdr.markForCheck(); - } - }; - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - handleStartDateChange(date: string, task: IProjectTask) { - this.socket.emit( - SocketEvents.TASK_START_DATE_CHANGE.toString(), JSON.stringify({ - task_id: task.id, - start_date: formatGanttDate(date) || null, - parent_task: task.parent_task_id, - time_zone: this.auth.getCurrentSession()?.timezone_name ? this.auth.getCurrentSession()?.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone - })); - } - -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/status/status.component.html b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/status/status.component.html deleted file mode 100644 index 3610373e..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/status/status.component.html +++ /dev/null @@ -1,14 +0,0 @@ -
- - - - - -
diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/status/status.component.scss b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/status/status.component.scss deleted file mode 100644 index dd984827..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/status/status.component.scss +++ /dev/null @@ -1,5 +0,0 @@ -nz-select { - max-width: 96px; - width: 100%; - text-overflow: ellipsis; -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/status/status.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/status/status.component.spec.ts deleted file mode 100644 index 1fcecc44..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/status/status.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { StatusComponent } from './status.component'; - -describe('StatusComponent', () => { - let component: StatusComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [StatusComponent] - }); - fixture = TestBed.createComponent(StatusComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/status/status.component.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/status/status.component.ts deleted file mode 100644 index d5f9bb4e..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/status/status.component.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostBinding, - Input, - NgZone, - OnDestroy, - OnInit, - Renderer2 -} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {ScheduleMemberTasksService} from "../../service/schedule-member-tasks-service.service"; -import {Socket} from "ngx-socket-io"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskListStatusChangeResponse} from "@interfaces/task-list-status-change-response"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-schedule-status', - templateUrl: './status.component.html', - styleUrls: ['./status.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class StatusComponent implements OnInit, OnDestroy { - @Input({required: true}) task: IProjectTask = {}; - @HostBinding("class") cls = "flex-row task-status"; - - statuses: ITaskStatusViewModel[] = []; - loading = false; - - constructor( - private readonly service: ScheduleMemberTasksService, - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly element: ElementRef, - private readonly renderer: Renderer2, - private readonly auth: AuthService, - ) { - this.service.onStatusesChange$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.updateStatuses(); - this.cdr.markForCheck(); - }); - } - - ngOnInit() { - this.updateStatuses(); - this.socket.on(SocketEvents.TASK_STATUS_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_STATUS_CHANGE.toString(), this.handleResponse); - } - - private getTaskProgress(taskId: string) { - this.socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), taskId); - } - - private isGroupByStatus() { - return this.service.getCurrentGroup().value === this.service.GROUP_BY_STATUS_VALUE; - } - - trackById(index: number, item: ITaskStatusViewModel) { - return item.id; - } - - handleStatusChange(statusId: string, taskId?: string) { - if (!taskId) return; - - this.socket.emit(SocketEvents.TASK_STATUS_CHANGE.toString(), JSON.stringify({ - task_id: taskId, - status_id: statusId, - parent_task: this.task.parent_task_id, -team_id: this.auth.getCurrentSession()?.team_id - })); - - this.getTaskProgress(taskId); - } - - private handleResponse = (response: ITaskListStatusChangeResponse) => { - if (response && response.id === this.task.id) { - this.task.status_color = response.color_code; - this.task.complete_ratio = +response.complete_ratio || 0; - this.task.status = response.status_id; - this.task.status_category = response.statusCategory; - - if (this.isGroupByStatus()) { - this.service.updateTaskGroup(this.task, false); - if (this.service.isSubtasksIncluded) { - this.service.emitRefreshSubtasksIncluded(); - } - } - this.cdr.markForCheck(); - } - } - - private updateStatuses() { - this.statuses = this.service.statuses; - } - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - handleOpen(open: boolean) { - this.toggleHighlightCls(open, this.element.nativeElement); - } - - -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-input/task-add-input.component.html b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-input/task-add-input.component.html deleted file mode 100644 index f9332b79..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-input/task-add-input.component.html +++ /dev/null @@ -1,21 +0,0 @@ -
-
- -
-
- - {{label}} -
-
diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-input/task-add-input.component.scss b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-input/task-add-input.component.scss deleted file mode 100644 index 0723b13e..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-input/task-add-input.component.scss +++ /dev/null @@ -1,25 +0,0 @@ -.input-icon { - font-size: 11px; -} - -input { - background: white; - border: 1px solid #40a9ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); -} - -.task-name { - border: 1px solid transparent; - - &:hover { - cursor: text; - background: #fff; - border: 1px solid #bfbfbf; - border-radius: 4px; - } -} - -.name-input-container { - max-width: 300px; - min-width: 300px; -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-input/task-add-input.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-input/task-add-input.component.spec.ts deleted file mode 100644 index f9910f9f..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-input/task-add-input.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskAddInputComponent } from './task-add-input.component'; - -describe('TaskAddInputComponent', () => { - let component: TaskAddInputComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskAddInputComponent] - }); - fixture = TestBed.createComponent(TaskAddInputComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-input/task-add-input.component.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-input/task-add-input.component.ts deleted file mode 100644 index 82595573..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-input/task-add-input.component.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - Input, - NgZone, - Output, - ViewChild -} from '@angular/core'; -import {FormBuilder, FormGroup, Validators} from "@angular/forms"; -import {smallId} from "@shared/utils"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {Socket} from "ngx-socket-io"; -import {AuthService} from "@services/auth.service"; -import {ITaskCreateRequest} from "@interfaces/api-models/task-create-request"; -import {SocketEvents} from "@shared/socket-events"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {DRAWER_ANIMATION_INTERVAL} from "@shared/constants"; -import {ProjectScheduleService} from "../../service/project-schedule-service.service"; -import {ITaskAssigneesUpdateResponse} from "@interfaces/task-assignee-update-response"; - -@Component({ - selector: 'worklenz-task-add-input', - templateUrl: './task-add-input.component.html', - styleUrls: ['./task-add-input.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskAddInputComponent { - @ViewChild('taskInput', {static: false}) taskInput!: ElementRef; - readonly form!: FormGroup; - - @Input({required: true}) projectId: string | null = null; - @Input({required: true}) teamMemberId: string | null = null; - @Input({required: true}) chartStart: string | null = null; - @Input() label = "Add Task"; - - @Output() focusChange: EventEmitter = new EventEmitter(); - - taskInputVisible = false; - creating = false; - - protected readonly id = smallId(4); - - private readonly _session: ILocalSession | null = null; - - constructor( - private readonly socket: Socket, - private readonly auth: AuthService, - private readonly fb: FormBuilder, - private readonly ngZone: NgZone, - private readonly cdr: ChangeDetectorRef, - private readonly service: ProjectScheduleService - ) { - this.form = this.fb.group({ - name: [null, [Validators.required, Validators.pattern(/^(\s+\S+\s*)*(?!\s).*$/)]] - }); - this._session = this.auth.getCurrentSession(); - } - - focusTaskInput() { - this.taskInputVisible = true; - this.focusChange.emit(this.taskInputVisible); - - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.taskInput?.nativeElement.focus(); - this.taskInput?.nativeElement.select(); - }, 100); // wait for the animation end - }); - } - - addTaskInputBlur() { - this.taskInputVisible = false; - this.focusChange.emit(this.taskInputVisible); - } - - async onInputBlur() { - if (this.isValidInput()) { - await this.addInstantTask(); - return; - } - this.addTaskInputBlur(); - } - - private createRequest() { - if (!this.projectId || !this._session) return null; - const sess = this._session; - const body: ITaskCreateRequest = { - name: this.form.value.name, - project_id: this.projectId, - reporter_id: sess.id, - team_id: sess.team_id, - chart_start: this.chartStart ? this.chartStart : '' - }; - return body; - } - - private isValidInput() { - return this.form.valid && this.form.value.name.trim().length; - } - - async addInstantTask() { - if (this.creating) return; - if (!this.projectId || !this._session) return; - if (this.isValidInput()) { - try { - const req = this.createRequest(); - if (!req) return; - this.creating = true; - this.socket.emit(SocketEvents.QUICK_TASK.toString(), JSON.stringify(req)); - this.socket.once(SocketEvents.QUICK_TASK.toString(), (task: IProjectTask) => { - this.creating = false; - this.onNewTaskReceived(task); - }); - } catch (e) { - this.creating = false; - } - this.cdr.markForCheck(); - } - } - - public reset(scroll = true) { - this.creating = false; - this.form.controls["name"].setValue(null); - this.taskInputVisible = true; - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.taskInput?.nativeElement.focus(); - if (scroll) - window.scrollTo(0, document.body.scrollHeight); - }, DRAWER_ANIMATION_INTERVAL); // wait for the animation end - }); - this.cdr.markForCheck(); - } - - private onNewTaskReceived(task: IProjectTask) { - if (!task || !this._session || !this.projectId || !this.teamMemberId) return; - - const body = { - team_member_id: this.teamMemberId, - project_id: this.projectId, - task_id: task.id, - reporter_id: this._session.id, - mode: 0, - parent_task: task.parent_task_id - }; - - this.socket.emit(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), JSON.stringify(body)); - this.socket.once(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), (response: ITaskAssigneesUpdateResponse) => { - this.cdr.markForCheck(); - }) - this.reset(false); - } -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-row/task-add-row.component.html b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-row/task-add-row.component.html deleted file mode 100644 index a80ebc9f..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-row/task-add-row.component.html +++ /dev/null @@ -1,3 +0,0 @@ -
-
-
diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-row/task-add-row.component.scss b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-row/task-add-row.component.scss deleted file mode 100644 index c389b15c..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-row/task-add-row.component.scss +++ /dev/null @@ -1,14 +0,0 @@ -.indicator { - height: 30px; - background: rgb(217, 227, 238); - border: 1px solid rgb(173, 181, 189); - border-radius: 4px; - position: absolute; - z-index: 9; - transition: 0.15s all; -} - -.h-default { - min-height: 45px; - max-height: 45px; -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-row/task-add-row.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-row/task-add-row.component.spec.ts deleted file mode 100644 index 748f311d..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-row/task-add-row.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskAddRowComponent } from './task-add-row.component'; - -describe('TaskAddRowComponent', () => { - let component: TaskAddRowComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskAddRowComponent] - }); - fixture = TestBed.createComponent(TaskAddRowComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-row/task-add-row.component.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-row/task-add-row.component.ts deleted file mode 100644 index 69e7d66f..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-add-row/task-add-row.component.ts +++ /dev/null @@ -1,76 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {Socket} from "ngx-socket-io"; -import {AuthService} from "@services/auth.service"; -import {SocketEvents} from "@shared/socket-events"; -import {log_error} from "@shared/utils"; -import {ITaskCreateRequest} from "@interfaces/api-models/task-create-request"; -import {DEFAULT_TASK_NAME} from "@shared/constants"; -import {ITaskAssigneesUpdateResponse} from "@interfaces/task-assignee-update-response"; -import {ProjectScheduleService} from "../../service/project-schedule-service.service"; - -@Component({ - selector: 'worklenz-task-add-row', - templateUrl: './task-add-row.component.html', - styleUrls: ['./task-add-row.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskAddRowComponent { - // @Input({required: true}) projectId: string | null = null; - // @Input({required: true}) teamMemberId: string | null = null; - // @Input({required: true}) chartStart: string | null = null; - // @Output() openTask = new EventEmitter(); - - left: number = 0; - width: number = 0; - - private readonly _session: ILocalSession | null = null; - - constructor( - private readonly socket: Socket, - private readonly auth: AuthService, - private readonly cdr: ChangeDetectorRef, - private readonly service: ProjectScheduleService, - ) { - this._session = this.auth.getCurrentSession(); - } - - onDragStart(event: MouseEvent) { - - this.left = event.offsetX - (event.offsetX % 35); - const pageX = event.pageX; - const onMouseMove = (e: MouseEvent) => { - const deltaX = e.pageX - pageX; - requestAnimationFrame(() => { - this.width = deltaX; - this.service.highlighterWidth = this.width; - this.service.highlighterLeft = this.left; - this.cdr.markForCheck(); - }); - }; - const onMouseUp = (event: MouseEvent) => { - document.removeEventListener('mousemove', onMouseMove); - document.removeEventListener('mouseup', onMouseUp); - if (pageX > event.pageX) { - this.width = 0; - this.left = 0; - this.cdr.markForCheck(); - return; - } - requestAnimationFrame(() => { - this.width = this.width + (35 - (this.width % 35)); - this.service.highlighterWidth = this.width; - this.service.highlighterLeft = this.left; - this.cdr.markForCheck(); - }) - }; - document.addEventListener('mousemove', onMouseMove); - document.addEventListener('mouseup', onMouseUp); - } - reset() { - this.left = 0; - this.width = 0; - } - -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-header/task-list-header.component.html b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-header/task-list-header.component.html deleted file mode 100644 index 6dee69f9..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-header/task-list-header.component.html +++ /dev/null @@ -1,36 +0,0 @@ - - -
- -
- - -
Task
- - - -
Status
-
- - - -
Priority
-
- - - -
- {{phaseLabel | ellipsis : 10}} -
-
- - - -
Start Date
-
- - - -
Due Date
-
diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-header/task-list-header.component.scss b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-header/task-list-header.component.scss deleted file mode 100644 index 8c01e7a9..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-header/task-list-header.component.scss +++ /dev/null @@ -1,60 +0,0 @@ -.flex-row { - padding: 4px 11px; - background-color: #fafafa; - border-top: 1px solid #f0f0f0; - border-bottom: 1px solid #f0f0f0; - border-right: 1px solid #f0f0f0; - display: flex; - align-items: center; - flex-direction: row; -} - -.task-check { - text-align: center; - padding: 8px 6px 8px 6px !important; - border-left: 1px solid #f0f0f0; - position: sticky; - left: 0px; - z-index: 1; -} - -.task-name { - width: 450px; - min-width: 450px; - position: sticky; - left: 29px; - z-index: 1; - - nz-filter-trigger { - margin-left: auto; - } - - &.left-0 { - left: 47px; - } - -} - -.task-status { - width: 120px; - min-width: 120px; -} - -.task-phase { - width: 150px; - min-width: 150px; -} - -.task-priority { - width: 120px; - min-width: 120px; -} - -.task-start-date, .task-due-date { - width: 150px; - min-width: 150px; -} - -.visibility-hidden { - visibility: hidden; -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-header/task-list-header.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-header/task-list-header.component.spec.ts deleted file mode 100644 index d6f4108c..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-header/task-list-header.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskListHeaderComponent } from './task-list-header.component'; - -describe('TaskListHeaderComponent', () => { - let component: TaskListHeaderComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskListHeaderComponent] - }); - fixture = TestBed.createComponent(TaskListHeaderComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-header/task-list-header.component.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-header/task-list-header.component.ts deleted file mode 100644 index 17a5a44a..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-header/task-list-header.component.ts +++ /dev/null @@ -1,53 +0,0 @@ -import {ChangeDetectorRef, Component, EventEmitter, HostBinding, Input, Output} from '@angular/core'; -import {TaskListV2Service} from "../../../../../modules/task-list-v2/task-list-v2.service"; -import {ProjectPhasesService} from "@services/project-phases.service"; -import {AuthService} from "@services/auth.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {merge} from "rxjs"; -import {ScheduleMemberTasksHashmapService} from "../../service/schedule-member-tasks-hashmap-service.service"; - -@Component({ - selector: 'worklenz-schedule-task-list-header', - templateUrl: './task-list-header.component.html', - styleUrls: ['./task-list-header.component.scss'] -}) -export class TaskListHeaderComponent { -@HostBinding("class") headerCls = "d-flex header mb-0 flex-row"; - - @Input() groupId!: string; - @Output() selectChange = new EventEmitter(); - - checked = false; - indeterminate = false; - - get phaseLabel() { - return this.phasesService.label; - } - - constructor( - public readonly service: TaskListV2Service, - private readonly map: ScheduleMemberTasksHashmapService, - private readonly cdr: ChangeDetectorRef, - private readonly phasesService: ProjectPhasesService, - public readonly auth: AuthService - ) { - this.map.onDeselectAll$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.checked = false; - this.indeterminate = false; - this.cdr.markForCheck(); - }); - - this.phasesService.onLabelChange - .pipe(takeUntilDestroyed()) - .subscribe(value => { - this.cdr.markForCheck(); - }); - - } - - onAllChecked(checked: boolean) { - this.selectChange?.emit(checked); - } -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-row/task-list-row.component.html b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-row/task-list-row.component.html deleted file mode 100644 index 00f585f2..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-row/task-list-row.component.html +++ /dev/null @@ -1,83 +0,0 @@ -
- -
- -
- -
-
-
- -
-
-
- - - - {{ task.name }}   -
-
- - -
- - - {{task.sub_tasks_count}} - - - -
-
-
- - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-row/task-list-row.component.scss b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-row/task-list-row.component.scss deleted file mode 100644 index c33e6079..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-row/task-list-row.component.scss +++ /dev/null @@ -1,232 +0,0 @@ -.hidden-arrow { - display: none !important; -} - -.dropdown-highlight:hover { - background-color: rgba(208, 238, 250, 0.33); - border: #5587f5 1px solid; - border-radius: 3px; -} - -.plus-icon { - display: none; - position: absolute; - right: 0; - z-index: 1; - top: 0; - bottom: 0; - height: 100%; -} - -.expanded { - transform: rotate(-90deg); -} - -.sub-tasks-arrow { - position: relative; - cursor: pointer; - left: 3px; - width: 16px; - padding: 2px; - border: 1px solid transparent; - z-index: 1; - - .sub-arrow { - width: 10px; - height: 10px; - color: #191919; - margin-left: -2px; - } -} - -.task-name-text { - border: 1px solid transparent; - padding-left: 2px; - border-radius: 4px; - - &:hover { - border: 1px solid #d9d9d9; - } -} - -.task-name { - border: 1px solid transparent; - - &:hover { - cursor: text; - background: #fff; - border-radius: 4px; - } -} - -.highlight-col { - border: 1px solid #1890ff !important; - - nz-date-picker { - box-shadow: none; - } -} - -.editable { - .add-button { - visibility: hidden; - } - - &:hover { - .add-button { - visibility: visible; - } - } -} - -.ant-popover { - width: 500px; -} - -.flex-table { - display: flex; -} - -.rows { - .flex-row { - padding: 3px 12px; - border-bottom: 1px solid #f0f0f0; - border-right: 1px solid #f0f0f0; - background: white; - display: flex; - align-items: center; - max-height: 50px; - border-radius: 0px; - } - - &:hover .flex-row { - background: #f8f7f9; - } -} - -.subtask { - .flex-row { - background: #fcfcfc; - } -} - -.task-check { - text-align: center; - padding: 8px 6px 8px 6px !important; - border-left: 1px solid #f0f0f0; - position: sticky; - z-index: 1; - left: 0px; -} - -.task-arrow { - width: 20px; - min-width: 20px; - padding: 8px 11px 8px 2px !important; - border-right: none !important; - position: sticky; - z-index: 1; - left: 29px; - - &.highlight-col { - border-top: 1px solid #188fff !important; - border-left: 1px solid #188fff !important; - border-bottom: 1px solid #188fff !important; - } -} - -.task-name { - width: 450px; - min-width: 450px; - position: sticky; - z-index: 1; - border-radius: 0px; - padding-right: 25px; - left: 29px; - - &.highlight-col { - border: 1px solid #188fff !important; - } - -} - -.task-status { - width: 120px; - min-width: 120px; -} - -.task-phase { - width: 150px; - min-width: 150px; -} - -.task-priority { - width: 120px; - min-width: 120px; -} - -.task-start-date, .task-due-date { - width: 150px; - min-width: 150px; -} - -.task-due-date { - padding: 0px 0px !important; - - .editable { - align-items: center; - display: flex; - } -} - -.task-name-text { - width: 100%; - -webkit-line-clamp: 1; - display: -webkit-box; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; -} - -.inner-icon-cont { - width: max-content; - display: flex; - justify-content: flex-end; - align-items: center; - column-gap: 4px; -} - -.name-input { - padding: 5px 12px; - border-left: 1px solid transparent; -} - -.double-arrow { - line-height: 16px; - border: none; - cursor: pointer; -} - -.task-placeholder { - width: 100%; - height: 42px; - border: 1px dashed #d9d9d9; - background: #fafafa; -} - -.task-open-btn { - opacity: 0; - position: absolute; - right: 0; - top: 4px; - background: whitesmoke; - transition: 0.25s all; -} - -.task-name { - &:hover { - & .task-open-btn { - opacity: 1; - } - } -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-row/task-list-row.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-row/task-list-row.component.spec.ts deleted file mode 100644 index 44f81ddd..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-row/task-list-row.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskListRowComponent } from './task-list-row.component'; - -describe('TaskListRowComponent', () => { - let component: TaskListRowComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskListRowComponent] - }); - fixture = TestBed.createComponent(TaskListRowComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-row/task-list-row.component.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-row/task-list-row.component.ts deleted file mode 100644 index bfa22ef6..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-list-row/task-list-row.component.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - HostBinding, - HostListener, - Input, - NgZone, - OnDestroy, - OnInit, - Output, - Renderer2 -} from '@angular/core'; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ScheduleMemberTasksHashmapService} from "../../service/schedule-member-tasks-hashmap-service.service"; -import {ScheduleMemberTasksService} from "../../service/schedule-member-tasks-service.service"; -import {Socket} from "ngx-socket-io"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; -import {UtilsService} from "@services/utils.service"; -import {filter, merge} from "rxjs"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskListStatusChangeResponse} from "@interfaces/task-list-status-change-response"; - -@Component({ - selector: 'worklenz-schedule-task-list-row', - templateUrl: './task-list-row.component.html', - styleUrls: ['./task-list-row.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListRowComponent implements OnInit, OnDestroy { - @Input({required: true}) task!: IProjectTask; - @Output() onShowSubTasks = new EventEmitter(); - @Output() onOpenTask = new EventEmitter(); - @HostBinding("class") cls = "position-relative task-row"; - - private readonly highlight = 'highlight-col'; - - protected editId: string | null = null; - - protected selected = false; - - protected readonly Number = Number; - - public get id() { - return this.task.id; - } - - constructor( - private readonly element: ElementRef, - private readonly renderer: Renderer2, - public readonly service: ScheduleMemberTasksService, - private readonly cdr: ChangeDetectorRef, - private readonly socket: Socket, - private readonly map: ScheduleMemberTasksHashmapService, - private readonly ngZone: NgZone, - private readonly view: TaskViewService, - public readonly utils: UtilsService - ) { - merge( - this.map.onSelect$.pipe( - filter(value => value.id === this.id), - filter(() => !this.selected) - ), - this.map.onDeselect$.pipe( - filter(value => value.id === this.id), - filter(() => this.selected) - ), - this.map.onDeselectAll$.pipe( - filter(() => this.selected) - ) - ).pipe( - takeUntilDestroyed() - ).subscribe(value => { - this.selected = !this.selected; - this.toggleSelection(); - this.markForCheck(); - }); - } - - private toggleSelection() { - this.ngZone.runOutsideAngular(() => { - const cls = "selected"; - const ele = this.element.nativeElement; - - if (this.selected) { - this.renderer.addClass(ele, cls); - } else { - this.renderer.removeClass(ele, cls); - } - }); - } - - ngOnInit() { - this.registerSocketEvents(); - } - - ngOnDestroy() { - this.unregisterSocketEvents(); - } - - private registerSocketEvents() { - this.socket.on(SocketEvents.TASK_NAME_CHANGE.toString(), this.handleNameChangeResponse); - } - - private unregisterSocketEvents() { - this.socket.removeListener(SocketEvents.TASK_NAME_CHANGE.toString(), this.handleNameChangeResponse); - } - - @HostListener("contextmenu", ["$event"]) - private onContextMenu(event: MouseEvent) { - this.service.emitOnContextMenu(event, this.task); - } - - focus(tr: HTMLDivElement) { - setTimeout(() => { - const element = tr.querySelector("input"); - element?.focus(); - }); - } - - onCheckChange(checked: boolean) { - if (checked) { - this.map.selectTask(this.task); - } else { - this.map.deselectTask(this.task); - } - - this.toggleSelection(); - } - - openSubTasks() { - this.onShowSubTasks?.emit(this.task); - } - - openTask(task: IProjectTask) { - this.onOpenTask?.emit(task); - } - - selectCol(element: HTMLDivElement) { - if (element.classList.contains(this.highlight)) return; - element.classList.add(this.highlight); - } - - deselectCol(element: HTMLDivElement) { - element.classList.remove(this.highlight); - this.editId = null; - } - - handleNameChange(data?: IProjectTask) { - if (!data) return; - this.socket.emit(SocketEvents.TASK_NAME_CHANGE.toString(), JSON.stringify({ - task_id: data.id, - name: data.name, - parent_task: this.task.parent_task_id - })); - this.editId = null; - } - - onTaskNameClick(event: MouseEvent, tr1: HTMLDivElement, task: IProjectTask) { - event.stopPropagation(); - this.focus(tr1); - this.editId = task.id || null; - } - - public markForCheck() { - this.cdr.markForCheck(); - } - - public detectChanges() { - this.cdr.detectChanges(); - } - - private handleNameChangeResponse = (response: { id: string; parent_task: string; name: string; }) => { - if (!response) return; - if (this.id !== response.id) return; - - if (this.task && this.task.name != response.name) { - this.task.name = response.name; - this.markForCheck(); - } - }; - - private handleCompletedAt = (response: ITaskListStatusChangeResponse) => { - if (!response.id) return; - if (this.id !== response.id) return; - this.task.completed_at = response.completed_at; - } - - -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-name/task-name.component.html b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-name/task-name.component.html deleted file mode 100644 index a214a45c..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-name/task-name.component.html +++ /dev/null @@ -1,15 +0,0 @@ -
-
-
- {{task.task_name}} -
-
-
- -
- -
diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-name/task-name.component.scss b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-name/task-name.component.scss deleted file mode 100644 index c74ef393..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-name/task-name.component.scss +++ /dev/null @@ -1,47 +0,0 @@ -input { - padding-left: 4px; - padding-right: 0px; -} - -.border-input { - border: 1px solid transparent; - border-radius: 4px; - transition: 0.25s all; - padding-left: 4px; - max-width: 260px; - - &:hover { - border: 1px solid #d9d9d9; - } -} - -.task-name-block, .name-holder, .border-input { - max-height: 32px; - min-height: 32px; - line-height: 32px; -} - -.ellipsis { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - - -.task-open-btn { - opacity: 0; - position: absolute; - right: 0; - top: 0; - background: whitesmoke; - transition: 0.25s all; -} - -.task-name-block { - position: relative; - &:hover { - & .task-open-btn { - opacity: 1; - } - } -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-name/task-name.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-name/task-name.component.spec.ts deleted file mode 100644 index 0ffb2281..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-name/task-name.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskNameComponent } from './task-name.component'; - -describe('TaskNameComponent', () => { - let component: TaskNameComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskNameComponent] - }); - fixture = TestBed.createComponent(TaskNameComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-name/task-name.component.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-name/task-name.component.ts deleted file mode 100644 index 1006da25..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/task-name/task-name.component.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - Input, OnDestroy, OnInit, - Output, - ViewChild -} from '@angular/core'; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {IScheduleMemberTask} from "@interfaces/schedular"; - -@Component({ - selector: 'worklenz-task-name', - templateUrl: './task-name.component.html', - styleUrls: ['./task-name.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskNameComponent implements OnInit, OnDestroy{ - @ViewChild('input') input!: ElementRef; - @Input({required: true}) task: IScheduleMemberTask | null = null; - @Output() openTask = new EventEmitter(); - - initialTaskName: string | null = null; - - isEditing = false; - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - ) { - } - - ngOnInit() { - if (this.task?.task_name) this.initialTaskName = this.task.task_name; - this.socket.on(SocketEvents.TASK_NAME_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.TASK_NAME_CHANGE.toString(), this.handleResponse); - } - - enableEdit() { - this.isEditing = true; - setTimeout(() => { - if (this.input) { - this.input.nativeElement.focus(); - } - }, 250); - } - - validateName() { - if (this.task && this.task.task_name.trim() === '') { - this.task.task_name = this.initialTaskName as string; - this.isEditing = false; - this.cdr.markForCheck(); - return; - } else { - this.changeName(); - } - } - - changeName() { - if (!this.task) return; - this.socket.emit( - SocketEvents.TASK_NAME_CHANGE.toString(), JSON.stringify({ - task_id: this.task.task_id, - name: this.task.task_name, - parent_task: null, - })); - this.isEditing = false; - } - - private handleResponse = (response: { id: string; parent_task: string; name: string; }) => { - if (!response) return; - if (this.task?.task_id !== response.id) return; - - if (this.task && this.task.task_name != response.name) { - this.task.task_name = response.name; - this.cdr.markForCheck(); - } - }; -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/tasks-context-menu/tasks-context-menu.component.html b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/tasks-context-menu/tasks-context-menu.component.html deleted file mode 100644 index 69f74d37..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/tasks-context-menu/tasks-context-menu.component.html +++ /dev/null @@ -1,28 +0,0 @@ - -
    - -
  • - - Remove from member -
  • - - -
  • -
      -
    • - - - {{item?.name || null}} - -
    • -
    -
  • -
-
- - - Move to - diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/tasks-context-menu/tasks-context-menu.component.scss b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/tasks-context-menu/tasks-context-menu.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/tasks-context-menu/tasks-context-menu.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/tasks-context-menu/tasks-context-menu.component.spec.ts deleted file mode 100644 index dbd08441..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/tasks-context-menu/tasks-context-menu.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TasksContextMenuComponent } from './tasks-context-menu.component'; - -describe('TasksContextMenuComponent', () => { - let component: TasksContextMenuComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TasksContextMenuComponent] - }); - fixture = TestBed.createComponent(TasksContextMenuComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/tasks-context-menu/tasks-context-menu.component.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/tasks-context-menu/tasks-context-menu.component.ts deleted file mode 100644 index 223894e1..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/components/tasks-context-menu/tasks-context-menu.component.ts +++ /dev/null @@ -1,151 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, ViewChild} from '@angular/core'; -import {NzContextMenuService, NzDropdownMenuComponent} from "ng-zorro-antd/dropdown"; -import {IMemberTaskListGroup} from "@interfaces/schedular"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {smallId} from "@shared/utils"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {Subject, takeUntil} from "rxjs"; -import {ScheduleMemberTasksHashmapService} from "../../service/schedule-member-tasks-hashmap-service.service"; -import {ScheduleMemberTasksService} from "../../service/schedule-member-tasks-service.service"; -import {Socket} from "ngx-socket-io"; -import {AuthService} from "@services/auth.service"; -import {ITaskListContextMenuEvent} from "../../../../../modules/task-list-v2/interfaces"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskAssigneesUpdateResponse} from "@interfaces/task-assignee-update-response"; - -@Component({ - selector: 'worklenz-schedule-tasks-context-menu', - templateUrl: './tasks-context-menu.component.html', - styleUrls: ['./tasks-context-menu.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TasksContextMenuComponent { - @ViewChild('contextMenuDropdown', {static: false}) contextMenuDropdown!: NzDropdownMenuComponent; - @Input({required: true}) projectId: string | null = null; - @Input({required: true}) teamMemberId: string | null = null; - @Input() groups: IMemberTaskListGroup[] = []; - - protected removing = false; - - selectedTask: IProjectTask | null = null; - - protected readonly id = smallId(4); - - private readonly _session: ILocalSession | null = null; - - get profile() { - return this.auth.getCurrentSession(); - } - - private readonly destroy$ = new Subject(); - - constructor( - private readonly contextMenuService: NzContextMenuService, - private readonly service: ScheduleMemberTasksService, - private readonly map: ScheduleMemberTasksHashmapService, - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly auth: AuthService, - ) { - this.service.onContextMenu$ - .pipe(takeUntil(this.destroy$)) - .subscribe(value => { - this.onContextMenu(value); - }); - this._session = this.auth.getCurrentSession(); - } - - private onContextMenu(value: ITaskListContextMenuEvent) { - this.selectedTask = value.task; - this.map.deselectAll(); - this.map.selectTask(value.task); - this.cdr.detectChanges(); - this.contextMenuService.create(value.event, this.contextMenuDropdown); - } - - removeMember() { - if (!this._session || !this.selectedTask || !this.teamMemberId) return - const body = { - team_member_id: this.teamMemberId, - project_id: this.projectId, - task_id: this.selectedTask.id, - reporter_id: this._session.id, - mode: 1, - parent_task: this.selectedTask.parent_task_id - }; - this.socket.emit(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), JSON.stringify(body)); - this.socket.once(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), () => { - this.service.emitRemoveMembersTask(body.task_id as string); - }) - } - - handleMemberChangeResponse = (response: ITaskAssigneesUpdateResponse) => { - try { - if (response && response.id) { - this.service.deleteTask(response.id) - this.cdr.markForCheck(); - } - } catch (e) { - // ignore - } - } - - changeGroup(toGroupId: string) { - if (!this.selectedTask) return; - const groupBy = this.service.getCurrentGroup(); - if (groupBy.value === this.service.GROUP_BY_STATUS_VALUE) { - this.handleStatusChange(toGroupId, this.selectedTask.id); - } else if (groupBy.value === this.service.GROUP_BY_PRIORITY_VALUE) { - this.handlePriorityChange(toGroupId, this.selectedTask.id); - } else if (groupBy.value === this.service.GROUP_BY_PHASE_VALUE) { - this.handlePhaseChange(toGroupId, this.selectedTask.id); - } - } - - handleStatusChange(statusId: string, taskId?: string) { - if (!taskId) return; - this.socket.emit(SocketEvents.TASK_STATUS_CHANGE.toString(), JSON.stringify({ - task_id: taskId, - status_id: statusId, -team_id: this.auth.getCurrentSession()?.team_id - })); - if (this.service.getCurrentGroup().value === this.service.GROUP_BY_STATUS_VALUE && this.selectedTask) { - this.service.updateTaskGroup(this.selectedTask, false); - if (this.service.isSubtasksIncluded) { - this.service.emitRefreshSubtasksIncluded(); - } - } - this.cdr.markForCheck(); - } - - handlePriorityChange(priorityId: string, taskId?: string) { - if (!taskId) return; - this.socket.emit(SocketEvents.TASK_PRIORITY_CHANGE.toString(), JSON.stringify({ - task_id: taskId, - priority_id: priorityId - })); - if (this.service.getCurrentGroup().value === this.service.GROUP_BY_PRIORITY_VALUE && this.selectedTask) { - this.service.updateTaskGroup(this.selectedTask, false); - if (this.service.isSubtasksIncluded) { - this.service.emitRefreshSubtasksIncluded(); - } - } - this.cdr.markForCheck(); - } - - handlePhaseChange(phaseId: string, taskId?: string) { - if (!taskId) return; - this.socket.emit(SocketEvents.TASK_PHASE_CHANGE.toString(), { - task_id: taskId, - phase_id: phaseId - }); - if (this.service.getCurrentGroup().value === this.service.GROUP_BY_PHASE_VALUE && this.selectedTask) { - this.service.updateTaskGroup(this.selectedTask, false); - if (this.service.isSubtasksIncluded) { - this.service.emitRefreshSubtasksIncluded(); - } - } - this.cdr.markForCheck(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/projects-schedule.component.html b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/projects-schedule.component.html deleted file mode 100644 index 24aed330..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/projects-schedule.component.html +++ /dev/null @@ -1,149 +0,0 @@ - -
- -
- -
-
-
No Projects to show.
- -
-
-
-
-
-
-
-
-
-
- - - {{project.name}} -
-
- -
-
- - {{member.name}} - (Pending Invitation) -
-
-
- -
- - No members assigned to the project. - -
-
-
-
-
-
-
-
-
-
-
-
- {{m.month}} -
-
-
-
-
-
-
-
- {{d.name}} -
-
- {{d.day}} -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- -
-
-
- -
- - - -
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - - - - - - diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/projects-schedule.component.scss b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/projects-schedule.component.scss deleted file mode 100644 index 2e8bcd73..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/projects-schedule.component.scss +++ /dev/null @@ -1,261 +0,0 @@ -.gannt { - min-width: 97vw; - max-width: 97vw; - border-top: 1px solid #f0f0f0; - border-bottom: 1px solid #f0f0f0; - border-radius: 4px; - overflow: hidden; - border-right: 1px solid #f0f0f0; -} - -.top, .top-left-placeholder { - max-height: 70px; - min-height: 70px; - position: sticky; - top: 0; - z-index: 1; - background: white; -} - -.top-left-placeholder { - width: 100%; - right: 0; - box-shadow: 0px 1px #f0f0f0; - background: #fafafa; -} - -.fixed-left-column, .fixed-right-column { - overflow-y: auto; - min-height: calc(100vh - 200px); - max-height: calc(100vh - 200px); - will-change: transform; - transition: scroll-behavior 0.125s ease; -} - -.fixed-left-column { - min-width: 400px; - max-width: 400px; - overflow-x: scroll; - box-shadow: 10px 0px 8px -8px #00000026; - border-left: 1px solid #f0f0f0; - z-index: 9; -} - -.scroll-animation { - scroll-behavior: smooth; -} - -.fixed-right-column { - position: relative; - overflow-x: scroll; - box-shadow: 1px 0px #f0f0f0; -} - -.h-default { - min-height: 42px; - max-height: 42px; -} - -.day-boundary { - position: relative; - box-shadow: -1px 1px #f0f0f0; -} - -.month-boundary { - box-shadow: -1px 0px #f0f0f0; - padding-left: 14px; - background: #e6f7ff; -} - -.active-hover-bg { - cursor: pointer; -} - -.weekend { - background: linear-gradient(-45deg, rgb(230 230 230 / 50%) 12.5%, transparent 12.5%, transparent 50%, rgb(230 230 230 / 50%) 50%, rgb(230 230 230 / 50%) 62.5%, transparent 62.5%, transparent) 0% 0%/5px 5px; -} - -.middle { - position: relative; -} - -.day-cells { - position: absolute; - top: 0; - bottom: 0; - height: 100%; - z-index: -1; -} - -.inner-day-cell { - box-shadow: -1px 1px #f0f0f0; - height: 100%; -} - -.bar-top { - padding-top: 11px; -} - -.ellipsis { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.today { - position: relative; - - &::after { - position: absolute; - content: ""; - left: 0; - top: 0; - bottom: 0; - width: 2px; - z-index: -1; - background: #188ff9a6; - } -} - -.today-bg { - background: #188ff9a6; - - &::after { - display: none; - } - - & div { - color: white; - } -} - -.d-name { - font-size: 13px -} - -.d-day { - line-height: 25px -} - -.month-name { - min-height: 25px; - max-height: 25px -} - -.no-data-img-holder { - max-width: 100px; -} - -.single-group { - min-height: 36px; - max-height: 36px; - border-bottom: 1px solid #f0f0f0; - padding-left: 7px; - color: hwb(0 0% 100%/0.85); - font-weight: 500; -} - -.placeholder-drag { - background: rgb(217, 227, 238); - border: 1px solid rgb(173, 181, 189); - position: absolute; - min-height: 28px; - max-height: 28px; - border-radius: 4px; - z-index: 9; - cursor: move; -} - -.highlighter { - position: absolute; - top: 25px; - bottom: 0; - z-index: -1; - background: rgb(217, 227, 238); - transition: 0.15s all; - will-change: transform; -} - -.no-tasks { - width: 100%; - height: 42px; - background: #fafafa; - display: flex; - align-items: center; - padding-left: 12px; -} - -.single-project, .single-project-member, .day-boundary-inner, .single-member-task, .h-default { - min-height: 50px; -} - -.single-member-task { - box-shadow: 1px 1px #f0f0f0; -} - -.single-project, .single-project-member { - border-bottom: 1px solid #f0f0f0; - transition: 0.25s all; -} - - -.single-project-members { - border-bottom: 1px solid rgb(213 213 213); -} - -.single-project-member { - min-height: 45px; - background: #f5f5f552; - - & .active { - background: rgb(241 241 241 / 43%); - } -} - -.project-member-container { - min-height: 45px; - transition: 0.2s all; -} - -.project-unassigned-tasks-container { - -} - -.project-unassigned-task-container { - min-height: 45px; - max-height: 45px; - border-top: 1px solid #f0f0f0; -} - -.project-member-task-container { - min-height: 45px; - max-height: 45px; - border-top: 1px solid #f0f0f0; -} - -.add-parent-task-section { - min-height: 45px; - max-height: 45px; - border-top: 1px solid #f0f0f0; - background: #f6f6f64f; -} - -.title-unassigned { - min-height: 45px; - max-height: 45px; - background: rgb(255 206 206 / 16%); -} - -.project-active { - color: #1890ff; - background: #e6f7ffa8; -} - -.add-member-allocation { - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; - z-index: 0; -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/projects-schedule.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/projects-schedule.component.spec.ts deleted file mode 100644 index 22e14790..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/projects-schedule.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ProjectsScheduleComponent } from './projects-schedule.component'; - -describe('ProjectsScheduleComponent', () => { - let component: ProjectsScheduleComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ProjectsScheduleComponent] - }); - fixture = TestBed.createComponent(ProjectsScheduleComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/projects-schedule.component.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/projects-schedule.component.ts deleted file mode 100644 index 4c043212..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/projects-schedule.component.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - NgZone, - OnInit, - ViewChild -} from '@angular/core'; -import {IScheduleProject, IScheduleProjectMember, IScheduleSingleMonth} from "@interfaces/schedular"; -import {log_error} from "@shared/utils"; -import {ScheduleApiService} from "@api/schedule-api.service"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {AuthService} from "@services/auth.service"; -import {AvatarNamesMap} from "@shared/constants"; -import {ProjectScheduleService} from "./service/project-schedule-service.service"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ProjectsService} from "../../../projects/projects.service"; -import {TaskViewService} from "@admin/components/task-view/task-view.service"; -import {Socket} from "ngx-socket-io"; -import {SchedulerCommonService} from "../service/scheduler-common.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; - -@Component({ - selector: 'worklenz-projects-schedule', - templateUrl: './projects-schedule.component.html', - styleUrls: ['./projects-schedule.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectsScheduleComponent implements OnInit { - @ViewChild('scroller') scroller?: ElementRef; - @ViewChild('fixed_right_column') fixed_right_column?: ElementRef; - @ViewChild('fixed_left_column') fixed_left_column?: ElementRef; - - loading = false - innerLoading = false; - - protected readonly Number = Number; - protected showTaskModal = false; - protected showMemberModal = false; - protected selectedTaskId: string | null = null; - protected selectedTeamMember: IScheduleProjectMember | null = null; - protected selectedProjectId: string | null = null; - - initialScroll = 0; - numberOfDays: number = 0; - - projectId: string | null = null; - chartStart: string | null = null; - chartEnd: string | null = null; - - selectedTask: IProjectTask | null = null; - private session: ILocalSession | null = null; - - months: IScheduleSingleMonth[] = []; - projects: IScheduleProject[] = []; - - getColor(name?: string) { - return AvatarNamesMap[name?.charAt(0).toUpperCase() || 'A']; - } - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly api: ScheduleApiService, - private readonly auth: AuthService, - private readonly ngZone: NgZone, - public service: ProjectScheduleService, - public common: SchedulerCommonService, - private readonly projectService: ProjectsService, - private readonly taskViewService: TaskViewService - ) { - this.session = this.auth.getCurrentSession(); - - this.common.onScrollToDate.pipe(takeUntilDestroyed()).subscribe((scrollAmount) => { - this.scrollToDate(scrollAmount); - }) - - this.service.onMemberIndicatorChange.pipe(takeUntilDestroyed()).subscribe((body) => { - void this.getMemberAllocation(body.projectId, body.teamMemberId) - }); - - this.service.onMemberProjectIndicatorChange.pipe(takeUntilDestroyed()).subscribe((body) => { - void this.getMemberProjectAllocation(body.projectId, body.teamMemberId, body.isProjectRefresh); - }); - - this.service.onReload.pipe(takeUntilDestroyed()).subscribe(() => { - void this.init(true); - }) - - } - - async ngOnInit() { - await this.init(true); - } - - async init(loading: boolean) { - await this.createChartDates(loading); - await this.getProjects(); - setTimeout(() => { - this.scrollToDate(this.initialScroll - (this.common.GANNT_COLUMN_WIDTH * 2)); - }, 125) - } - - async createChartDates(loading: boolean) { - if (!this.session?.team_id) return; - const timeZone = this.session?.timezone_name ? this.session.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone; - try { - this.loading = loading; - const res = await this.api.getGanttDates(this.session.team_id, timeZone) - if (res.done) { - this.months = res.body.date_data; - this.numberOfDays = res.body.width; - this.initialScroll = res.body.scroll_by; - this.chartStart = res.body.chart_start; - this.chartEnd = res.body.chart_end; - this.common.startDate = res.body.chart_start; - this.common.endDate = res.body.chart_end; - } - this.cdr.markForCheck(); - } catch (e) { - log_error(e); - this.cdr.markForCheck(); - } - } - - async getProjects() { - if (!this.session?.team_id) return; - const timeZone = this.session?.timezone_name ? this.session.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone; - try { - const res = await this.api.getProjects(this.session.team_id, timeZone) - if (res.done) { - this.service.projects = res.body; - } - this.loading = false; - await this.scrollListner(); - this.cdr.markForCheck(); - } catch (e) { - log_error(e); - this.loading = false; - this.cdr.markForCheck(); - } - } - - async getMemberAllocation(projectId: string, teamMemberId: string) { - if (!projectId || !teamMemberId) return; - const timeZone = this.session?.timezone_name ? this.session.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone; - try { - const res = await this.api.getMemberAllocation(projectId, teamMemberId, timeZone, false); - if (res.done) { - this.service.updateMemberAllocation(projectId, teamMemberId, res.body); - this.cdr.markForCheck(); - } - } catch (e) { - log_error(e) - } - } - - async getMemberProjectAllocation(projectId: string, teamMemberId: string, isProjectRefresh: boolean) { - if (!projectId || !teamMemberId) return; - - const timeZone = this.session?.timezone_name ? this.session.timezone_name : Intl.DateTimeFormat().resolvedOptions().timeZone; - try { - const res = await this.api.getMemberProjectAllocation(projectId, teamMemberId, timeZone, isProjectRefresh); - if (res.done) { - this.service.updateMemberAllocation(projectId, teamMemberId, res.body); - this.cdr.markForCheck(); - } - } catch (e) { - log_error(e) - } - } - - async scrollListner() { - this.ngZone.runOutsideAngular(() => { - this.fixed_left_column?.nativeElement.addEventListener('scroll', () => { - if (this.fixed_right_column) this.fixed_right_column.nativeElement.scrollTop = this.fixed_left_column?.nativeElement.scrollTop; - }); - this.fixed_right_column?.nativeElement.addEventListener('scroll', () => { - if (this.fixed_left_column) this.fixed_left_column.nativeElement.scrollTop = this.fixed_right_column?.nativeElement.scrollTop; - }); - }) - } - - scrollToDate = (scrollAmount: number) => { - this.ngZone.runOutsideAngular(() => { - if (this.fixed_right_column) { - this.fixed_right_column.nativeElement.classList.add('scroll-animation'); - this.fixed_right_column.nativeElement.scrollLeft = scrollAmount; - setTimeout(() => { - this.fixed_right_column?.nativeElement.classList.remove('scroll-animation'); - void this.scrollListner(); - }, 125) - } - this.cdr.markForCheck(); - }) - } - - toggleProject(project: IScheduleProject) { - if (!project) return; - this.service.toggleProject(project.id) - } - - onShowChange(show: boolean) { - if (!show) { - this.selectedTask = null; - this.cdr.markForCheck(); - } - } - - protected openMember(member: IScheduleProjectMember, projectId: string) { - if (member.pending_invitation) return; - this.selectedProjectId = projectId - this.selectedTeamMember = member; - this.showMemberModal = true; - this.cdr.markForCheck(); - } - - protected openTask(task: IProjectTask) { - this.selectedTask = task; - this.showTaskModal = true; - this.cdr.markForCheck(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/service/project-schedule-service.service.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/service/project-schedule-service.service.ts deleted file mode 100644 index 3d9b1095..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/service/project-schedule-service.service.ts +++ /dev/null @@ -1,136 +0,0 @@ -import {Injectable} from '@angular/core'; -import { - IMemberIndicatorContextMenuEvent, - IMemberUpdateResponse, - IProjectUpdateResposne, - IScheduleProject -} from "@interfaces/schedular"; -import {Subject} from "rxjs"; - -@Injectable({ - providedIn: 'root' -}) -export class ProjectScheduleService { - public width = 0; - public top = 0; - public left = 0; - public opacity = 0; - public transition = 0.15; - - public innerLoading = false; - - public highlighterLeft = 0; - public highlighterWidth = 0; - - projects: IScheduleProject[] = []; - - private readonly contextMenuSbj$ = new Subject(); - private readonly projectRefresh$Sbj = new Subject<{ - projectId: string, - indicatorOffset: number, - indicatorWidth: number - }>(); - private readonly scheduleRefresh$Sbj = new Subject<{ projectId: string, teamMemberId: string }>(); - private readonly scheduleProjectRefresh$Sbj = new Subject<{ projectId: string, teamMemberId: string, isProjectRefresh: boolean }>(); - private readonly resetAllocator$Sbj = new Subject(); - private readonly projectToggle$Sbj = new Subject(); - private readonly reload$Sbj = new Subject(); - - get onProjectIndicatorChange() { - return this.projectRefresh$Sbj.asObservable(); - } - - get onMemberIndicatorChange() { - return this.scheduleRefresh$Sbj.asObservable(); - } - - get onMemberProjectIndicatorChange() { - return this.scheduleProjectRefresh$Sbj.asObservable(); - } - - get onResetAllocator() { - return this.resetAllocator$Sbj.asObservable(); - } - - get onContextMenu$() { - return this.contextMenuSbj$.asObservable(); - } - - get onProjectToggle$() { - return this.projectToggle$Sbj.asObservable(); - } - - get onReload() { - return this.reload$Sbj.asObservable(); - } - - public emitProjectToggle(projectId: string) { - this.projectToggle$Sbj.next(projectId); - } - - public emitOnContextMenu(event: MouseEvent, projectId: string, teamMemberId: string, ids: string[]) { - this.contextMenuSbj$.next({event, projectId, teamMemberId , ids}); - } - - public emitProjectIndicatorChange(projectId: string, indicatorOffset: number, indicatorWidth: number) { - this.projectRefresh$Sbj.next({projectId, indicatorOffset, indicatorWidth}); - } - - public emitMemberIndicatorChange(projectId: string, teamMemberId: string) { - this.scheduleRefresh$Sbj.next({projectId, teamMemberId}); - } - - public emitMemberProjectIndicatorChange(projectId: string, teamMemberId: string, isProjectRefresh: boolean) { - this.scheduleProjectRefresh$Sbj.next({projectId, teamMemberId, isProjectRefresh}); - } - - public emitResetAllocator() { - this.resetAllocator$Sbj.next(); - } - - public emitReload() { - this.reload$Sbj.next(); - } - - toggleProject(projectId: string) { - const project = this.projects.find(p => p.id === projectId); - if (!project) return; - - project.is_expanded = !project.is_expanded; - - this.emitProjectToggle(projectId); - - } - - updateMemberAllocation(projectId: string, teamMemberId: string, response: IMemberUpdateResponse) { - const project = this.projects.find(p => p.id === projectId); - if (!project) return; - - response.project_allocation.indicator_offset = response.project_allocation.indicator_offset ? response.project_allocation.indicator_offset : 0; - response.project_allocation.indicator_width = response.project_allocation.indicator_width ? response.project_allocation.indicator_width : 0; - - this.emitProjectIndicatorChange(projectId, response.project_allocation.indicator_offset, response.project_allocation.indicator_width) - - const member = project.members.find(m => m.team_member_id === teamMemberId); - if (!member) return; - - member.allocations = response.member_allocations; - - this.emitResetAllocator(); - - } - - updateProjectAllocation(projectId: string, response: IProjectUpdateResposne) { - const project= this.projects.find(p => p.id === projectId); - if (!project) return; - - response.project_allocation.indicator_offset = response.project_allocation.indicator_offset ? response.project_allocation.indicator_offset : 0; - response.project_allocation.indicator_width = response.project_allocation.indicator_width ? response.project_allocation.indicator_width : 0; - - this.emitProjectIndicatorChange(projectId, response.project_allocation.indicator_offset, response.project_allocation.indicator_width) - - this.emitResetAllocator(); - - } - -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/service/schedule-member-tasks-hashmap-service.service.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/service/schedule-member-tasks-hashmap-service.service.ts deleted file mode 100644 index d7c915e3..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/service/schedule-member-tasks-hashmap-service.service.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { Injectable } from '@angular/core'; -import {Subject} from "rxjs"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {IMemberTaskListGroup} from "@interfaces/schedular"; - -@Injectable({ - providedIn: 'root' -}) -export class ScheduleMemberTasksHashmapService { -private readonly _selectSbj$ = new Subject(); - private readonly _deselectSbj$ = new Subject(); - private readonly _deselectAllSbj$ = new Subject(); - - /** Map */ - private readonly _groupTaskMap = new Map(); - /** Map */ - private readonly _taskGroupIdsMap = new Map(); - /** Map */ - private readonly _selectedTasksMap = new Map(); - /** Map */ - private readonly _allTasksMap = new Map(); - /** Map(); - - private _selectedCount = 0; - - public get tasks() { - return this._allTasksMap; - } - - public get onSelect$() { - return this._selectSbj$.asObservable(); - } - - public get onDeselect$() { - return this._deselectSbj$.asObservable(); - } - - public get onDeselectAll$() { - return this._deselectAllSbj$.asObservable(); - } - - public reset() { - this._groupTaskMap.clear(); - this._taskGroupIdsMap.clear(); - this._selectedTasksMap.clear(); - this._allTasksMap.clear(); - this._subTasksMap.clear(); - this._selectedCount = 0; - } - - public registerGroup(group: IMemberTaskListGroup) { - for (const task of group.tasks) { - this.add(group.id, task); - } - } - - public add(groupId: string, task: IProjectTask) { - if (!task.id) return; - this.updateGroupTaskMap(groupId, task.id); - this._taskGroupIdsMap.set(task.id, groupId); - this._allTasksMap.set(task.id, task); - - if (task.parent_task_id) { - this.updateSubtasksMap(task.parent_task_id, task) - } - } - - public addGroupTask(groupId: string, task: IProjectTask) { - if (!task.id) return; - this._taskGroupIdsMap.set(task.id, groupId); - } - - public has(taskId: string) { - return this._allTasksMap.has(taskId); - } - - public remove(task: IProjectTask) { - if (!task.id) return; - this.deselectTask(task); - this._taskGroupIdsMap.get(task.id); - this._allTasksMap.delete(task.id); - } - - private updateGroupTaskMap(groupId: string, taskId: string, selected?: boolean) { - const map = this._groupTaskMap.get(groupId); - if (map) { - if (typeof selected === "boolean") { - map[taskId] = selected; - } else { - delete map[taskId]; - } - - this._groupTaskMap.set(groupId, map); - } else { - this._groupTaskMap.set(groupId, {[taskId]: selected || false}); - } - } - - private updateSubtasksMap(parentTaskId: string, task: IProjectTask, selected?: boolean) { - const subtasks = this._subTasksMap.get(parentTaskId) || []; - const isParentTaskAvailable = subtasks.some((subtask) => subtask.id === task.id); - - // Only push the subtask if the parent task is not available - if (!isParentTaskAvailable) { - subtasks.push(task); - this._subTasksMap.set(parentTaskId, subtasks); - } - - } - - public selectTask(task: IProjectTask) { - if (this._selectedTasksMap.get(task.id as string)) return; - - this._selectedTasksMap.set(task.id as string, task); - this._selectedCount++; - - this.updateGroupTaskMap( - this._taskGroupIdsMap.get(task.id as string) as string, - task.id as string, - true - ); - - this._selectSbj$.next(task); - - } - - public deselectTask(task: IProjectTask) { - if (this._selectedTasksMap.has(task.id as string)) { - this._selectedTasksMap.delete(task.id as string); - this._selectedCount--; - - this.updateGroupTaskMap( - this._taskGroupIdsMap.get(task.id as string) as string, - task.id as string, - false - ); - - this._deselectSbj$.next(task); - } - } - - private deselectLocalGroups() { - for (const [groupId, task] of this._groupTaskMap) { - for (const taskId in task) { - this.updateGroupTaskMap(groupId, taskId, false); - } - } - } - - public deselectAll() { - if (!this._selectedTasksMap.size) return; - - this.deselectLocalGroups(); - this._selectedTasksMap.clear(); - this._selectedCount = 0; - - this._deselectAllSbj$.next(); - } - - public getGroupId(taskId: string) { - return this._taskGroupIdsMap.get(taskId); - } - - public getSelectedTasks() { - const tasks = []; - for (const [, task] of this._selectedTasksMap.entries()) { - tasks.push(task); - } - return tasks; - } -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/service/schedule-member-tasks-service.service.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/service/schedule-member-tasks-service.service.ts deleted file mode 100644 index d0e5480e..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/projects-schedule/service/schedule-member-tasks-service.service.ts +++ /dev/null @@ -1,264 +0,0 @@ -import { Injectable } from '@angular/core'; -import {BehaviorSubject, Subject} from "rxjs"; -import { - IGroupByOption, - ITaskListContextMenuEvent, - ITaskListGroupChangeResponse -} from "../../../../modules/task-list-v2/interfaces"; -import {ITeamMemberViewModel} from "@interfaces/api-models/team-members-get-response"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {ITaskPrioritiesGetResponse} from "@interfaces/api-models/task-priorities-get-response"; -import {ITaskPhase} from "@interfaces/api-models/task-phase"; -import {Socket} from "ngx-socket-io"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {SocketEvents} from "@shared/socket-events"; -import {IMemberTaskListGroup} from "@interfaces/schedular"; -import {ScheduleMemberTasksHashmapService} from "./schedule-member-tasks-hashmap-service.service"; - -@Injectable({ - providedIn: 'root' -}) -export class ScheduleMemberTasksService { -private readonly membersSbj$ = new Subject(); - private readonly statusesSbj$ = new Subject(); - private readonly prioritiesSbj$ = new Subject(); - private readonly contextMenuSbj$ = new Subject(); - private readonly taskAddOrDeleteSbj$ = new BehaviorSubject(null); - private readonly phasesSbj$ = new Subject(); - private readonly updateOverviewChartsSbj$ = new Subject(); - private readonly removeMembersTaskSbj$ = new Subject(); - private readonly refreshOnlyMembersSbj$ = new Subject(); - private readonly refreshSubtasksIncludedSbj$ = new Subject(); - - public readonly HIGHLIGHT_COL_CLS = 'highlight-col'; - - public readonly GROUP_BY_STATUS_VALUE = "status"; - public readonly GROUP_BY_PRIORITY_VALUE = "priority"; - public readonly GROUP_BY_PHASE_VALUE = "phase"; - public readonly GROUP_BY_OPTIONS: IGroupByOption[] = [ - {label: "Status", value: this.GROUP_BY_STATUS_VALUE}, - {label: "Priority", value: this.GROUP_BY_PRIORITY_VALUE}, - {label: "Phase", value: this.GROUP_BY_PHASE_VALUE} - ]; - - public groups: IMemberTaskListGroup[] = []; - - private _members: ITeamMemberViewModel[] = []; - private _statuses: ITaskStatusViewModel[] = []; - private _priorities: ITaskPrioritiesGetResponse[] = []; - private _phases: ITaskPhase[] = []; - - public isSubtasksIncluded = false; - - public currentTab: "" | "end_date_null" | "start_date_null" | "start_end_dates_null" = ""; - - private get _currentGroup(): IGroupByOption { - const key = localStorage.getItem("worklenz.schedule.group_by"); - if (key) { - const g = this.GROUP_BY_OPTIONS.find(o => o.value === key); - if (g) - return g; - } - return this.GROUP_BY_OPTIONS[0]; - } - - private set _currentGroup(option) { - localStorage.setItem("worklenz.schedule.group_by", option.value); - } - - public set members(value) { - this._members = value; - this.membersSbj$.next(); - } - - public get members() { - return this._members; - } - - public set priorities(value) { - this._priorities = value; - this.prioritiesSbj$.next(); - } - - public get priorities() { - return this._priorities; - } - - public set phases(value) { - this._phases = value; - this.phasesSbj$.next(); - } - - public get phases() { - return this._phases; - } - - get onStatusesChange$() { - return this.statusesSbj$.asObservable(); - } - - get onPrioritiesChange$() { - return this.prioritiesSbj$.asObservable(); - } - - get onContextMenu$() { - return this.contextMenuSbj$.asObservable(); - } - - get onPhaseChange$() { - return this.phasesSbj$.asObservable(); - } - - set statuses(value) { - this._statuses = value; - this.statusesSbj$.next(); - } - - get statuses() { - return this._statuses; - } - - get updateOverviewCharts() { - return this.updateOverviewChartsSbj$.asObservable(); - } - - get onRemoveMembersTask() { - return this.removeMembersTaskSbj$.asObservable(); - } - - constructor( - private readonly socket: Socket, - private readonly map: ScheduleMemberTasksHashmapService - ) { - } - - public setCurrentGroup(group: IGroupByOption) { - this._currentGroup = group; - } - - public getCurrentGroup() { - return this._currentGroup; - } - - public onRefreshMembers() { - return this.refreshOnlyMembersSbj$.asObservable(); - } - - public emitRefreshMembers() { - this.refreshOnlyMembersSbj$.next(); - } - - public emitRefreshSubtasksIncluded() { - this.refreshSubtasksIncludedSbj$.next(); - } - - - public emitOnContextMenu(event: MouseEvent, task: IProjectTask) { - this.contextMenuSbj$.next({event, task}); - } - - public emitTaskAddOrDelete(taskId: string, isSubTask: boolean) { - this.taskAddOrDeleteSbj$.next({ - taskId: taskId, - isSubTask: isSubTask - }); - } - - public emitUpdateOverviewCharts() { - this.updateOverviewChartsSbj$.next(); - } - - public emitRemoveMembersTask(taskId: string) { - this.removeMembersTaskSbj$.next(taskId); - } - - public getGroupIdByGroupedColumn(task: IProjectTask) { - const groupBy = this.getCurrentGroup().value; - if (groupBy === this.GROUP_BY_STATUS_VALUE) - return task.status as string; - - if (groupBy === this.GROUP_BY_PRIORITY_VALUE) - return task.priority as string; - - if (groupBy === this.GROUP_BY_PHASE_VALUE) - return task.phase_id as string; - - return null; - } - - public updateTaskGroup(task: IProjectTask, insert = true) { - if (!task.id) return; - const groupId = this.getGroupIdByGroupedColumn(task); - - if (groupId) { - // Delete the task from its current group - this.deleteTask(task.id); - - // Add the task to the new group - this.addTask(task, groupId, insert); - } - } - - public deleteTask(taskId: string, index: number | null = null) { - // Get the group id of the task. - const groupId = this.map.getGroupId(taskId); - if (!groupId || !taskId) return; - - // Find the group that contains the task. - const group = this.groups.find(g => g.id === groupId); - if (!group) return; - - // Get the task object from the list of selected tasks. - const task = this.map.getSelectedTasks().find(t => t.id === taskId); - - // If the task is a sub-task, remove it from its parent task's sub-tasks list. - if (task?.is_sub_task) { - const parentTask = group.tasks.find(t => t.id === task.parent_task_id); - if (parentTask) { - // Find the index of the sub-task in the parent task's sub-tasks list. - const index = parentTask.sub_tasks?.findIndex(t => t.id === task.id); - if (typeof index !== "undefined" && index !== -1) { - if (!parentTask.sub_tasks_count) parentTask.sub_tasks_count = 0; - parentTask.sub_tasks_count = Math.max(+parentTask.sub_tasks_count - 1, 0); - parentTask.sub_tasks?.splice(index, 1); - this.emitTaskAddOrDelete(parentTask.id as string, true); - } - } - this.socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), parentTask?.id); - this.map.remove(task); - } else { // If the task is not a sub-task, remove it from the group's task list. - const taskIndex = index ?? group.tasks.findIndex(t => t.id === taskId); - if (taskIndex !== -1) { - this.map.remove(group.tasks[taskIndex]); - group.tasks.splice(taskIndex, 1); - this.emitTaskAddOrDelete(taskId, false); - } - } - - // Deselect all tasks after removing the task. - this.map.deselectAll(); - } - - public addTask(task: IProjectTask, groupId: string, insert = false) { - const group = this.groups.find(g => g.id === groupId); - if (group && task.id) { - // Add the task to the group's task array - if (insert) { - group.tasks.unshift(task); - } else { - group.tasks.push(task); - } - this.map.add(groupId, task); - this.emitTaskAddOrDelete(task.parent_task_id as string, !!task.parent_task_id); - } - } - - public reset() { - this._members = []; - this._statuses = []; - this._priorities = []; - - this.groups = []; - this.isSubtasksIncluded = false; - } -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/service/scheduler-common.service.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-v2/service/scheduler-common.service.ts deleted file mode 100644 index 629dbeee..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-v2/service/scheduler-common.service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {Injectable} from '@angular/core'; -import moment from "moment"; -import {Subject} from "rxjs"; - -@Injectable({ - providedIn: 'root' -}) -export class SchedulerCommonService { - - public readonly GANNT_COLUMN_WIDTH = 35; - - startDate: string | null = null; - endDate: string | null = null; - - private readonly scrollAmount$Sbj = new Subject(); - - get onScrollToDate() { - return this.scrollAmount$Sbj.asObservable(); - } - - public emitScrollToDate(scrollAmount: number) { - this.scrollAmount$Sbj.next(scrollAmount); - } - - scrollToDay(dateToScroll: Date) { - - const formattedStartDate = moment(this.startDate).format("YYYY-MM-DD"); - const formattedDateToScroll = moment(dateToScroll).format("YYYY-MM-DD"); - const daysDifference = moment(formattedDateToScroll).diff(formattedStartDate, "days"); - - this.emitScrollToDate(daysDifference * this.GANNT_COLUMN_WIDTH); - - } - -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-view/schedule-view.component.html b/worklenz-frontend/src/app/administrator/schedule/schedule-view/schedule-view.component.html deleted file mode 100644 index 44c54844..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-view/schedule-view.component.html +++ /dev/null @@ -1,36 +0,0 @@ -
-
- - - -
- -
- - -
-
- - - -
-
- - -
-
- -
-
- - - - - - - - - -
- diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-view/schedule-view.component.scss b/worklenz-frontend/src/app/administrator/schedule/schedule-view/schedule-view.component.scss deleted file mode 100644 index d679d54a..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-view/schedule-view.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -nz-date-picker { - min-width: 180px; -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-view/schedule-view.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-view/schedule-view.component.spec.ts deleted file mode 100644 index 837799a7..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-view/schedule-view.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ScheduleViewComponent} from './schedule-view.component'; - -describe('ResourceViewComponent', () => { - let component: ScheduleViewComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ScheduleViewComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ScheduleViewComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule-view/schedule-view.component.ts b/worklenz-frontend/src/app/administrator/schedule/schedule-view/schedule-view.component.ts deleted file mode 100644 index 68ca22c6..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule-view/schedule-view.component.ts +++ /dev/null @@ -1,64 +0,0 @@ -import {Component, OnInit, ViewChild} from '@angular/core'; -import { startOfWeek, startOfMonth} from "date-fns" -import {ProjectScheduleComponent} from "../project-schedule/project-schedule.component"; -import {TeamScheduleComponent} from "../team-schedule/team-schedule.component"; -import {SchedulerCommonService} from "../schedule-v2/service/scheduler-common.service"; - -enum ResourceGanttTypes { - 'Projects', 'Teams' -} - -@Component({ - selector: 'worklenz-schedule-view', - templateUrl: './schedule-view.component.html', - styleUrls: ['./schedule-view.component.scss'] -}) -export class ScheduleViewComponent { - @ViewChild(ProjectScheduleComponent) projectsSchedule: ProjectScheduleComponent | undefined; - @ViewChild(TeamScheduleComponent) teamSchedule: TeamScheduleComponent | undefined; - - options = [ResourceGanttTypes[0], ResourceGanttTypes[1]]; - selectedChartType: ResourceGanttTypes = ResourceGanttTypes.Projects; - - dateRange: Date | null = null; - - dateModes = [ - { - value: 0, - label: 'Week' - }, - { - value: 1, - label: 'Month' - } - ] - - dateModeModel = 0; - - constructor( - private readonly service: SchedulerCommonService - ) { - } - - dateModeChange(value: number) { - this.dateModeModel = value; - } - - disabledDate = (current: Date): boolean => { - if (this.service.startDate && this.service.endDate) { - return current < new Date(this.service.startDate) || current > new Date(this.service.endDate); - } - return false; - } - - onChange(event: any) { - if (event) { - if (this.dateModeModel === 0) this.service.scrollToDay(new Date(startOfWeek(event))); - if (this.dateModeModel === 1) this.service.scrollToDay(new Date(startOfMonth(event))); - } - } - - scrollToday() { - this.service.scrollToDay(new Date()); - } -} diff --git a/worklenz-frontend/src/app/administrator/schedule/schedule.module.ts b/worklenz-frontend/src/app/administrator/schedule/schedule.module.ts deleted file mode 100644 index 1e74a4d7..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/schedule.module.ts +++ /dev/null @@ -1,120 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; - -import {ScheduleRoutingModule} from './schedule-routing.module'; -import {ScheduleViewComponent} from './schedule-view/schedule-view.component'; -import {NzEmptyModule} from "ng-zorro-antd/empty"; -import {NzSpinModule} from "ng-zorro-antd/spin"; -import {NzAvatarModule} from "ng-zorro-antd/avatar"; -import {NzPopoverModule} from "ng-zorro-antd/popover"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzDatePickerModule} from "ng-zorro-antd/date-picker"; -import {FormsModule, ReactiveFormsModule} from "@angular/forms"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzSegmentedModule} from "ng-zorro-antd/segmented"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzListModule} from "ng-zorro-antd/list"; -import {ProjectScheduleComponent} from './project-schedule/project-schedule.component'; -import {NzDividerModule} from "ng-zorro-antd/divider"; -import {TeamScheduleComponent} from './team-schedule/team-schedule.component'; -import {TaskViewModule} from "../components/task-view/task-view.module"; -import {FirstCharUpperPipe} from "@pipes/first-char-upper.pipe"; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; -import { ProjectsScheduleComponent } from './schedule-v2/projects-schedule/projects-schedule.component'; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {TaskListV2Module} from "../modules/task-list-v2/task-list-v2.module"; -import {RxFor} from "@rx-angular/template/for"; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import { TaskAddRowComponent } from './schedule-v2/projects-schedule/components/task-add-row/task-add-row.component'; -import { TaskAddInputComponent } from './schedule-v2/projects-schedule/components/task-add-input/task-add-input.component'; -import {NzInputModule} from "ng-zorro-antd/input"; -import { ProjectIndicatorComponent } from './schedule-v2/projects-schedule/components/project-indicator/project-indicator.component'; -import { MemberIndicatorComponent } from './schedule-v2/projects-schedule/components/member-indicator/member-indicator.component'; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzSelectModule} from "ng-zorro-antd/select"; -import { AddMemberAllocationComponent } from './schedule-v2/projects-schedule/components/add-member-allocation/add-member-allocation.component'; -import { ContextMenuComponent } from './schedule-v2/projects-schedule/components/context-menu/context-menu.component'; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import { ProjectMemberTasksDrawerComponent } from './schedule-v2/projects-schedule/components/project-member-tasks-drawer/project-member-tasks-drawer.component'; -import {EllipsisPipe} from "@pipes/ellipsis.pipe"; -import {NzTabsModule} from "ng-zorro-antd/tabs"; -import { TaskListHeaderComponent } from './schedule-v2/projects-schedule/components/task-list-header/task-list-header.component'; -import {NzCheckboxModule} from "ng-zorro-antd/checkbox"; -import { TaskNameComponent } from './schedule-v2/projects-schedule/components/task-name/task-name.component'; -import { EndDateComponent } from './schedule-v2/projects-schedule/components/end-date/end-date.component'; -import { StartDateComponent } from './schedule-v2/projects-schedule/components/start-date/start-date.component'; -import {DateFormatterPipe} from "@pipes/date-formatter.pipe"; -import { StatusComponent } from './schedule-v2/projects-schedule/components/status/status.component'; -import { PriorityComponent } from './schedule-v2/projects-schedule/components/priority/priority.component'; -import { PhaseComponent } from './schedule-v2/projects-schedule/components/phase/phase.component'; -import { TaskListRowComponent } from './schedule-v2/projects-schedule/components/task-list-row/task-list-row.component'; -import {NzTagModule} from "ng-zorro-antd/tag"; -import { MemberTaskAddContainerComponent } from './schedule-v2/projects-schedule/components/member-task-add-container/member-task-add-container.component'; -import { TasksContextMenuComponent } from './schedule-v2/projects-schedule/components/tasks-context-menu/tasks-context-menu.component'; -import {TaskPriorityLabelComponent} from "@admin/components/task-priority-label/task-priority-label.component"; - -@NgModule({ - declarations: [ - ScheduleViewComponent, - ProjectScheduleComponent, - TeamScheduleComponent, - ProjectsScheduleComponent, - TaskAddRowComponent, - TaskAddInputComponent, - ProjectIndicatorComponent, - MemberIndicatorComponent, - AddMemberAllocationComponent, - ContextMenuComponent, - ProjectMemberTasksDrawerComponent, - TaskListHeaderComponent, - TaskNameComponent, - EndDateComponent, - StartDateComponent, - StatusComponent, - PriorityComponent, - PhaseComponent, - TaskListRowComponent, - MemberTaskAddContainerComponent, - TasksContextMenuComponent, - ], - imports: [ - CommonModule, - ScheduleRoutingModule, - NzEmptyModule, - NzSpinModule, - NzAvatarModule, - NzPopoverModule, - NzTypographyModule, - NzIconModule, - NzDatePickerModule, - FormsModule, - NzButtonModule, - NzSegmentedModule, - NzToolTipModule, - NzDrawerModule, - NzListModule, - NzDividerModule, - TaskViewModule, - FirstCharUpperPipe, - SafeStringPipe, - NzSkeletonModule, - TaskListV2Module, - RxFor, - NzBadgeModule, - ReactiveFormsModule, - NzInputModule, - NzSpaceModule, - NzSelectModule, - NzDropDownModule, - EllipsisPipe, - NzTabsModule, - NzCheckboxModule, - DateFormatterPipe, - NzTagModule, - TaskPriorityLabelComponent - ] -}) -export class ScheduleModule { -} diff --git a/worklenz-frontend/src/app/administrator/schedule/team-schedule/team-schedule.component.html b/worklenz-frontend/src/app/administrator/schedule/team-schedule/team-schedule.component.html deleted file mode 100644 index 1d9b8405..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/team-schedule/team-schedule.component.html +++ /dev/null @@ -1,155 +0,0 @@ -
-
- - -
-
-
- -
- The selected team has no scheduled member activities. -
-
- -
- -
-
-
-
-
-
- - - - W{{month.week_index || 0 - 1 }} - {{month.month_name}} - - - - -
-
-
-
-
{{date.date | date: 'dd'}} -
- -
-
- Subtasks -
- -
- -
- - {{resource.name || resource.invitee_email}}
-
-
-
    -
  • -
  • -
-
-
-
- - - - -
-
- {{element.name}} -
-
-
-
    -
  • -
  • -
-
-
-
- - - - -
-
- -
-
Unassigned tasks
-
-
-
    -
  • -
  • -
-
-
-
- - -
-
-
-
-
-
-
- - - - - - - - - - -
-
    -
  • - - - {{task.name}} - - -
  • -
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/schedule/team-schedule/team-schedule.component.scss b/worklenz-frontend/src/app/administrator/schedule/team-schedule/team-schedule.component.scss deleted file mode 100644 index 7fc84c70..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/team-schedule/team-schedule.component.scss +++ /dev/null @@ -1,220 +0,0 @@ -$column_count: var(--column_count); -$column_width: var(--column_width); -$top_margin: var(--top-margin); - -body { - margin: 0; -} - -.grid-scroller { - position: relative; - overflow: auto; - height: calc(100vh - $top_margin); -} - -.grid { - display: grid; - grid-template-columns: 250px 0 repeat($column_count, $column_width); - width: calc(250px + var(--column_count) * var(--column_width)); -} - -.grid-entry { - padding: 12px 7px; - background-color: white; - border-left: 1px solid #f1f1f1; - border-bottom: 1px solid #f1f1f1; -} - -.grid-entry.last, -.grid-header.last { - border-right: none; -} - -.grid-header { - background-color: #fafafa; - padding: 2px 2px; - font-weight: 500; - font-size: 11px; - color: #202125; - position: sticky; - top: 0; - z-index: 1; - border: 1px solid #ededed; - text-align: center; - line-height: 13px; -} - -.grid-header.fixed-left { - z-index: 5; -} - -.fixed-left { - position: sticky; - left: 0; - z-index: 2; - border-bottom: 1px solid #f1f1f1; - border-left: 1px solid #f1f1f1; - border-right: 1.4px solid #B5B4B4; -} - -.placeholder { - grid-column-start: 1; - grid-column-end: 95; - border-right: none; -} - -mwlResizable { - box-sizing: border-box; // required for the enableGhostResize option to work -} - -.resize-handle-top, -.resize-handle-bottom { - position: absolute; - height: 5px; - cursor: row-resize; - width: 100%; -} - -.resize-handle-top { - top: 0; -} - -.resize-handle-bottom { - bottom: 0; -} - -.resize-handle-left, -.resize-handle-right { - position: absolute; - cursor: col-resize; - width: 9px; - height: 18px; - background: #ffffff; - border: none; - border-radius: 1px; - top: 0; - bottom: 0; - margin: auto 1px; -} - -.resize-handle-right::after, -.resize-handle-left::after { - content: "||"; - position: absolute; - display: inline-block; - top: 0; - bottom: 0; - left: 0; - right: 0; - color: #929292; - line-height: 19px; - font-weight: 400; - margin: auto; - width: max-content; -} - -.gantt__row-bars { - li { - user-select: none; - - & > div { - background-color: transparent !important; - } - - .resize-handle-left, - .resize-handle-right { - box-shadow: 0 0 2px rgb(0 0 0 / 45%); - } - } -} - -.member { - color: darkslategrey; -} - -.resize-handle-left { - left: 0; -} - -.resize-handle-right { - right: 0; -} - -.project-collapse-icon { - font-size: 12px; - line-height: 15px; - height: 15px; - width: 15px; - color: #47474791 !important -} - -.today { - border-left: 2px solid #1890ff; - background-repeat: no-repeat; - background-position: center center; -} - -.today-header { - background-color: #1890ff; - border-color: #1890ff; - color: white; -} - -.top-grid-header { - border-top: none; -} - -.grid-entry-top { - border-bottom: none; -} - -.month-grid { - border-top: 1px solid #f1f1f1; -} - -.parent-relative { - position: absolute; -} - -.px-3.py-0.ng-star-inserted.resize-active.resize-ghost-element { - margin-top: 0 !important; -} - -.dropdown-highlight:hover { - background-color: rgba(208, 238, 250, 0.33); - border: #5587f5 1px solid; - border-radius: 3px; -} - -.expanded { - transform: rotate(-90deg); -} - -.sub-tasks-arrow { - position: relative; - cursor: pointer; - left: 3px; - width: 16px; - padding: 2px; - border: 1px solid transparent; - z-index: 1; -} - -.resource-cell { - height: 22px; - margin: 7px 0 1px 0; - border-radius: 0; -} - -.child-background-cell { - background-color: #f5f5f5; -} - -.child-background-color { - background-color: #f5f5f58a; -} - -.no-data-img-holder { - width: 100px; - margin-top: 42px; -} diff --git a/worklenz-frontend/src/app/administrator/schedule/team-schedule/team-schedule.component.spec.ts b/worklenz-frontend/src/app/administrator/schedule/team-schedule/team-schedule.component.spec.ts deleted file mode 100644 index ec2af134..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/team-schedule/team-schedule.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TeamScheduleComponent} from './team-schedule.component'; - -describe('TeamScheduleComponent', () => { - let component: TeamScheduleComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TeamScheduleComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TeamScheduleComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/schedule/team-schedule/team-schedule.component.ts b/worklenz-frontend/src/app/administrator/schedule/team-schedule/team-schedule.component.ts deleted file mode 100644 index aee70f1b..00000000 --- a/worklenz-frontend/src/app/administrator/schedule/team-schedule/team-schedule.component.ts +++ /dev/null @@ -1,113 +0,0 @@ -import {Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges} from '@angular/core'; -import {endOfWeek, format, startOfWeek} from "date-fns"; -import {IResource, IScheduledTask} from "@interfaces/project-wise-resources-view-model"; -import {EGanttColumnWidth, IGanttDateRange, IGanttWeekRange} from "@interfaces/gantt-chart"; -import {ResourceAllocationService} from "@api/resource-allocation.service"; -import {AvatarNamesMap} from "@shared/constants"; -import {log_error} from "@shared/utils"; -import {Subscription} from "rxjs"; -import {TaskViewService} from "../../components/task-view/task-view.service"; - -@Component({ - selector: 'worklenz-team-schedule', - templateUrl: './team-schedule.component.html', - styleUrls: ['./team-schedule.component.scss'] -}) -export class TeamScheduleComponent implements OnChanges, OnInit, OnDestroy { - @Input() selectedWeek: { start: Date, end: Date } = {start: startOfWeek(new Date()), end: endOfWeek(new Date())}; - - loading: boolean = false; - visible = false; - showTaskModal = false; - - resourceData: IResource[] = []; - - months: IGanttWeekRange[] = []; - dates: IGanttDateRange[] = []; - scheduledTasks: IScheduledTask[] = []; - selectedTaskId: string | null = ''; - selectedResourceId: string | null = ''; - selectedDate: string | null = ''; - - projectId: string = ''; - private tvRefreshSubscription!: Subscription; - - constructor( - private api: ResourceAllocationService, - private tvService: TaskViewService - ) { - } - - get title() { - return `Scheduled tasks - ${this.selectedDate}` - } - - open(): void { - this.visible = true; - } - - close(): void { - this.visible = false; - } - - ngOnInit(): void { - this.tvRefreshSubscription = this.tvService.onRefresh.subscribe(() => { - this.getResources(); - }); - } - - ngOnDestroy() { - this.tvRefreshSubscription?.unsubscribe(); - } - - getColor(name?: string) { - return AvatarNamesMap[name?.charAt(0).toUpperCase() || 'A']; - } - - ngOnChanges(changes: SimpleChanges): void { - const selectedWeek = changes['selectedWeek']; - if (selectedWeek.currentValue !== selectedWeek.previousValue) this.getResources().then(r => r); - } - - async getResources() { - try { - this.loading = true; - const res = await this.api.getUserWiseResources(this.selectedWeek); - - if (res.done) { - this.months = res.body.months; - this.dates = res.body.dates; - this.resourceData = res.body.projects; - document.documentElement.style.setProperty('--column_count', this.dates.length.toString()); - document.documentElement.style.setProperty('--column_width', `${EGanttColumnWidth.DAYS}px`); - document.documentElement.style.setProperty('--top-margin', '180px'); - this.loading = false; - } - - } catch (e) { - this.loading = false; - log_error(e); - } - } - - onTaskCreateOrUpdate() { - void this.getResources(); - } - - taskSelected(id: string = '', projectId: string = '') { - this.projectId = projectId; - this.selectedTaskId = id; - this.showTaskModal = true; - } - - scheduleClicked(scheduledTasks: IScheduledTask[], resourceId: string = '', date: string = '') { - this.visible = true; - this.selectedDate = format(new Date(date), 'yyyy-MM-dd'); - this.selectedResourceId = resourceId; - this.scheduledTasks = scheduledTasks; - } - - onVisibilityChange(visible: boolean) { - if (visible) document.body.classList.add("task-form-drawer-opened"); - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/categories/categories-routing.module.ts b/worklenz-frontend/src/app/administrator/settings/categories/categories-routing.module.ts deleted file mode 100644 index 4bc4deef..00000000 --- a/worklenz-frontend/src/app/administrator/settings/categories/categories-routing.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {CategoriesComponent} from "./categories/categories.component"; - -const routes: Routes = [ - { - path: "", component: CategoriesComponent - } -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class CategoriesRoutingModule { -} diff --git a/worklenz-frontend/src/app/administrator/settings/categories/categories.module.ts b/worklenz-frontend/src/app/administrator/settings/categories/categories.module.ts deleted file mode 100644 index cb5a99ec..00000000 --- a/worklenz-frontend/src/app/administrator/settings/categories/categories.module.ts +++ /dev/null @@ -1,41 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; - -import {CategoriesRoutingModule} from './categories-routing.module'; -import {CategoriesComponent} from "./categories/categories.component"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {NzCardModule} from "ng-zorro-antd/card"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzTableModule} from "ng-zorro-antd/table"; -import {NzTagModule} from "ng-zorro-antd/tag"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {ToggleMenuButtonComponent} from "@admin/components/toggle-menu-button/toggle-menu-button.component"; -import {FormsModule} from "@angular/forms"; -import {NzInputModule} from "ng-zorro-antd/input"; - -@NgModule({ - declarations: [CategoriesComponent], - imports: [ - CommonModule, - CategoriesRoutingModule, - NzDropDownModule, - NzCardModule, - NzSkeletonModule, - NzTableModule, - NzTagModule, - NzToolTipModule, - NzSpaceModule, - NzButtonModule, - NzIconModule, - NzFormModule, - ToggleMenuButtonComponent, - FormsModule, - NzInputModule - ] -}) -export class CategoriesModule { -} diff --git a/worklenz-frontend/src/app/administrator/settings/categories/categories/categories.component.html b/worklenz-frontend/src/app/administrator/settings/categories/categories/categories.component.html deleted file mode 100644 index 25337d26..00000000 --- a/worklenz-frontend/src/app/administrator/settings/categories/categories/categories.component.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - Category - Associated Projects - - - - - - {{data.name}} - - -
    -
  • - {{data.name}} -
  • -
-
- - {{data.usage}} - -
- - - -
- - - -
-
-
- - - -
- - - - - - -
- -
-
diff --git a/worklenz-frontend/src/app/administrator/settings/categories/categories/categories.component.scss b/worklenz-frontend/src/app/administrator/settings/categories/categories/categories.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/settings/categories/categories/categories.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/categories/categories/categories.component.spec.ts deleted file mode 100644 index 92ca2087..00000000 --- a/worklenz-frontend/src/app/administrator/settings/categories/categories/categories.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {CategoriesComponent} from './categories.component'; - -describe('CategoriesComponent', () => { - let component: CategoriesComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [CategoriesComponent] - }); - fixture = TestBed.createComponent(CategoriesComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/categories/categories/categories.component.ts b/worklenz-frontend/src/app/administrator/settings/categories/categories/categories.component.ts deleted file mode 100644 index e7fa8112..00000000 --- a/worklenz-frontend/src/app/administrator/settings/categories/categories/categories.component.ts +++ /dev/null @@ -1,95 +0,0 @@ -import {ChangeDetectorRef, Component, OnInit} from '@angular/core'; -import {ProjectsDefaultColorCodes} from "@shared/constants"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {MenuService} from "@services/menu.service"; -import {log_error} from "@shared/utils"; -import {AppService} from "@services/app.service"; -import {ProjectCategoriesApiService} from "@api/project-categories-api.service"; -import {IProjectCategoryViewModel} from "@interfaces/project-category"; - -@Component({ - selector: 'worklenz-categories', - templateUrl: './categories.component.html', - styleUrls: ['./categories.component.scss'] -}) -export class CategoriesComponent implements OnInit { - readonly colorCodes = ProjectsDefaultColorCodes; - - categories: IProjectCategoryViewModel[] = []; - filteredCategories: IProjectCategoryViewModel[] = []; - - loading = false; - labelsSearch: string | null = null; - - constructor( - private readonly app: AppService, - private readonly api: ProjectCategoriesApiService, - private readonly searchPipe: SearchByNamePipe, - private readonly cdr: ChangeDetectorRef, - public readonly menu: MenuService - ) { - this.app.setTitle("Manage Categories"); - } - - ngOnInit(): void { - void this.get(); - } - - trackByFn(index: number, category: IProjectCategoryViewModel) { - return category.id; - } - - async get() { - try { - this.loading = true; - const res = await this.api.get(); - if (res.done) { - this.categories = res.body; - this.filteredCategories = this.categories; - } - this.loading = false; - } catch (e) { - this.loading = false; - log_error(e); - } - - this.cdr.detectChanges(); - } - - async deleteCategory(category: IProjectCategoryViewModel) { - if (!category?.id) return; - try { - this.loading = true; - const res = await this.api.deleteById(category.id); - if (res.done) { - void this.get(); - } - this.loading = false; - } catch (e) { - this.loading = false; - log_error(e); - } - - this.cdr.detectChanges(); - } - - async updateColorCode(id?: string, color?: string) { - if (!id || !color) return; - try { - const res = await this.api.updateColor(id, color); - if (res.done) { - const label = this.categories.find(l => l.id === id); - if (label) - label.color_code = color; - this.cdr.markForCheck(); - } - } catch (e) { - log_error(e); - } - } - - searchCategories(val: string) { - this.filteredCategories = this.searchPipe.transform(this.categories, val || null) as IProjectCategoryViewModel[]; - this.cdr.markForCheck(); - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/change-password/change-password.component.html b/worklenz-frontend/src/app/administrator/settings/change-password/change-password.component.html deleted file mode 100644 index 9e5f37bb..00000000 --- a/worklenz-frontend/src/app/administrator/settings/change-password/change-password.component.html +++ /dev/null @@ -1,68 +0,0 @@ - -
- - - Current Password - - - - - - - - - - - - New Password - - - - - - - - - - - Confirm New Password - - - - - - - - - - - - New password should be {{passwordPolicy | lowercase}} - - - - - - - - -
-
diff --git a/worklenz-frontend/src/app/administrator/settings/change-password/change-password.component.scss b/worklenz-frontend/src/app/administrator/settings/change-password/change-password.component.scss deleted file mode 100644 index 5d78afec..00000000 --- a/worklenz-frontend/src/app/administrator/settings/change-password/change-password.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -[nz-form] { - max-width: 600px; -} diff --git a/worklenz-frontend/src/app/administrator/settings/change-password/change-password.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/change-password/change-password.component.spec.ts deleted file mode 100644 index 07486461..00000000 --- a/worklenz-frontend/src/app/administrator/settings/change-password/change-password.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ChangePasswordComponent} from './change-password.component'; - -describe('ChangePasswordComponent', () => { - let component: ChangePasswordComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ChangePasswordComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ChangePasswordComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/change-password/change-password.component.ts b/worklenz-frontend/src/app/administrator/settings/change-password/change-password.component.ts deleted file mode 100644 index 6712aef2..00000000 --- a/worklenz-frontend/src/app/administrator/settings/change-password/change-password.component.ts +++ /dev/null @@ -1,68 +0,0 @@ -import {Component} from '@angular/core'; -import {AbstractControlOptions, FormBuilder, FormGroup, Validators} from "@angular/forms"; -import {UsersService} from "@api/users.service"; -import {AppService} from "@services/app.service"; -import {PASSWORD_POLICY} from "@shared/constants"; - -@Component({ - selector: 'worklenz-change-password', - templateUrl: './change-password.component.html', - styleUrls: ['./change-password.component.scss'] -}) -export class ChangePasswordComponent { - passwordChangeForm!: FormGroup; - - oldPasswordVisible = false; - newPasswordVisible = false; - confirmPasswordVisible = false; - showTaskModal = false; - - loading = false; - - readonly passwordPolicy = PASSWORD_POLICY; - - constructor( - private usersService: UsersService, - private fb: FormBuilder, - private app: AppService - ) { - this.app.setTitle("Change Password"); - - this.passwordChangeForm = this.fb.group({ - password: [null, [Validators.required]], - new_password: [null, [Validators.required]], - confirm_password: [null, [Validators.required]] - }, - {validators: [this.confirmPasswordsValidator]} as unknown as AbstractControlOptions - ); - } - - async confirmPasswordsValidator(form: FormGroup) { - const password = form.controls['new_password'].value; - const confirmation = form.controls['confirm_password'].value; - - if (confirmation !== password) { - return form.controls['confirm_password'].setErrors({ - passwordMismatch: true - }); // set the error in the confirmation input/control - } - return form.controls['confirm_password']; - } - - async submitForm() { - const body = { - password: this.passwordChangeForm.value.password, - new_password: this.passwordChangeForm.value.new_password, - confirm_password: this.passwordChangeForm.value.confirm_password - }; - - if (body.password && body.new_password && body.confirm_password) { - this.passwordChangeForm.setErrors({invalid: false}); - const res: any = await this.usersService.changePassword(body); - if (res.done) { - this.passwordChangeForm.reset(); - this.showTaskModal = false; - } - } - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/clients/clients-routing.module.ts b/worklenz-frontend/src/app/administrator/settings/clients/clients-routing.module.ts deleted file mode 100644 index e40a2ee5..00000000 --- a/worklenz-frontend/src/app/administrator/settings/clients/clients-routing.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {ClientsComponent} from "./clients/clients.component"; - -const routes: Routes = [ - {path: "", component: ClientsComponent} -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class ClientsRoutingModule { -} diff --git a/worklenz-frontend/src/app/administrator/settings/clients/clients.module.ts b/worklenz-frontend/src/app/administrator/settings/clients/clients.module.ts deleted file mode 100644 index 6fa69a90..00000000 --- a/worklenz-frontend/src/app/administrator/settings/clients/clients.module.ts +++ /dev/null @@ -1,58 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; - -import {ClientsRoutingModule} from './clients-routing.module'; -import {ClientsComponent} from './clients/clients.component'; -import {NzPageHeaderModule} from 'ng-zorro-antd/page-header'; -import {NzButtonModule} from 'ng-zorro-antd/button'; -import {NzTableModule} from 'ng-zorro-antd/table'; -import {NzDividerModule} from 'ng-zorro-antd/divider'; -import {NzModalModule} from 'ng-zorro-antd/modal'; -import {NzFormModule} from 'ng-zorro-antd/form'; -import {ReactiveFormsModule} from '@angular/forms'; -import {NzInputModule} from 'ng-zorro-antd/input'; -import {NzPopconfirmModule} from 'ng-zorro-antd/popconfirm'; -import {NzBreadCrumbModule} from 'ng-zorro-antd/breadcrumb'; -import {NzIconModule} from 'ng-zorro-antd/icon'; -import {NzCardModule} from "ng-zorro-antd/card"; -import {NzLayoutModule} from 'ng-zorro-antd/layout'; -import {NzSpaceModule} from 'ng-zorro-antd/space'; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzSpinModule} from "ng-zorro-antd/spin"; -import {ToggleMenuButtonComponent} from "../../components/toggle-menu-button/toggle-menu-button.component"; - - -@NgModule({ - declarations: [ - ClientsComponent - ], - imports: [ - CommonModule, - ClientsRoutingModule, - NzPageHeaderModule, - NzButtonModule, - NzTableModule, - NzDividerModule, - NzModalModule, - NzFormModule, - ReactiveFormsModule, - NzInputModule, - NzPopconfirmModule, - NzBreadCrumbModule, - NzIconModule, - NzCardModule, - NzLayoutModule, - NzSpaceModule, - NzDrawerModule, - NzTypographyModule, - NzToolTipModule, - NzSkeletonModule, - NzSpinModule, - ToggleMenuButtonComponent - ] -}) -export class ClientsModule { -} diff --git a/worklenz-frontend/src/app/administrator/settings/clients/clients/clients.component.html b/worklenz-frontend/src/app/administrator/settings/clients/clients/clients.component.html deleted file mode 100644 index 137351b6..00000000 --- a/worklenz-frontend/src/app/administrator/settings/clients/clients/clients.component.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - Name - Projects - - - - - - {{ data.name }} - - - - {{(data.projects_count || 0) <= 0 ? 'No projects available' : (data.projects_count || 0) + ' Projects'}} - - - -
- - - - -
- - - -
-
- - - -
- - - - - - -
- - -
-
-
- - - - -
- - Name - - - - -
- -
-
-
diff --git a/worklenz-frontend/src/app/administrator/settings/clients/clients/clients.component.scss b/worklenz-frontend/src/app/administrator/settings/clients/clients/clients.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/settings/clients/clients/clients.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/clients/clients/clients.component.spec.ts deleted file mode 100644 index 5d882f19..00000000 --- a/worklenz-frontend/src/app/administrator/settings/clients/clients/clients.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ClientsComponent} from './clients.component'; - -describe('ClientsComponent', () => { - let component: ClientsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ClientsComponent] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(ClientsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/clients/clients/clients.component.ts b/worklenz-frontend/src/app/administrator/settings/clients/clients/clients.component.ts deleted file mode 100644 index 76cedfea..00000000 --- a/worklenz-frontend/src/app/administrator/settings/clients/clients/clients.component.ts +++ /dev/null @@ -1,181 +0,0 @@ -import {Component} from '@angular/core'; -import {FormBuilder, FormGroup, Validators} from "@angular/forms"; - -import {ClientsApiService} from "@api/clients-api.service"; - -import {IClientViewModel} from "@interfaces/api-models/client-view-model"; -import {IClient} from "@interfaces/client"; - -import {AppService} from "@services/app.service"; -import {IPaginationComponent} from "@interfaces/pagination-component"; -import {NzTableQueryParams} from "ng-zorro-antd/table"; -import {IClientsViewModel} from "@interfaces/api-models/clients-view-model"; -import {MenuService} from "@services/menu.service"; -import {DEFAULT_PAGE_SIZE} from "@shared/constants"; -import {UtilsService} from "@services/utils.service"; -import {log_error} from "@shared/utils"; - -@Component({ - selector: 'worklenz-clients', - templateUrl: './clients.component.html', - styleUrls: ['./clients.component.scss'] -}) -export class ClientsComponent implements IPaginationComponent { - form!: FormGroup; - searchForm!: FormGroup; - - showClientsModal = false; - - client: IClientViewModel = {}; - model: IClientsViewModel = {}; - - loading = false; - loadingSingleClient = false; - action = 'Create'; - - // Table sorting & pagination - total = 0; - pageSize = DEFAULT_PAGE_SIZE; - pageIndex = 1; - paginationSizes = [5, 10, 15, 20, 50, 100]; - sortField: string | null = null; - sortOrder: string | null = null; - - constructor( - private app: AppService, - private api: ClientsApiService, - private fb: FormBuilder, - public menu: MenuService, - private utilsService: UtilsService - ) { - this.app.setTitle("Manage Clients"); - - this.form = this.fb.group({ - name: [null, [Validators.required]] - }); - this.searchForm = this.fb.group({search: []}); - this.searchForm.valueChanges.subscribe(() => this.searchProjects()); - } - - async addClient() { - try { - const body: IClient = { - name: this.form.controls["name"].value - }; - - const res = await this.api.create(body); - if (res.done) { - this.closeModal(); - await this.getClients(); - } - } catch (e) { - log_error(e); - } - } - - async getClients() { - try { - this.loading = true; - const res = await this.api.get(this.pageIndex, this.pageSize, this.sortField, this.sortOrder, this.searchForm.value.search); - if (res.done) { - this.model = res.body; - this.total = this.model.total || 0; - - this.utilsService.handleLastIndex(this.total, this.model.data?.length || 0, this.pageIndex, - index => { - this.pageIndex = index; - this.getClients(); - }); - } - this.loading = false; - } catch (e) { - log_error(e); - this.loading = false; - } - } - - async getClient(id: string) { - try { - this.loadingSingleClient = true; - const res = await this.api.getById(id); - if (res.done) { - this.client = res.body; - this.form.controls["name"].setValue(this.client.name); - } - this.loadingSingleClient = false; - } catch (e) { - log_error(e); - this.loadingSingleClient = false; - } - } - - async updateClient() { - if (!this.client || !this.client.id) return; - try { - this.loadingSingleClient = true; - const body: IClient = { - name: this.form.controls["name"].value - }; - const res = await this.api.update(this.client.id, body); - this.loadingSingleClient = false; - if (res.done) { - this.closeModal(); - await this.getClients(); - } - } catch (e) { - this.loadingSingleClient = false; - log_error(e); - } - } - - async deleteClient(id: string | undefined) { - if (!id) return; - try { - const res = await this.api.delete(id); - if (res.done) { - await this.getClients(); - } - } catch (e) { - log_error(e); - } - } - - async editClient(id: string | undefined) { - if (!id) return; - await this.getClient(id); - this.action = 'Update'; - this.showClientsModal = true; - } - - closeModal() { - this.showClientsModal = false; - this.form.reset(); - this.action = 'Create'; - } - - async handleOk() { - if (this.client && this.client.id) { - await this.updateClient(); - } else { - await this.addClient(); - } - } - - async searchProjects() { - this.getClients(); - } - - async onQueryParamsChange(params: NzTableQueryParams) { - const {pageSize, pageIndex, sort} = params; - this.pageIndex = pageIndex; - this.pageSize = pageSize; - - const currentSort = sort.find(item => item.value !== null); - - this.sortField = (currentSort && currentSort.key) || null; - this.sortOrder = (currentSort && currentSort.value) || null; - - await this.getClients(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/settings/job-titles/job-titles-routing.module.ts b/worklenz-frontend/src/app/administrator/settings/job-titles/job-titles-routing.module.ts deleted file mode 100644 index e7b872ba..00000000 --- a/worklenz-frontend/src/app/administrator/settings/job-titles/job-titles-routing.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {JobTitlesComponent} from "./job-titles/job-titles.component"; - -const routes: Routes = [ - {path: "", component: JobTitlesComponent} -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class JobTitlesRoutingModule { -} diff --git a/worklenz-frontend/src/app/administrator/settings/job-titles/job-titles.module.ts b/worklenz-frontend/src/app/administrator/settings/job-titles/job-titles.module.ts deleted file mode 100644 index 11379bd2..00000000 --- a/worklenz-frontend/src/app/administrator/settings/job-titles/job-titles.module.ts +++ /dev/null @@ -1,57 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; - -import {JobTitlesRoutingModule} from './job-titles-routing.module'; -import {JobTitlesComponent} from './job-titles/job-titles.component'; -import {NzPageHeaderModule} from 'ng-zorro-antd/page-header'; -import {NzButtonModule} from 'ng-zorro-antd/button'; -import {NzTableModule} from 'ng-zorro-antd/table'; -import {NzDividerModule} from 'ng-zorro-antd/divider'; -import {NzModalModule} from 'ng-zorro-antd/modal'; -import {NzFormModule} from 'ng-zorro-antd/form'; -import {ReactiveFormsModule} from '@angular/forms'; -import {NzInputModule} from 'ng-zorro-antd/input'; -import {NzPopconfirmModule} from 'ng-zorro-antd/popconfirm'; -import {NzBreadCrumbModule} from 'ng-zorro-antd/breadcrumb'; -import {NzIconModule} from 'ng-zorro-antd/icon'; -import {NzCardModule} from "ng-zorro-antd/card"; -import {NzLayoutModule} from 'ng-zorro-antd/layout'; -import {NzSpaceModule} from 'ng-zorro-antd/space'; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzSpinModule} from "ng-zorro-antd/spin"; -import {ToggleMenuButtonComponent} from "../../components/toggle-menu-button/toggle-menu-button.component"; - -@NgModule({ - declarations: [ - JobTitlesComponent - ], - imports: [ - CommonModule, - JobTitlesRoutingModule, - NzPageHeaderModule, - NzButtonModule, - NzTableModule, - NzDividerModule, - NzModalModule, - NzFormModule, - ReactiveFormsModule, - NzInputModule, - NzPopconfirmModule, - NzBreadCrumbModule, - NzIconModule, - NzCardModule, - NzSpaceModule, - NzLayoutModule, - NzTypographyModule, - NzDrawerModule, - NzToolTipModule, - NzSkeletonModule, - NzSpinModule, - ToggleMenuButtonComponent - ] -}) -export class JobTitlesModule { -} diff --git a/worklenz-frontend/src/app/administrator/settings/job-titles/job-titles/job-titles.component.html b/worklenz-frontend/src/app/administrator/settings/job-titles/job-titles/job-titles.component.html deleted file mode 100644 index 0048f06b..00000000 --- a/worklenz-frontend/src/app/administrator/settings/job-titles/job-titles/job-titles.component.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - Name - - - - - {{ data.name }} - -
- - - - -
- - - -
-
- - - -
- - - - - - -
- - -
-
-
- - - - -
- - Name - - - - - -
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/settings/job-titles/job-titles/job-titles.component.scss b/worklenz-frontend/src/app/administrator/settings/job-titles/job-titles/job-titles.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/settings/job-titles/job-titles/job-titles.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/job-titles/job-titles/job-titles.component.spec.ts deleted file mode 100644 index 9efb6c85..00000000 --- a/worklenz-frontend/src/app/administrator/settings/job-titles/job-titles/job-titles.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {JobTitlesComponent} from './job-titles.component'; - -describe('JobTitlesComponent', () => { - let component: JobTitlesComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [JobTitlesComponent] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(JobTitlesComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/job-titles/job-titles/job-titles.component.ts b/worklenz-frontend/src/app/administrator/settings/job-titles/job-titles/job-titles.component.ts deleted file mode 100644 index b7fb2687..00000000 --- a/worklenz-frontend/src/app/administrator/settings/job-titles/job-titles/job-titles.component.ts +++ /dev/null @@ -1,183 +0,0 @@ -import {Component} from '@angular/core'; -import {NzTableQueryParams} from "ng-zorro-antd/table"; - -import {IJobTitle} from "@interfaces/job-title"; -import {JobTitlesApiService} from "@api/job-titles-api.service"; -import {AppService} from "@services/app.service"; -import {IPaginationComponent} from "@interfaces/pagination-component"; -import {IJobTitlesViewModel} from "@interfaces/api-models/job-titles-view-model"; -import {FormBuilder, FormGroup, Validators} from "@angular/forms"; -import {MenuService} from "@services/menu.service"; -import {DEFAULT_PAGE_SIZE} from "@shared/constants"; -import {UtilsService} from "@services/utils.service"; -import {log_error} from "@shared/utils"; - -@Component({ - selector: 'worklenz-job-titles', - templateUrl: './job-titles.component.html', - styleUrls: ['./job-titles.component.scss'] -}) -export class JobTitlesComponent implements IPaginationComponent { - form!: FormGroup; - searchForm!: FormGroup; - - showJobTitlesModal = false; - - model: IJobTitlesViewModel = {}; - jobTitle: IJobTitle = {}; - - loading = false; - loadingSingle = false; - action = 'Create'; - - // Table sorting & pagination - total = 0; - pageSize = DEFAULT_PAGE_SIZE; - pageIndex = 1; - paginationSizes = [5, 10, 15, 20, 50, 100]; - sortField: string | null = null; - sortOrder: string | null = null; - - constructor( - private api: JobTitlesApiService, - private fb: FormBuilder, - private app: AppService, - public menu: MenuService, - private utilsService: UtilsService - ) { - this.app.setTitle("Manage Job Titles"); - - this.form = this.fb.group({ - name: [null, [Validators.required]] - }); - this.searchForm = this.fb.group({ - search: [''] - }) - this.searchForm.valueChanges.subscribe(() => { - this.searchJobTitles().then(r => r); - }) - } - - async onQueryParamsChange(params: NzTableQueryParams) { - const {pageSize, pageIndex, sort} = params; - this.pageIndex = pageIndex; - this.pageSize = pageSize; - - const currentSort = sort.find(item => item.value !== null); - - this.sortField = (currentSort && currentSort.key) || null; - this.sortOrder = (currentSort && currentSort.value) || null; - - await this.getJobTitles(); - } - - async addJobTitle() { - try { - const body: IJobTitle = { - name: this.form.controls["name"].value - }; - - const res = await this.api.create(body); - if (res.done) { - this.closeModal(); - await this.getJobTitles(); - } - } catch (e) { - log_error(e); - } - } - - async getJobTitles() { - try { - this.loading = true; - const res = await this.api.get(this.pageIndex, this.pageSize, this.sortField, this.sortOrder, this.searchForm.value.search); - if (res.done) { - this.model = res.body; - this.total = this.model.total || 0; - - this.utilsService.handleLastIndex(this.total, this.model.data?.length || 0, this.pageIndex, - index => { - this.pageIndex = index; - this.getJobTitles(); - }); - } - this.loading = false; - } catch (e) { - log_error(e); - this.loading = false; - } - } - - async getJobTitle(id: string) { - try { - this.loadingSingle = true; - const res = await this.api.getById(id); - if (res.done) { - this.jobTitle = res.body; - this.form.controls["name"].setValue(this.jobTitle.name); - } - this.loadingSingle = false; - } catch (e) { - log_error(e); - this.loadingSingle = false; - } - } - - async updateJobTitle() { - if (!this.jobTitle || !this.jobTitle.id) return; - try { - this.loadingSingle = true; - const body: IJobTitle = { - name: this.form.controls["name"].value - }; - const res = await this.api.update(this.jobTitle.id, body); - this.loadingSingle = false; - if (res.done) { - this.closeModal(); - await this.getJobTitles(); - } - } catch (e) { - this.loadingSingle = false; - log_error(e); - } - } - - async delete(id: string | undefined) { - if (!id) return; - try { - const res = await this.api.delete(id); - if (res.done) { - await this.getJobTitles(); - } - } catch (e) { - log_error(e); - } - } - - async edit(id: string | undefined) { - if (!id) return; - await this.getJobTitle(id); - this.action = 'Update'; - this.showJobTitlesModal = true; - } - - closeModal() { - this.showJobTitlesModal = false; - this.form.reset(); - this.jobTitle = {}; - this.action = 'Create'; - } - - async handleOk() { - if (this.jobTitle && this.jobTitle.id) { - await this.updateJobTitle(); - } else { - await this.addJobTitle(); - } - } - - async searchJobTitles() { - this.getJobTitles().then(r => r); - } - -} diff --git a/worklenz-frontend/src/app/administrator/settings/labels/labels.component.html b/worklenz-frontend/src/app/administrator/settings/labels/labels.component.html deleted file mode 100644 index ff33c5a2..00000000 --- a/worklenz-frontend/src/app/administrator/settings/labels/labels.component.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - Label - Associated tasks - - - - - - {{data.name}} - - -
    -
  • - {{data.name}} -
  • -
-
- - {{data.usage}} - -
- - - -
- - - -
-
-
- - - -
- - - - - - -
- -
-
diff --git a/worklenz-frontend/src/app/administrator/settings/labels/labels.component.scss b/worklenz-frontend/src/app/administrator/settings/labels/labels.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/settings/labels/labels.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/labels/labels.component.spec.ts deleted file mode 100644 index 5ef2baa4..00000000 --- a/worklenz-frontend/src/app/administrator/settings/labels/labels.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {LabelsComponent} from './labels.component'; - -describe('LabelsComponent', () => { - let component: LabelsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [LabelsComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(LabelsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/labels/labels.component.ts b/worklenz-frontend/src/app/administrator/settings/labels/labels.component.ts deleted file mode 100644 index dbf916a1..00000000 --- a/worklenz-frontend/src/app/administrator/settings/labels/labels.component.ts +++ /dev/null @@ -1,96 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit} from '@angular/core'; -import {log_error} from "@shared/utils"; -import {TaskLabelsApiService} from "@api/task-labels-api.service"; -import {ITaskLabel} from "@interfaces/task-label"; -import {MenuService} from "@services/menu.service"; -import {ALPHA_CHANNEL, ProjectsDefaultColorCodes} from "@shared/constants"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {AppService} from "@services/app.service"; - -@Component({ - selector: 'worklenz-labels', - templateUrl: './labels.component.html', - styleUrls: ['./labels.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class LabelsComponent implements OnInit { - colorCodes = ProjectsDefaultColorCodes; - labels: ITaskLabel[] = []; - filteredLabels: ITaskLabel[] = []; - - loading = false; - alpha = ALPHA_CHANNEL; - labelsSearch: string | null = null; - - constructor( - private readonly app: AppService, - private readonly api: TaskLabelsApiService, - private readonly searchPipe: SearchByNamePipe, - private readonly cdr: ChangeDetectorRef, - public readonly menu: MenuService - ) { - this.app.setTitle("Manage Labels"); - } - - ngOnInit(): void { - void this.get(); - } - - trackByFn(index: number, label: ITaskLabel) { - return label.id; - } - - async get() { - try { - this.loading = true; - const res = await this.api.get(); - if (res.done) { - this.labels = res.body; - this.filteredLabels = this.labels; - } - this.loading = false; - } catch (e) { - this.loading = false; - log_error(e); - } - - this.cdr.detectChanges(); - } - - async deleteLabel(label: ITaskLabel) { - if (!label?.id) return; - try { - this.loading = true; - const res = await this.api.deleteById(label.id); - if (res.done) { - void this.get(); - } - this.loading = false; - } catch (e) { - this.loading = false; - log_error(e); - } - - this.cdr.detectChanges(); - } - - async updateColorCode(id?: string, color?: string) { - if (!id || !color) return; - try { - const res = await this.api.updateColor(id, color); - if (res.done) { - const label = this.labels.find(l => l.id === id); - if (label) - label.color_code = color; - this.cdr.markForCheck(); - } - } catch (e) { - log_error(e); - } - } - - searchLabels(val: string) { - this.filteredLabels = this.searchPipe.transform(this.labels, val || null) as ITaskLabel[]; - this.cdr.markForCheck(); - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/language-and-region/language-and-region.component.html b/worklenz-frontend/src/app/administrator/settings/language-and-region/language-and-region.component.html deleted file mode 100644 index ee82c15f..00000000 --- a/worklenz-frontend/src/app/administrator/settings/language-and-region/language-and-region.component.html +++ /dev/null @@ -1,35 +0,0 @@ - - -
- - Language - - - - {{item}} - - - - - - Time zone - - - -
- {{item.name}} {{item.abbrev}} -
-
-
-
-
- - - - - -
-
-
diff --git a/worklenz-frontend/src/app/administrator/settings/language-and-region/language-and-region.component.scss b/worklenz-frontend/src/app/administrator/settings/language-and-region/language-and-region.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/settings/language-and-region/language-and-region.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/language-and-region/language-and-region.component.spec.ts deleted file mode 100644 index 38c74e93..00000000 --- a/worklenz-frontend/src/app/administrator/settings/language-and-region/language-and-region.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {LanguageAndRegionComponent} from './language-and-region.component'; - -describe('LanguageAndRegionComponent', () => { - let component: LanguageAndRegionComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [LanguageAndRegionComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(LanguageAndRegionComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/language-and-region/language-and-region.component.ts b/worklenz-frontend/src/app/administrator/settings/language-and-region/language-and-region.component.ts deleted file mode 100644 index 473482a6..00000000 --- a/worklenz-frontend/src/app/administrator/settings/language-and-region/language-and-region.component.ts +++ /dev/null @@ -1,70 +0,0 @@ -import {Component, OnInit} from '@angular/core'; -import {AppService} from "@services/app.service"; -import {TimezonesApiService} from "@api/timezones-api.service"; -import {FormBuilder, FormGroup, Validators} from "@angular/forms"; -import {ITimezone} from "@interfaces/timezone"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-language-and-region', - templateUrl: './language-and-region.component.html', - styleUrls: ['./language-and-region.component.scss'] -}) -export class LanguageAndRegionComponent implements OnInit { - form!: FormGroup; - - loading = false; - updating = false; - options: string[] = ['English']; - - timezones: ITimezone[] = []; - - constructor( - private app: AppService, - private api: TimezonesApiService, - private fb: FormBuilder, - private auth: AuthService - ) { - this.app.setTitle("Language & Region"); - - const profile = this.auth.getCurrentSession(); - - this.form = this.fb.group({ - language: ['English', Validators.required], - timezone: [profile?.timezone || null, Validators.required] - }); - } - - ngOnInit(): void { - this.get(); - } - - async submit() { - if (this.form.invalid) - return this.app.displayErrorsOf(this.form); - - try { - this.updating = true; - const res = await this.api.update(this.form.value); - if (res.done) { - await this.auth.authorize(); - } - this.updating = false; - } catch (e) { - this.updating = false; - } - } - - async get() { - try { - this.loading = true; - const res = await this.api.get(); - if (res.done) { - this.timezones = res.body; - } - this.loading = false; - } catch (e) { - this.loading = false; - } - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings-routing.module.ts b/worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings-routing.module.ts deleted file mode 100644 index dc27f5b8..00000000 --- a/worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings-routing.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {NotificationSettingsComponent} from "./notification-settings/notification-settings.component"; - -const routes: Routes = [ - {path: "", component: NotificationSettingsComponent} -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class NotificationSettingsRoutingModule { -} diff --git a/worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings.module.ts b/worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings.module.ts deleted file mode 100644 index a03f32bf..00000000 --- a/worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings.module.ts +++ /dev/null @@ -1,28 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; - -import {NotificationSettingsRoutingModule} from './notification-settings-routing.module'; -import {NotificationSettingsComponent} from './notification-settings/notification-settings.component'; -import {NzCardModule} from "ng-zorro-antd/card"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {NzCheckboxModule} from "ng-zorro-antd/checkbox"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzDividerModule} from "ng-zorro-antd/divider"; - - -@NgModule({ - declarations: [ - NotificationSettingsComponent - ], - imports: [ - CommonModule, - NotificationSettingsRoutingModule, - NzCardModule, - NzSkeletonModule, - NzCheckboxModule, - NzTypographyModule, - NzDividerModule - ] -}) -export class NotificationSettingsModule { -} diff --git a/worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings/notification-settings.component.html b/worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings/notification-settings.component.html deleted file mode 100644 index 8eafdea0..00000000 --- a/worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings/notification-settings.component.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - -

- This includes new task assignments. -

- - -
- - - -

- Every evening, you will receive a summary of recent activity in tasks. -

- - -
- - - -

- Pop up notifications can be disabled by your browser. Change your browser settings to allow them. -

- - -
- - - -

- You’ll see counts for each notification. -

-
- -
-
diff --git a/worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings/notification-settings.component.scss b/worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings/notification-settings.component.scss deleted file mode 100644 index a4a6c65a..00000000 --- a/worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings/notification-settings.component.scss +++ /dev/null @@ -1,8 +0,0 @@ -h4 { - position: relative; - top: 2px; -} - -label { - user-select: none; -} diff --git a/worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings/notification-settings.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings/notification-settings.component.spec.ts deleted file mode 100644 index f3e32efd..00000000 --- a/worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings/notification-settings.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {NotificationSettingsComponent} from './notification-settings.component'; - -describe('NotificationSettingsComponent', () => { - let component: NotificationSettingsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [NotificationSettingsComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(NotificationSettingsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings/notification-settings.component.ts b/worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings/notification-settings.component.ts deleted file mode 100644 index 9c2bcbbd..00000000 --- a/worklenz-frontend/src/app/administrator/settings/notification-settings/notification-settings/notification-settings.component.ts +++ /dev/null @@ -1,79 +0,0 @@ -import {Component, NgZone, OnInit} from '@angular/core'; -import {AppService} from "@services/app.service"; -import {ProfileSettingsApiService} from "@api/profile-settings-api.service"; -import {INotificationSettings} from "@interfaces/notification-settings"; -import {NotificationSettingsService} from "@services/notification-settings.service"; - -@Component({ - selector: 'worklenz-notification-settings', - templateUrl: './notification-settings.component.html', - styleUrls: ['./notification-settings.component.scss'] -}) -export class NotificationSettingsComponent implements OnInit { - loading = false; - updating = false; - - model: INotificationSettings = {}; - - constructor( - private app: AppService, - private api: ProfileSettingsApiService, - private settingsService: NotificationSettingsService, - private ngZone: NgZone - ) { - this.app.setTitle("Notification Settings"); - } - - ngOnInit() { - void this.get(); - } - - async get() { - try { - this.loading = true; - const res = await this.api.getNotificationSettings(); - if (res.done) { - this.model = res.body; - } - this.loading = false; - } catch (e) { - this.loading = false; - } - } - - async update() { - try { - this.updating = true; - const res = await this.api.updateNotificationSettings(this.model); - if (res.done) { - this.model = res.body; - this.settingsService.settings = res.body; - } - this.updating = false; - } catch (e) { - this.updating = false; - } - } - - updateSettings() { - if (this.updating) return; - void this.update(); - } - - requestPermissions() { - if (this.model.popup_notifications_enabled && Notification.permission === "default") { - this.askNotificationPermission(); - } - } - - askNotificationPermission() { - this.ngZone.runOutsideAngular(() => { - // Let's check if the browser supports notifications - if (!('Notification' in window)) { - console.log("This browser does not support notifications."); - return; - } - void Notification.requestPermission(); - }); - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/profile/profile.component.html b/worklenz-frontend/src/app/administrator/settings/profile/profile.component.html deleted file mode 100644 index fbafe347..00000000 --- a/worklenz-frontend/src/app/administrator/settings/profile/profile.component.html +++ /dev/null @@ -1,59 +0,0 @@ - - - -
-
-
- - -
- - -
{{uploading ? 'Uploading...' : 'Upload'}}
-
- - -
-
-
- -
- - Name - - - - - - E-mail - - - - - - - - - - -

- - Joined {{profile?.joined_date | fromNow}} - -

-

- - Last updated {{profile?.last_updated | fromNow}} - -

- -
-
-
diff --git a/worklenz-frontend/src/app/administrator/settings/profile/profile.component.scss b/worklenz-frontend/src/app/administrator/settings/profile/profile.component.scss deleted file mode 100644 index 5d50a1d6..00000000 --- a/worklenz-frontend/src/app/administrator/settings/profile/profile.component.scss +++ /dev/null @@ -1,15 +0,0 @@ -[nz-form] { - max-width: 600px; -} - -.avatar-upload-icon { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - display: flex; - align-items: center; - justify-content: center; - background: #ffffff9e; -} diff --git a/worklenz-frontend/src/app/administrator/settings/profile/profile.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/profile/profile.component.spec.ts deleted file mode 100644 index 92ffce16..00000000 --- a/worklenz-frontend/src/app/administrator/settings/profile/profile.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {ProfileComponent} from './profile.component'; - -describe('ProfileComponent', () => { - let component: ProfileComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ProfileComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ProfileComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/profile/profile.component.ts b/worklenz-frontend/src/app/administrator/settings/profile/profile.component.ts deleted file mode 100644 index 5999980c..00000000 --- a/worklenz-frontend/src/app/administrator/settings/profile/profile.component.ts +++ /dev/null @@ -1,133 +0,0 @@ -import {ApplicationRef, Component, OnInit} from '@angular/core'; -import {FormBuilder, FormGroup, Validators} from "@angular/forms"; - -import {ProfileSettingsApiService} from "@api/profile-settings-api.service"; -import {IProfileSettings} from "@interfaces/profile-settings"; -import {AppService} from "@services/app.service"; -import {AuthService} from "@services/auth.service"; -import {dispatchProfilePictureChange, dispatchProfileUpdate} from "@shared/events"; -import {getBase64, log_error} from "@shared/utils"; -import {AttachmentsApiService} from "@api/attachments-api.service"; - -@Component({ - selector: 'worklenz-profile', - templateUrl: './profile.component.html', - styleUrls: ['./profile.component.scss'] -}) -export class ProfileComponent implements OnInit { - form!: FormGroup; - - model: IProfileSettings = {}; - - loading = false; - updating = false; - uploading = false; - avatarUrl: string | null = null; - - constructor( - private fb: FormBuilder, - private api: ProfileSettingsApiService, - private app: AppService, - private auth: AuthService, - private attachmentsApi: AttachmentsApiService, - private ref: ApplicationRef - ) { - this.app.setTitle("Profile Settings"); - - this.form = this.fb.group({ - name: [null, Validators.required], - email: [null, Validators.required] - }); - - this.avatarUrl = this.profile?.avatar_url || null; - this.form.controls["email"].disable(); - } - - get profile() { - return this.auth.getCurrentSession(); - } - - async ngOnInit() { - await this.get(); - } - - isInvalidForm() { - return this.form.invalid; - } - - async get() { - this.loading = true; - try { - const res = await this.api.get(); - if (res.done) { - this.model = res.body; - this.form.controls["name"].setValue(this.model.name); - this.form.controls["email"].setValue(this.model.email); - if (this.profile?.is_google) - this.form.controls["email"].disable(); - } - this.loading = false; - } catch (e) { - this.loading = false; - log_error(e); - } - } - - async submit() { - if (this.form.invalid) { - this.app.displayErrorsOf(this.form); - return; - } - - this.updating = true; - try { - const body = { - name: this.form.controls["name"].value, - // email: this.form.controls["email"].value - }; - const res = await this.api.update(body); - if (res.done) { - await this.get(); - await this.auth.authorize(); - dispatchProfileUpdate(); - } - this.updating = false; - } catch (e) { - this.updating = false; - log_error(e); - } - } - - async uploadFile(input: HTMLInputElement) { - if (this.uploading) return; - - try { - const files = input.files || []; - - if (!files || !files.length) return; - - const file = files[0]; - - this.uploading = true; - - const base64 = await getBase64(file); - const res = await this.attachmentsApi.createAvatarAttachment({ - file: base64 as string, - file_name: file.name, - size: file.size - }); - if (res.done) { - await this.auth.authorize(); - dispatchProfilePictureChange(); - this.avatarUrl = res.body.url; - } - this.uploading = false; - } catch (e) { - this.uploading = false; - } - - // Reset file input - const dt = new DataTransfer(); - input.files = dt.files; - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/add-task-input/add-task-input.component.html b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/add-task-input/add-task-input.component.html deleted file mode 100644 index 2bd6c591..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/add-task-input/add-task-input.component.html +++ /dev/null @@ -1,21 +0,0 @@ - -
- -
-
- - {{label}} -
-
diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/add-task-input/add-task-input.component.scss b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/add-task-input/add-task-input.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/add-task-input/add-task-input.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/add-task-input/add-task-input.component.spec.ts deleted file mode 100644 index bd14df88..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/add-task-input/add-task-input.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { AddTaskInputComponent } from './add-task-input.component'; - -describe('AddTaskInputComponent', () => { - let component: AddTaskInputComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [AddTaskInputComponent] - }); - fixture = TestBed.createComponent(AddTaskInputComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/add-task-input/add-task-input.component.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/add-task-input/add-task-input.component.ts deleted file mode 100644 index 2c3368e7..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/add-task-input/add-task-input.component.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - Input, - NgZone, - Output, - ViewChild -} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms"; -import {smallId} from "@shared/utils"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {Socket} from "ngx-socket-io"; -import {AuthService} from "@services/auth.service"; -import {PtTaskListHashMapService} from "../../services/pt-task-list-hash-map.service"; -import {PtTaskListService} from "../../services/pt-task-list.service"; -import {DRAWER_ANIMATION_INTERVAL} from "@shared/constants"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {SocketEvents} from "@shared/socket-events"; -import {IPTTask, IPTTaskCreateRequest} from "../../interfaces"; - -@Component({ - selector: 'worklenz-add-task-input', - standalone: true, - imports: [CommonModule, ReactiveFormsModule, NzInputModule, NzIconModule], - templateUrl: './add-task-input.component.html', - styleUrls: ['./add-task-input.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class AddTaskInputComponent { - @ViewChild('taskInput', {static: false}) taskInput!: ElementRef; - readonly form!: FormGroup; - - @Input() subTaskInput = false; - @Input() templateId: string | null = null; - @Input() parentTask: string | null = null; - @Input() groupId: string | null = null; - @Input() label = "Add Task"; - - @Output() focusChange: EventEmitter = new EventEmitter(); - - taskInputVisible = false; - creating = false; - - protected readonly id = smallId(4); - - private readonly _session: ILocalSession | null = null; - - constructor( - private readonly socket: Socket, - private readonly auth: AuthService, - private readonly fb: FormBuilder, - private readonly service: PtTaskListService, - private readonly ngZone: NgZone, - private readonly cdr: ChangeDetectorRef, - private readonly map: PtTaskListHashMapService, - ) { - this.form = this.fb.group({ - name: [null, [Validators.required, Validators.pattern(/^(\s+\S+\s*)*(?!\s).*$/)]] - }); - this._session = this.auth.getCurrentSession(); - } - - focusTaskInput() { - this.taskInputVisible = true; - this.focusChange.emit(this.taskInputVisible); - - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.taskInput?.nativeElement.focus(); - this.taskInput?.nativeElement.select(); - }, 100); // wait for the animation end - }); - } - - addTaskInputBlur() { - this.taskInputVisible = false; - this.focusChange.emit(this.taskInputVisible); - } - - async onInputBlur() { - if (this.isValidInput()) { - await this.addInstantTask(); - return; - } - this.addTaskInputBlur(); - } - - private createRequest() { - if (!this.templateId || !this._session) return null; - - const sess = this._session; - const body: IPTTaskCreateRequest = { - name: this.form.value.name, - template_id: this.templateId, - reporter_id: sess.id, - team_id: sess.team_id - }; - - const groupBy = this.service.getCurrentGroup(); - - if (groupBy.value === this.service.GROUP_BY_STATUS_VALUE) { - body.status_id = this.groupId || undefined; - } else if (groupBy.value === this.service.GROUP_BY_PRIORITY_VALUE) { - body.priority_id = this.groupId || undefined; - } else if (groupBy.value === this.service.GROUP_BY_PHASE_VALUE) { - body.phase_id = this.groupId || undefined; - } - - if (this.parentTask) { - body.parent_task_id = this.parentTask; - } - return body; - } - - private isValidInput() { - return this.form.valid && this.form.value.name.trim().length; - } - - async addInstantTask() { - if (this.creating) return; - if (!this.templateId || !this._session) return; - - if (this.isValidInput()) { - try { - const req = this.createRequest(); - if (!req) return; - this.creating = true; - this.socket.emit(SocketEvents.PT_QUICK_TASK.toString(), JSON.stringify(req)); - this.socket.once(SocketEvents.PT_QUICK_TASK.toString(), (task: IPTTask) => { - this.creating = false; - this.onNewTaskReceived(task); - }); - - } catch (e) { - this.creating = false; - } - - this.cdr.markForCheck(); - } - } - - public reset(scroll = true) { - this.creating = false; - - this.form.controls["name"].setValue(null); - this.taskInputVisible = true; - - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.taskInput?.nativeElement.focus(); - if (scroll) - window.scrollTo(0, document.body.scrollHeight); - }, DRAWER_ANIMATION_INTERVAL); // wait for the animation end - }); - - this.cdr.markForCheck(); - } - - private onNewTaskReceived(task: IPTTask) { - if (this.groupId && task.id) { - if (this.map.has(task.id)) return; - this.service.addTask(task, this.groupId); - this.reset(false); - } - } - - -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/context-menu/context-menu.component.html b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/context-menu/context-menu.component.html deleted file mode 100644 index 6557e8fc..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/context-menu/context-menu.component.html +++ /dev/null @@ -1,29 +0,0 @@ - -
    - - - -
  • -
      -
    • - - - {{item?.name || null}} - -
    • -
    -
  • -
    - -
  • - - Delete -
  • -
-
- - - Move to - diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/context-menu/context-menu.component.scss b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/context-menu/context-menu.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/context-menu/context-menu.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/context-menu/context-menu.component.spec.ts deleted file mode 100644 index 0837cb70..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/context-menu/context-menu.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ContextMenuComponent } from './context-menu.component'; - -describe('ContextMenuComponent', () => { - let component: ContextMenuComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ContextMenuComponent] - }); - fixture = TestBed.createComponent(ContextMenuComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/context-menu/context-menu.component.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/context-menu/context-menu.component.ts deleted file mode 100644 index ed0ebefd..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/context-menu/context-menu.component.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Input, - OnDestroy, - ViewChild -} from '@angular/core'; -import {NzContextMenuService, NzDropdownMenuComponent} from "ng-zorro-antd/dropdown"; -import {IPTTask, IPTTaskListContextMenuEvent, IPTTaskListGroup} from "../../interfaces"; -import {Subject, takeUntil} from "rxjs"; -import {PtTaskListService} from "../../services/pt-task-list.service"; -import {PtTaskListHashMapService} from "../../services/pt-task-list-hash-map.service"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {PtTasksApiService} from "@api/pt-tasks-api.service"; - -@Component({ - selector: 'worklenz-context-menu', - templateUrl: './context-menu.component.html', - styleUrls: ['./context-menu.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ContextMenuComponent implements OnDestroy { - @ViewChild('contextMenuDropdown', {static: false}) contextMenuDropdown!: NzDropdownMenuComponent; - @Input() templateId: string | null = null; - @Input() groups: IPTTaskListGroup[] = []; - - protected deleting = false; - protected hasSubTasks = false; - - selectedTask: IPTTask | null = null; - - private readonly destroy$ = new Subject(); - - constructor( - private readonly contextMenuService: NzContextMenuService, - private readonly service: PtTaskListService, - private readonly map: PtTaskListHashMapService, - private readonly api: PtTasksApiService, - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - ) { - this.service.onContextMenu$ - .pipe(takeUntil(this.destroy$)) - .subscribe(value => { - this.onContextMenu(value); - }); - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } - - private onContextMenu(value: IPTTaskListContextMenuEvent) { - this.selectedTask = value.task; - - this.map.deselectAll(); - this.map.selectTask(value.task); - - this.hasSubTasks = this.isSelectionHasSubTasks(); - - this.cdr.detectChanges(); - - this.contextMenuService.create(value.event, this.contextMenuDropdown); - } - - private isSelectionHasSubTasks() { - return this.map.getSelectedTasks().some(t => t.is_sub_task); - } - - changeGroup(toGroupId: string) { - if (!this.selectedTask) return; - const groupBy = this.service.getCurrentGroup(); - if (groupBy.value === this.service.GROUP_BY_STATUS_VALUE) { - this.handleStatusChange(toGroupId, this.selectedTask.id); - } else if (groupBy.value === this.service.GROUP_BY_PRIORITY_VALUE) { - this.handlePriorityChange(toGroupId, this.selectedTask.id); - } else if (groupBy.value === this.service.GROUP_BY_PHASE_VALUE) { - this.handlePhaseChange(toGroupId, this.selectedTask.id); - } - } - - handleStatusChange(statusId: string, taskId?: string) { - if (!taskId) return; - this.socket.emit(SocketEvents.PT_TASK_STATUS_CHANGE.toString(), JSON.stringify({ - task_id: taskId, - status_id: statusId - })); - } - - handlePriorityChange(priorityId: string, taskId?: string) { - if (!taskId) return; - this.socket.emit(SocketEvents.PT_TASK_PRIORITY_CHANGE.toString(), JSON.stringify({ - task_id: taskId, - priority_id: priorityId - })); - } - - handlePhaseChange(phaseId: string, taskId?: string) { - if (!taskId) return; - this.socket.emit(SocketEvents.PT_TASK_PHASE_CHANGE.toString(), { - task_id: taskId, - phase_id: phaseId - }); - } - - async delete() { - if (this.deleting) return; - try { - this.deleting = true; - const tasks = this.map.getSelectedTaskIds(); - const res = await this.api.bulkDelete({tasks}, this.templateId as string); - if (res.done) { - for (const taskId of res.body.deleted_tasks) { - this.service.deleteTask(taskId); - } - } - this.deleting = false; - } catch (e) { - this.deleting = false; - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/group-filter/group-filter.component.html b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/group-filter/group-filter.component.html deleted file mode 100644 index bd233da7..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/group-filter/group-filter.component.html +++ /dev/null @@ -1,64 +0,0 @@ -
-
-
- - - - - - - - - - - - - - - - - - - -
    -
  • - {{item.label}} -
  • -
-
- - - - - - -
-
- diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/group-filter/group-filter.component.scss b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/group-filter/group-filter.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/group-filter/group-filter.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/group-filter/group-filter.component.spec.ts deleted file mode 100644 index 825eb3ef..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/group-filter/group-filter.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { GroupFilterComponent } from './group-filter.component'; - -describe('GroupFilterComponent', () => { - let component: GroupFilterComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [GroupFilterComponent] - }); - fixture = TestBed.createComponent(GroupFilterComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/group-filter/group-filter.component.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/group-filter/group-filter.component.ts deleted file mode 100644 index fe60c1f1..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/group-filter/group-filter.component.ts +++ /dev/null @@ -1,109 +0,0 @@ -import {ChangeDetectorRef, Component, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output} from '@angular/core'; -import {IGroupByOption} from "../../../../modules/task-list-v2/interfaces"; -import {PtTaskListService} from "../../services/pt-task-list.service"; -import {TasksApiService} from "@api/tasks-api.service"; -import {Socket} from "ngx-socket-io"; -import {UtilsService} from "@services/utils.service"; -import {ProjectPhasesService} from "@services/project-phases.service"; -import {AuthService} from "@services/auth.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {DRAWER_ANIMATION_INTERVAL} from "@shared/constants"; - -@Component({ - selector: 'worklenz-group-filter', - templateUrl: './group-filter.component.html', - styleUrls: ['./group-filter.component.scss'] -}) -export class GroupFilterComponent { - @Input() templateId!: string; - - @Output() onGroupBy = new EventEmitter(); - @Output() onFilterSearch = new EventEmitter(); - @Output() onPhaseSettingsClick = new EventEmitter(); - @Output() onCreateStatusClick = new EventEmitter(); - - readonly ASCEND = "ascend"; - readonly DESCEND = "descend"; - readonly COUNTS_LABELS_STYLE = {backgroundColor: '#1890ff', color: '#fff'}; - - taskSearch: string | null = null; - - get selectedGroup() { - return this.service.getCurrentGroup(); - } - - get phaseLabel() { - return this.phaseService.label; - } - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly tasksApi: TasksApiService, - private readonly socket: Socket, - private readonly utils: UtilsService, - private readonly phaseService: ProjectPhasesService, - public readonly service: PtTaskListService, - public readonly auth: AuthService - ) { - } - - changeGroup(item: IGroupByOption) { - this.service.setCurrentGroup(item); - this.onGroupBy.emit(item); - } - - isGroupByStatus() { - return this.selectedGroup.value === this.service.GROUP_BY_STATUS_VALUE; - } - - isGroupByPhase() { - return this.selectedGroup.value === this.service.GROUP_BY_PHASE_VALUE; - } - - trackById(index: number, item: any) { - return item.id; - } - - private toIdsMap(array: any[]) { - return array.map(s => s.id).join("+"); - } - - - search() { - if (!this.taskSearch) return; - this.onFilterSearch.emit(encodeURIComponent(this.taskSearch)); - // To close the search dropdown - document.body.click(); - } - - reset() { - if (!this.taskSearch) return; - this.taskSearch = null; - this.onFilterSearch.emit(this.taskSearch); - this.ngZone.runOutsideAngular(() => { - // To close the search dropdown - document.body.click(); - }); - } - - onSearchDropdownVisibleChange(visible: boolean) { - if (visible) { - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - const input = document.querySelector("#task-search-input") as HTMLInputElement; - input?.focus(); - }, DRAWER_ANIMATION_INTERVAL) - }); - } - } - - phaseSettingsClick() { - this.onPhaseSettingsClick?.emit(); - } - - createStatusClick() { - this.onCreateStatusClick?.emit(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/phase-settings-drawer/phase-settings-drawer.component.html b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/phase-settings-drawer/phase-settings-drawer.component.html deleted file mode 100644 index 81bac479..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/phase-settings-drawer/phase-settings-drawer.component.html +++ /dev/null @@ -1,78 +0,0 @@ - - - -
- - Phase Label: - - - - -
- - - -
-
-
- - Phase Options - -
-
- -
-
- - - - -   - - -
    -
  • -   - -
  • -
-
- - - -
-
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/phase-settings-drawer/phase-settings-drawer.component.scss b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/phase-settings-drawer/phase-settings-drawer.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/phase-settings-drawer/phase-settings-drawer.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/phase-settings-drawer/phase-settings-drawer.component.spec.ts deleted file mode 100644 index 0c57421d..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/phase-settings-drawer/phase-settings-drawer.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { PhaseSettingsDrawerComponent } from './phase-settings-drawer.component'; - -describe('PhaseSettingsDrawerComponent', () => { - let component: PhaseSettingsDrawerComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [PhaseSettingsDrawerComponent] - }); - fixture = TestBed.createComponent(PhaseSettingsDrawerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/phase-settings-drawer/phase-settings-drawer.component.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/phase-settings-drawer/phase-settings-drawer.component.ts deleted file mode 100644 index 73acc77c..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/phase-settings-drawer/phase-settings-drawer.component.ts +++ /dev/null @@ -1,197 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output} from '@angular/core'; -import {PtTaskListService} from "../../services/pt-task-list.service"; -import {ProjectTemplateService} from "@services/project-template.service"; -import {PtTaskPhasesApiService} from "@api/pt-task-phases-api.service"; -import {ITaskPhase} from "@interfaces/api-models/task-phase"; -import {PhaseColorCodes} from "@shared/constants"; - -@Component({ - selector: 'worklenz-phase-settings-drawer', - templateUrl: './phase-settings-drawer.component.html', - styleUrls: ['./phase-settings-drawer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class PhaseSettingsDrawerComponent { - @Input() templateId: string | null = null; - - @Input() show = false; - @Output() showChange = new EventEmitter(); - @Output() onCreateOrUpdate = new EventEmitter(); - @Output() refresh = new EventEmitter(); - - readonly COLOR_CODES = PhaseColorCodes - - loading = false; - creating = false; - updatingLabel = false; - - updating: { [x: string]: boolean } = {}; - deleting: { [x: string]: boolean } = {}; - updateCache: { [x: string]: string } = {}; - - oldLabel: string | null = null; - phaseLabel: string | null = null; - - get options() { - return this.list.phases; - } - - constructor( - private readonly api: PtTaskPhasesApiService, - private readonly cdr: ChangeDetectorRef, - private readonly list: PtTaskListService, - private readonly service: ProjectTemplateService - ) { - } - - close() { - this.show = false; - this.showChange.emit(false); - } - - addNewOption() { - void this.create(); - } - - onVisibleChange(visible: boolean) { - if (visible) { - void this.get(true); - this.phaseLabel = this.service.label; - } - } - - removeOption(id: string) { - if (!id) return; - void this.delete(id); - } - - async updateOption(phase: ITaskPhase) { - await this.update(phase); - delete this.updateCache[phase.id]; - } - - setNameCache(id: string, name: string) { - this.updateCache[id] = name; - } - - private async create() { - if (!this.templateId || this.creating) return; - try { - this.creating = true; - const res = await this.api.create(this.templateId); - if (res.done) { - // this.list.phases.unshift(res.body); - await this.get(false); - this.service.emitOptionsChange(); - this.onCreateOrUpdate.emit(); - } - this.creating = false; - } catch (e) { - this.creating = false; - } - - this.cdr.markForCheck(); - } - - private async get(loading: boolean) { - if (!this.templateId) return; - try { - this.loading = loading; - const res = await this.api.get(this.templateId); - if (res.done) { - this.list.phases = res.body; - } - this.loading = false; - } catch (e) { - this.loading = false; - } - - this.cdr.markForCheck(); - } - - public async updateLabel(label: string | null) { - if (!this.templateId) return; - - if (!label?.trim()) { - this.phaseLabel = this.oldLabel; - return; - } - - try { - this.updatingLabel = true; - const res = await this.api.updateLabel(this.templateId, label?.trim()); - if (res.done) { - this.service.updateLabel(label?.trim()); - } - this.updatingLabel = false; - } catch (e) { - this.updatingLabel = false; - } - - this.cdr.markForCheck(); - } - - private async update(phase: ITaskPhase) { - if (!phase?.id || !this.templateId || this.updating[phase.id]) return; - if (this.updateCache[phase.id] === phase.name) return; - - try { - this.updating[phase.id] = true; - const res = await this.api.update(this.templateId, phase); - if (res.done) { - await this.get(false); - this.service.emitOptionsChange(); - this.onCreateOrUpdate.emit(); - } - this.updating[phase.id] = false; - } catch (e) { - phase.name = this.updateCache[phase.id]; - this.updating[phase.id] = false; - } - - this.cdr.markForCheck(); - } - - private async delete(id: string) { - if (!id || !this.templateId || this.deleting[id]) return; - try { - this.deleting[id] = true; - const res = await this.api.delete(id, this.templateId); - if (res.done) { - const index = this.list.phases.findIndex(o => o.id === id); - if (index > -1) { - this.list.phases.splice(index, 1); - this.service.emitOptionsChange(); - this.onCreateOrUpdate.emit(); - } - } - this.deleting[id] = false; - } catch (e) { - this.deleting[id] = false; - } - - this.cdr.markForCheck(); - } - - async setColorCode(phase: ITaskPhase, color_code: string){ - phase.color_code = color_code+'69'; - await this.updateColor(phase); - } - - private async updateColor(phase: ITaskPhase) { - if (!phase?.id || !this.templateId) return; - try { - this.updating[phase.id] = true; - const res = await this.api.updateColor(this.templateId, phase); - if (res.done) { - this.refresh.emit(); - } - this.updating[phase.id] = false; - } catch (e) { - phase.name = this.updateCache[phase.id]; - this.updating[phase.id] = false; - } - this.cdr.markForCheck(); - } - -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-description/task-description.component.html b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-description/task-description.component.html deleted file mode 100644 index 93b45db5..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-description/task-description.component.html +++ /dev/null @@ -1,34 +0,0 @@ -
- - - -
- - - - - - -
- - - - - -
-
- -
-
diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-description/task-description.component.scss b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-description/task-description.component.scss deleted file mode 100644 index 82474113..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-description/task-description.component.scss +++ /dev/null @@ -1,36 +0,0 @@ -:host { - display: block; - max-width: 640px; -} - -.description-editor-placeholder { - pointer-events: none; - user-select: none; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - display: flex; - align-items: center; -} - -.description-editor-preview { - position: relative; - min-height: 32px; - - &.empty { - display: flex; - align-items: center; - padding: 10px; - } -} - -.task-description-editor { - padding-left: 12px; - padding-right: 12px; - padding-bottom: 12px; - margin-left: -12px; - margin-right: -12px; - margin-bottom: -12px; -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-description/task-description.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-description/task-description.component.spec.ts deleted file mode 100644 index ced01053..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-description/task-description.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskDescriptionComponent } from './task-description.component'; - -describe('TaskDescriptionComponent', () => { - let component: TaskDescriptionComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskDescriptionComponent] - }); - fixture = TestBed.createComponent(TaskDescriptionComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-description/task-description.component.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-description/task-description.component.ts deleted file mode 100644 index ba89b01e..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-description/task-description.component.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostBinding, - Input, NgZone, OnDestroy, OnInit, - ViewChild -} from '@angular/core'; -import {IPTTask} from "../../../interfaces"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {PtTaskListService} from "../../../services/pt-task-list.service"; -import {EditorComponent} from "@tinymce/tinymce-angular"; - -@Component({ - selector: 'worklenz-task-description', - templateUrl: './task-description.component.html', - styleUrls: ['./task-description.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskDescriptionComponent implements OnInit, OnDestroy { - @ViewChild("descriptionInput", {static: false}) descriptionInput!: ElementRef; - @ViewChild("descriptionEditor", {static: false}) descriptionEditor!: EditorComponent; - - @Input() task: IPTTask = {}; - @HostBinding("class") cls = "flex-row task-description p-0"; - show = false; - loading = false; - - readonly CONFIG = { - base_url: '/tinymce', - suffix: '.min', - plugins: "lists link code wordcount", - toolbar: 'blocks bold italic underline strikethrough | checklist numlist bullist link | alignleft aligncenter alignright alignjustify', - menubar: false, - content_css: "/assets/css/prebuilt-editor.css", - statusbar: true, - branding: false, - height: 200, - min_height: 100 - }; - - isEditing = false; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly socket: Socket, - public readonly service: PtTaskListService, - private readonly ngZone: NgZone, - ) { - } - - ngOnInit() { - this.socket.on(SocketEvents.PT_TASK_DESCRIPTION_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.PT_TASK_DESCRIPTION_CHANGE.toString(), this.handleResponse); - } - - private handleResponse = (response: { id: string; description: string; }) => { - if (this.task.id === response?.id) { - this.task.description = response.description; - this.closeDropdown(); - this.cdr.markForCheck(); - } - }; - - handleVisibleChange(visible: boolean, tr: HTMLDivElement) { - this.show = visible; - visible ? tr.classList.add(this.service.HIGHLIGHT_COL_CLS) : tr.classList.remove(this.service.HIGHLIGHT_COL_CLS); - } - - submit() { - this.socket.emit(SocketEvents.PT_TASK_DESCRIPTION_CHANGE.toString(), JSON.stringify({ - task_id: this.task.id, - description: this.task.description - })); - } - - closeDropdown() { - this.ngZone.runOutsideAngular(() => { - document.body.click(); - }); - } - -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-end-date/task-end-date.component.html b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-end-date/task-end-date.component.html deleted file mode 100644 index 8564c4b7..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-end-date/task-end-date.component.html +++ /dev/null @@ -1,17 +0,0 @@ -
- {{task.end_date | dateFormatter}} - - - -
diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-end-date/task-end-date.component.scss b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-end-date/task-end-date.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-end-date/task-end-date.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-end-date/task-end-date.component.spec.ts deleted file mode 100644 index 6145a4dd..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-end-date/task-end-date.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskEndDateComponent } from './task-end-date.component'; - -describe('TaskEndDateComponent', () => { - let component: TaskEndDateComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskEndDateComponent] - }); - fixture = TestBed.createComponent(TaskEndDateComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-end-date/task-end-date.component.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-end-date/task-end-date.component.ts deleted file mode 100644 index f8c4a4be..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-end-date/task-end-date.component.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - HostBinding, - Input, - NgZone, - Renderer2 -} from '@angular/core'; -import {IPTTask} from "../../../interfaces"; -import {PtTaskListService} from "../../../services/pt-task-list.service"; -import moment from "moment"; -import {Socket} from "ngx-socket-io"; - -@Component({ - selector: 'worklenz-task-end-date', - templateUrl: './task-end-date.component.html', - styleUrls: ['./task-end-date.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskEndDateComponent { - @Input() task: IPTTask = {}; - @HostBinding("class") cls = "flex-row task-due-date"; - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly service: PtTaskListService, - private readonly renderer: Renderer2 - ) { - } - - private handleResponse = (response: { - id: string; - parent_task: string | null; - end_date: string; - }) => { - if (response.id === this.task.id && this.task.end_date !== response.end_date) { - this.task.end_date = response.end_date; - this.cdr.markForCheck(); - } - }; - - handleEndDateChange(date: string, task: IPTTask) { - // this.socket.emit( - // SocketEvents.PT_TASK_END_DATE_CHANGE.toString(), JSON.stringify({ - // task_id: task.id, - // end_date: date || null, - // parent_task: task.parent_task_id, - // })); - } - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - checkForPastDate(endDate: any) { - const formattedEndDate = moment(endDate).format('YYYY-MM-DD'); - return formattedEndDate < moment().format('YYYY-MM-DD'); - } - - checkForSoonDate(endDate: any) { - const formattedEndDate = moment(endDate).format('YYYY-MM-DD'); - const tomorrow = moment().add(1, 'day').format('YYYY-MM-DD'); - return formattedEndDate === moment().format('YYYY-MM-DD') || formattedEndDate === tomorrow; - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-estimation/task-estimation.component.html b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-estimation/task-estimation.component.html deleted file mode 100644 index 6fa27dec..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-estimation/task-estimation.component.html +++ /dev/null @@ -1,41 +0,0 @@ -
- -

{{task.total_time_string}}

-
-
- - -
- - -
- - Hours - - - - Minutes - - -
-
-
-
-
- -
-
diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-estimation/task-estimation.component.scss b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-estimation/task-estimation.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-estimation/task-estimation.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-estimation/task-estimation.component.spec.ts deleted file mode 100644 index 4af435e3..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-estimation/task-estimation.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskEstimationComponent } from './task-estimation.component'; - -describe('TaskEstimationComponent', () => { - let component: TaskEstimationComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskEstimationComponent] - }); - fixture = TestBed.createComponent(TaskEstimationComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-estimation/task-estimation.component.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-estimation/task-estimation.component.ts deleted file mode 100644 index e18172dd..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-estimation/task-estimation.component.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostBinding, - Input, NgZone, OnDestroy, OnInit, - ViewChild -} from '@angular/core'; -import {IPTTask} from "../../../interfaces"; -import {Socket} from "ngx-socket-io"; -import {PtTaskListService} from "../../../services/pt-task-list.service"; -import {UtilsService} from "@services/utils.service"; -import {SocketEvents} from "@shared/socket-events"; - -@Component({ - selector: 'worklenz-task-estimation', - templateUrl: './task-estimation.component.html', - styleUrls: ['./task-estimation.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskEstimationComponent implements OnInit, OnDestroy { - @ViewChild('labelsSearchInput', {static: false}) labelsSearchInput!: ElementRef; - @Input() task: IPTTask = {}; - @HostBinding("class") cls = "flex-row task-estimation p-0"; - - show = false; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly socket: Socket, - public readonly service: PtTaskListService, - public readonly utils: UtilsService, - private readonly ngZone: NgZone, - ) { - } - - ngOnInit(): void { - this.socket.on(SocketEvents.PT_TASK_TIME_ESTIMATION_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy(): void { - this.socket.removeListener(SocketEvents.PT_TASK_TIME_ESTIMATION_CHANGE.toString(), this.handleResponse); - } - - - handleLabelsVisibleChange(visible: boolean, tr: HTMLDivElement) { - this.show = visible; - visible ? tr.classList.add(this.service.HIGHLIGHT_COL_CLS) : tr.classList.remove(this.service.HIGHLIGHT_COL_CLS); - } - - submit() { - if (!this.task?.id) return; - this.socket.emit(SocketEvents.PT_TASK_TIME_ESTIMATION_CHANGE.toString(), JSON.stringify({ - task_id: this.task.id, - total_hours: this.task.total_hours || 0, - total_minutes: this.task.total_minutes || 0, - parent_task: this.task.parent_task_id, - })); - } - - private handleResponse = (response: { id: string; total_time_string: string; }) => { - if (this.task.id === response?.id) { - this.task.total_time_string = response.total_time_string; - this.closeDropdown(); - this.cdr.markForCheck(); - } - }; - - closeDropdown() { - this.ngZone.runOutsideAngular(() => { - document.body.click(); - }); - } - -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-labels/task-labels.component.html b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-labels/task-labels.component.html deleted file mode 100644 index 53366191..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-labels/task-labels.component.html +++ /dev/null @@ -1,49 +0,0 @@ -
- - - {{item.name | ellipsis:10}} - - {{item.name | ellipsis:10}} - - - - - - - - - -
- - -
- - - Hit enter to create! - -
- -
- -
-
diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-labels/task-labels.component.scss b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-labels/task-labels.component.scss deleted file mode 100644 index ad21ba4f..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-labels/task-labels.component.scss +++ /dev/null @@ -1,36 +0,0 @@ -.dropdown-ul { - max-height: 250px; - overflow: hidden; - overflow-y: auto; -} - -.label-tag-container { - max-width: 220px; - overflow: hidden; - flex-wrap: wrap; - padding-top: 8px; - padding-bottom: 8px; - padding-right: 0px; -} - -nz-tag { - display: block; - overflow: hidden; - white-space: break-spaces; - margin-left: 3px; - margin-right: 3px; - line-height: 16px; - padding-left: 3px; - padding-right: 3px; -} - -.empty-label { - padding-left: 8px; - padding-right: 8px; - padding-top: 2px; - padding-bottom: 3px; - - & .ant-typography { - display: flex; - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-labels/task-labels.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-labels/task-labels.component.spec.ts deleted file mode 100644 index 9733010a..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-labels/task-labels.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskLabelsComponent } from './task-labels.component'; - -describe('TaskLabelsComponent', () => { - let component: TaskLabelsComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskLabelsComponent] - }); - fixture = TestBed.createComponent(TaskLabelsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-labels/task-labels.component.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-labels/task-labels.component.ts deleted file mode 100644 index d8791557..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-labels/task-labels.component.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostBinding, - Input, - NgZone, - ViewChild -} from '@angular/core'; -import {IPTTask} from "../../../interfaces"; -import {ALPHA_CHANNEL} from "@shared/constants"; -import {ITaskLabel} from "@interfaces/task-label"; -import {PtTaskListService} from "../../../services/pt-task-list.service"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import {AuthService} from "@services/auth.service"; -import {Socket} from "ngx-socket-io"; -import {UtilsService} from "@services/utils.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {SocketEvents} from "@shared/socket-events"; -import {ILabelsChangeResponse} from "../../../../../modules/task-list-v2/interfaces"; - -@Component({ - selector: 'worklenz-task-labels', - templateUrl: './task-labels.component.html', - styleUrls: ['./task-labels.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskLabelsComponent { - @ViewChild('labelsSearchInput', {static: false}) labelsSearchInput!: ElementRef; - @Input() task: IPTTask = {}; - @HostBinding("class") cls = "flex-row task-labels"; - - readonly alpha = ALPHA_CHANNEL; - - searchText: string | null = null; - - labels: ITaskLabel[] = []; - - show = false; - - get hasFilteredLabel() { - return !!this.filteredLabels.length; - } - - get filteredLabels() { - return this.searchPipe.transform(this.labels, this.searchText); - } - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly searchPipe: SearchByNamePipe, - private readonly auth: AuthService, - private readonly socket: Socket, - private readonly utils: UtilsService, - private readonly ngZone: NgZone, - public readonly service: PtTaskListService - ) { - this.service.onLabelsChange$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.updateLabels(); - this.cdr.markForCheck(); - }); - } - - ngOnInit() { - this.updateLabels(); - this.socket.on(SocketEvents.PT_TASK_LABELS_CHANGE.toString(), this.handleLabelsChange); - this.socket.on(SocketEvents.PT_CREATE_LABEL.toString(), this.handleLabelsChange); - } - - ngOnDestroy() { - this.labels = []; - this.socket.removeListener(SocketEvents.PT_TASK_LABELS_CHANGE.toString(), this.handleLabelsChange); - this.socket.removeListener(SocketEvents.PT_CREATE_LABEL.toString(), this.handleLabelsChange); - } - - private updateLabels() { - this.labels = this.service.labels; - } - - trackById(index: number, item: ITaskLabel) { - return item.id; - } - - private sortBySelected(labels: ITaskLabel[]) { - this.utils.sortBySelection(labels); - } - - private handleLabelsChange = (response: ILabelsChangeResponse) => { - if (response && response.id === this.task.id) { - this.task.labels = response.labels; - this.task.all_labels = response.all_labels; - - if (response.new_label) { - if (response.is_new) { - const labels = [...this.service.labels]; - labels.push(response.new_label); - this.service.labels = [...labels]; - } else { - const label = this.labels.find(l => l.id === response.new_label.id); - if (label) - label.selected = true; - } - } - this.cdr.markForCheck(); - } - } - - handleLabelsVisibleChange(visible: boolean, tr: HTMLDivElement) { - this.show = visible; - visible ? tr.classList.add(this.service.HIGHLIGHT_COL_CLS) : tr.classList.remove(this.service.HIGHLIGHT_COL_CLS); - if (visible) { - const labels = this.task.all_labels?.map(l => l.id) ?? []; - for (const label of this.labels) - label.selected = labels.includes(label.id); - this.focusLabelsSearchInput(); - } else { - this.searchText = null; - for (const label of this.labels) - label.selected = false; - } - - this.sortBySelected(this.labels); - } - - private focusLabelsSearchInput() { - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.labelsSearchInput?.nativeElement?.focus(); - }, 100); - }); - } - - handleLabelChange(label: ITaskLabel) { - this.socket.emit(SocketEvents.PT_TASK_LABELS_CHANGE.toString(), JSON.stringify({ - task_id: this.task.id, - label_id: label.id, - parent_task: this.task.parent_task_id - })); - - this.sortBySelected(this.labels); - } - - createLabel() { - if (this.hasFilteredLabel || !this.searchText) return; - - const session = this.auth.getCurrentSession(); - this.socket.emit(SocketEvents.PT_CREATE_LABEL.toString(), JSON.stringify({ - task_id: this.task.id, - label: this.searchText.trim(), - team_id: session?.team_id, - parent_task: this.task.parent_task_id - })); - this.searchText = null; - - this.cdr.detectChanges(); - } - - closeDropdown() { - this.ngZone.runOutsideAngular(() => { - document.body.click(); - }); - } - -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-phase/task-phase.component.html b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-phase/task-phase.component.html deleted file mode 100644 index 76808abe..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-phase/task-phase.component.html +++ /dev/null @@ -1,20 +0,0 @@ -
- - - - - -
diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-phase/task-phase.component.scss b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-phase/task-phase.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-phase/task-phase.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-phase/task-phase.component.spec.ts deleted file mode 100644 index 02cd620d..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-phase/task-phase.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskPhaseComponent } from './task-phase.component'; - -describe('TaskPhaseComponent', () => { - let component: TaskPhaseComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskPhaseComponent] - }); - fixture = TestBed.createComponent(TaskPhaseComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-phase/task-phase.component.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-phase/task-phase.component.ts deleted file mode 100644 index bf355742..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-phase/task-phase.component.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostBinding, - Input, - NgZone, - Renderer2 -} from '@angular/core'; -import {IPTTask} from "../../../interfaces"; -import {ALPHA_CHANNEL} from "@shared/constants"; -import {ITaskPhase} from "@interfaces/api-models/task-phase"; -import {PtTaskListService} from "../../../services/pt-task-list.service"; -import {Socket} from "ngx-socket-io"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {filter} from "rxjs"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {ITaskPhaseChangeResponse} from "@interfaces/task-phase-change-response"; - -@Component({ - selector: 'worklenz-task-phase', - templateUrl: './task-phase.component.html', - styleUrls: ['./task-phase.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskPhaseComponent { - @Input() task: IPTTask = {}; - @HostBinding("class") cls = "flex-row task-phase"; - - readonly PHASE_COLOR = "#a9a9a9" + ALPHA_CHANNEL; - readonly PLACEHOLDER_COLOR = 'rgba(0, 0, 0, 0.85) !important'; - - phases: ITaskPhase[] = []; - - loading = false; - - constructor( - private readonly service: PtTaskListService, - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly element: ElementRef, - private readonly renderer: Renderer2, - ) { - this.service.onPhaseChange$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.updatePhases(); - this.cdr.markForCheck(); - }); - - this.service.onGroupChange$ - .pipe( - filter(value => value.taskId === this.task.id), - filter(() => this.isGroupByPhase()), - takeUntilDestroyed() - ) - .subscribe(value => { - if(value.groupId === "Unmapped") value.color = ''; - this.task.phase_id = value.groupId; - this.task.phase_color = value.color; - this.cdr.markForCheck(); - }); - } - - ngOnInit() { - this.updatePhases(); - this.socket.on(SocketEvents.PT_TASK_PHASE_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.PT_TASK_PHASE_CHANGE.toString(), this.handleResponse); - } - - private isGroupByPhase() { - return this.service.getCurrentGroup().value === this.service.GROUP_BY_PHASE_VALUE; - } - - trackById(index: number, item: ITaskStatusViewModel) { - return item.id; - } - - handleChange(phaseId: string, taskId?: string) { - if (!taskId) return; - this.socket.emit(SocketEvents.PT_TASK_PHASE_CHANGE.toString(), JSON.stringify( - { - task_id: taskId, - phase_id: phaseId - } - )); - } - - private handleResponse = (response: ITaskPhaseChangeResponse) => { - if (response && response.task_id === this.task.id) { - this.task.phase_color = response.color_code || undefined; - this.task.phase_id = response.id; - - if (this.isGroupByPhase()) { - if (!this.task.is_sub_task) { - this.service.updateTaskGroup(this.task, false); - } - if (this.service.isSubtasksIncluded) { - this.service.emitRefreshSubtasksIncluded(); - } - } - this.cdr.markForCheck(); - } - } - - private updatePhases() { - this.phases = this.service.phases; - } - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - handleOpen(open: boolean) { - this.toggleHighlightCls(open, this.element.nativeElement); - } - -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-priority/task-priority.component.html b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-priority/task-priority.component.html deleted file mode 100644 index 066981bb..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-priority/task-priority.component.html +++ /dev/null @@ -1,15 +0,0 @@ -
- - - - - -
diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-priority/task-priority.component.scss b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-priority/task-priority.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-priority/task-priority.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-priority/task-priority.component.spec.ts deleted file mode 100644 index a0ea5aeb..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-priority/task-priority.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskPriorityComponent } from './task-priority.component'; - -describe('TaskPriorityComponent', () => { - let component: TaskPriorityComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskPriorityComponent] - }); - fixture = TestBed.createComponent(TaskPriorityComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-priority/task-priority.component.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-priority/task-priority.component.ts deleted file mode 100644 index ace9cef9..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-priority/task-priority.component.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostBinding, - Input, - NgZone, - Renderer2 -} from '@angular/core'; -import {IPTTask} from "../../../interfaces"; -import {PtTaskListService} from "../../../services/pt-task-list.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {filter} from "rxjs"; -import {ITaskPrioritiesGetResponse} from "@interfaces/api-models/task-priorities-get-response"; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; - -@Component({ - selector: 'worklenz-task-priority', - templateUrl: './task-priority.component.html', - styleUrls: ['./task-priority.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskPriorityComponent { - @Input() task: IPTTask = {}; - @HostBinding("class") cls = "flex-row task-priority"; - - priorities: ITaskPrioritiesGetResponse[] = []; - - loading = false; - - constructor( - private readonly service: PtTaskListService, - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly element: ElementRef, - private readonly renderer: Renderer2 - ) { - this.service.onPrioritiesChange$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.updatePriorities(); - this.cdr.markForCheck(); - }); - - this.service.onGroupChange$ - .pipe( - filter(value => value.taskId === this.task.id), - filter(() => this.isGroupByPriority()), - takeUntilDestroyed() - ) - .subscribe(value => { - this.task.priority = value.groupId; - this.task.priority_color = value.color; - this.cdr.markForCheck(); - }); - } - - ngOnInit() { - this.updatePriorities(); - this.socket.on(SocketEvents.PT_TASK_PRIORITY_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.priorities = []; - this.socket.removeListener(SocketEvents.PT_TASK_PRIORITY_CHANGE.toString(), this.handleResponse); - } - - private isGroupByPriority() { - return this.service.getCurrentGroup().value === this.service.GROUP_BY_PRIORITY_VALUE; - } - - trackById(index: number, item: ITaskStatusViewModel) { - return item.id; - } - - handlePriorityChange(priorityId: string, data: IPTTask) { - this.socket.emit(SocketEvents.PT_TASK_PRIORITY_CHANGE.toString(), JSON.stringify({ - task_id: data.id, - priority_id: priorityId, - parent_task: this.task.parent_task_id - })); - } - - private handleResponse = (response: { - priority_id: string | undefined; - name: string | undefined; id: string; parent_task: string; color_code: string; - }) => { - if (response && response.id === this.task.id) { - this.task.priority_color = response.color_code; - this.task.priority = response.priority_id; - - if (this.isGroupByPriority()) { - if (!this.task.is_sub_task) { - this.service.updateTaskGroup(this.task, false); - } - if (this.service.isSubtasksIncluded) { - this.service.emitRefreshSubtasksIncluded(); - } - } - this.cdr.markForCheck(); - } - } - - private updatePriorities() { - this.priorities = this.service.priorities; - } - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - handleOpen(open: boolean) { - this.toggleHighlightCls(open, this.element.nativeElement); - } - -} - diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-start-date/task-start-date.component.html b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-start-date/task-start-date.component.html deleted file mode 100644 index 444f863a..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-start-date/task-start-date.component.html +++ /dev/null @@ -1,18 +0,0 @@ - -
- - - - {{task.start_date | dateFormatter}} - - -
diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-start-date/task-start-date.component.scss b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-start-date/task-start-date.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-start-date/task-start-date.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-start-date/task-start-date.component.spec.ts deleted file mode 100644 index 7f6e137e..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-start-date/task-start-date.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskStartDateComponent } from './task-start-date.component'; - -describe('TaskStartDateComponent', () => { - let component: TaskStartDateComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskStartDateComponent] - }); - fixture = TestBed.createComponent(TaskStartDateComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-start-date/task-start-date.component.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-start-date/task-start-date.component.ts deleted file mode 100644 index 0203385a..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-start-date/task-start-date.component.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - HostBinding, - Input, - NgZone, - Renderer2 -} from '@angular/core'; -import {IPTTask} from "../../../interfaces"; -import {PtTaskListService} from "../../../services/pt-task-list.service"; -import {Socket} from "ngx-socket-io"; - -@Component({ - selector: 'worklenz-task-start-date', - templateUrl: './task-start-date.component.html', - styleUrls: ['./task-start-date.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskStartDateComponent { - @Input() task: IPTTask = {}; - @HostBinding("class") cls = "flex-row task-due-date"; - - constructor( - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly service: PtTaskListService, - private readonly renderer: Renderer2 - ) { - } - - private handleResponse = (response: { - id: string; - parent_task: string | null; - start_date: string; - }) => { - if (response.id === this.task.id && this.task.start_date !== response.start_date) { - this.task.start_date = response.start_date; - this.cdr.markForCheck(); - } - }; - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - handleStartDateChange(date: string, task: IPTTask) { - // this.socket.emit( - // SocketEvents.PT_TASK_START_DATE_CHANGE.toString(), JSON.stringify({ - // task_id: task.id, - // start_date: date || null, - // parent_task: task.parent_task_id, - // })); - } - -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-status/task-status.component.html b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-status/task-status.component.html deleted file mode 100644 index 3610373e..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-status/task-status.component.html +++ /dev/null @@ -1,14 +0,0 @@ -
- - - - - -
diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-status/task-status.component.scss b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-status/task-status.component.scss deleted file mode 100644 index 6bc7859f..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-status/task-status.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -nz-select { - max-width: 100px; -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-status/task-status.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-status/task-status.component.spec.ts deleted file mode 100644 index cd39a511..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-status/task-status.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskStatusComponent } from './task-status.component'; - -describe('TaskStatusComponent', () => { - let component: TaskStatusComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskStatusComponent] - }); - fixture = TestBed.createComponent(TaskStatusComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-status/task-status.component.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-status/task-status.component.ts deleted file mode 100644 index 5f932c13..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/row/task-status/task-status.component.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostBinding, - Input, - NgZone, - Renderer2 -} from '@angular/core'; -import {IPTTask} from "../../../interfaces"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {PtTaskListService} from "../../../services/pt-task-list.service"; -import {Socket} from "ngx-socket-io"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {filter} from "rxjs"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskListStatusChangeResponse} from "@interfaces/task-list-status-change-response"; - -@Component({ - selector: 'worklenz-task-status', - templateUrl: './task-status.component.html', - styleUrls: ['./task-status.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskStatusComponent { - @Input() task: IPTTask = {}; - @HostBinding("class") cls = "flex-row task-status"; - - statuses: ITaskStatusViewModel[] = []; - - loading = false; - - constructor( - private readonly service: PtTaskListService, - private readonly socket: Socket, - private readonly cdr: ChangeDetectorRef, - private readonly ngZone: NgZone, - private readonly element: ElementRef, - private readonly renderer: Renderer2 - ) { - this.service.onStatusesChange$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.updateStatuses(); - this.cdr.markForCheck(); - }); - - this.service.onGroupChange$ - .pipe( - filter(value => value.taskId === this.task.id), - filter(() => this.isGroupByStatus()), - takeUntilDestroyed() - ) - .subscribe(value => { - this.task.status = value.groupId; - this.task.status_color = value.color; - this.cdr.markForCheck(); - }); - } - - - ngOnInit() { - this.updateStatuses(); - this.socket.on(SocketEvents.PT_TASK_STATUS_CHANGE.toString(), this.handleResponse); - } - - ngOnDestroy() { - this.socket.removeListener(SocketEvents.PT_TASK_STATUS_CHANGE.toString(), this.handleResponse); - } - - private isGroupByStatus() { - return this.service.getCurrentGroup().value === this.service.GROUP_BY_STATUS_VALUE; - } - - trackById(index: number, item: ITaskStatusViewModel) { - return item.id; - } - - handleStatusChange(statusId: string, taskId?: string) { - if (!taskId) return; - - this.socket.emit(SocketEvents.PT_TASK_STATUS_CHANGE.toString(), JSON.stringify({ - task_id: taskId, - status_id: statusId, - parent_task: this.task.parent_task_id - })); - } - - private handleResponse = (response: ITaskListStatusChangeResponse) => { - if (response && response.id === this.task.id) { - this.task.status_color = response.color_code; - this.task.status = response.status_id; - this.task.status_category = response.statusCategory; - - if (this.isGroupByStatus()) { - if (!this.task.is_sub_task) { - this.service.updateTaskGroup(this.task, false); - } - if (this.service.isSubtasksIncluded) { - this.service.emitRefreshSubtasksIncluded(); - } - } - - this.cdr.markForCheck(); - } - } - - private updateStatuses() { - this.statuses = this.service.statuses; - } - - toggleHighlightCls(active: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - if (active) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - }); - } - - handleOpen(open: boolean) { - this.toggleHighlightCls(open, this.element.nativeElement); - } - -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/status-settings-drawer/status-settings-drawer.component.html b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/status-settings-drawer/status-settings-drawer.component.html deleted file mode 100644 index ce57b1da..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/status-settings-drawer/status-settings-drawer.component.html +++ /dev/null @@ -1,41 +0,0 @@ - - - -
- - Name - - - - - - Category - - - - - - - - - -
-
-
-
diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/status-settings-drawer/status-settings-drawer.component.scss b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/status-settings-drawer/status-settings-drawer.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/status-settings-drawer/status-settings-drawer.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/status-settings-drawer/status-settings-drawer.component.spec.ts deleted file mode 100644 index 01146e4d..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/status-settings-drawer/status-settings-drawer.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { StatusSettingsDrawerComponent } from './status-settings-drawer.component'; - -describe('StatusSettingsDrawerComponent', () => { - let component: StatusSettingsDrawerComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [StatusSettingsDrawerComponent] - }); - fixture = TestBed.createComponent(StatusSettingsDrawerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/status-settings-drawer/status-settings-drawer.component.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/status-settings-drawer/status-settings-drawer.component.ts deleted file mode 100644 index 4429798f..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/status-settings-drawer/status-settings-drawer.component.ts +++ /dev/null @@ -1,149 +0,0 @@ -import {Component, EventEmitter, Input, Output} from '@angular/core'; -import {PtStatusesApiService} from "@api/pt-statuses-api.service"; -import {FormBuilder, FormGroup, Validators} from "@angular/forms"; -import {ProjectsDefaultColorCodes} from "@shared/constants"; -import {ITaskStatusCategory} from "@interfaces/task-status-category"; -import {ITaskStatus} from "@interfaces/task-status"; -import {AppService} from "@services/app.service"; -import {log_error} from "@shared/utils"; -import {dispatchStatusChange} from "@shared/events"; - -@Component({ - selector: 'worklenz-status-settings-drawer', - templateUrl: './status-settings-drawer.component.html', - styleUrls: ['./status-settings-drawer.component.scss'] -}) -export class StatusSettingsDrawerComponent { - @Input() action: string = 'Create'; - @Input() show = false; - @Input() statusId: string | null = null; - @Input() templateId: string | null = null; - - @Output() showChange = new EventEmitter(); - @Output() onCreateOrUpdate = new EventEmitter(); - - form!: FormGroup; - loading = true; - loadingCategories = false; - - colorCodes = ProjectsDefaultColorCodes; - categories: ITaskStatusCategory[] = []; - taskStatus: ITaskStatus = {}; - - constructor( - private api: PtStatusesApiService, - private fb: FormBuilder, - private app: AppService, - ) { - this.createForm(); - } - - init() { - this.form.controls["template_id"].setValue(this.templateId); - this.getCategories(); - if (this.statusId) { - this.getById(this.statusId); - } else { - this.loading = false; - } - } - - closeModal() { - this.show = false; - this.form.reset(); - this.action = 'Create'; - this.createForm(); - this.showChange.emit(); - } - - async submit() { - if (this.taskStatus && this.taskStatus.id) { - await this.updateStatus(); - } else { - await this.addStatus(); - } - } - - async getById(id: string) { - try { - this.loading = true; - const res = await this.api.getById(id); - if (res.done) { - this.taskStatus = res.body; - this.form.patchValue(this.taskStatus); - } - this.loading = false; - } catch (e) { - log_error(e); - this.loading = false; - } - } - - async getCategories() { - try { - this.loadingCategories = true; - const res = await this.api.getCategories(); - if (res.done) { - this.categories = res.body; - this.form.controls["category_id"].setValue(this.categories[0].id); - } - this.loadingCategories = false; - } catch (e) { - this.loadingCategories = false; - log_error(e); - } - } - - async addStatus() { - if (this.form.invalid) { - this.app.displayErrorsOf(this.form); - return; - } - - try { - const res = await this.api.create(this.form.value); - if (res.done) { - res.body.color_code = res.body.color_code + "69"; - this.onCreateOrUpdate.emit(); - this.closeModal(); - } - } catch (e) { - log_error(e); - } - } - - async updateStatus() { - if (!this.taskStatus || !this.taskStatus.id) return; - if (this.form.invalid) { - this.app.displayErrorsOf(this.form); - return; - } - - try { - const res = await this.api.update(this.taskStatus.id, this.form.value); - if (res.done) { - this.onCreateOrUpdate.emit(); - this.closeModal(); - dispatchStatusChange(); - } - } catch (e) { - log_error(e); - } - } - - onVisibilityChange(visible: boolean) { - if (visible) { - // Wait for drawer animation to finish - setTimeout(() => this.init(), 100); - } - } - - private createForm() { - this.form = this.fb.group({ - name: [null, [Validators.required]], - category_id: [null, [Validators.required]], - template_id: [this.templateId] - }); - } - -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-group-settings/task-list-group-settings.component.html b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-group-settings/task-list-group-settings.component.html deleted file mode 100644 index 0aa98883..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-group-settings/task-list-group-settings.component.html +++ /dev/null @@ -1,58 +0,0 @@ -
-
- - - -
-
- - -
    -
  • - - Rename -
  • -
  • -
      -
    • - - - - - {{item?.name || null}} - - -
    • -
    -
  • -
-
- - - Change category - diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-group-settings/task-list-group-settings.component.scss b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-group-settings/task-list-group-settings.component.scss deleted file mode 100644 index a7883ac1..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-group-settings/task-list-group-settings.component.scss +++ /dev/null @@ -1,42 +0,0 @@ -.collapse { - - &.btn .collapse-icon { - transition: transform 0.1s; - transform: rotate(0deg); - } - - &.btn.active .collapse-icon { - transition: transform 0.1s; - transform: rotate(90deg); - } - - color: hwb(0 0% 100% / 0.85); - font-weight: 500; - padding: 6px 13px 6px 12px; - min-width: 120px; - width: auto; - border: none; - text-align: left; - outline: none; - border-top-right-radius: 4px; - border-top-left-radius: 4px; - -webkit-user-select: none; - user-select: none; - z-index: 8; - font-size: 14px; - height: 30px; - display: flex; - align-items: center; - - &.active { - border-bottom-left-radius: 0px; - border-bottom-right-radius: 0px; - } - - &:after { - color: #777; - font-weight: bold; - float: left; - margin-left: 5px; - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-group-settings/task-list-group-settings.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-group-settings/task-list-group-settings.component.spec.ts deleted file mode 100644 index 9014684a..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-group-settings/task-list-group-settings.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskListGroupSettingsComponent } from './task-list-group-settings.component'; - -describe('TaskListGroupSettingsComponent', () => { - let component: TaskListGroupSettingsComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskListGroupSettingsComponent] - }); - fixture = TestBed.createComponent(TaskListGroupSettingsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-group-settings/task-list-group-settings.component.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-group-settings/task-list-group-settings.component.ts deleted file mode 100644 index 82c9d376..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-group-settings/task-list-group-settings.component.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - NgZone, - Output -} from '@angular/core'; -import {ITaskStatusCategory} from "@interfaces/task-status-category"; -import {AuthService} from "@services/auth.service"; -import {ALPHA_CHANNEL, UNMAPPED} from "@shared/constants"; -import {calculateTaskCompleteRatio, log_error} from "@shared/utils"; -import {ITaskPhase} from "@interfaces/api-models/task-phase"; -import {IPTTaskListGroup} from "../../interfaces"; -import {PtTaskListService} from "../../services/pt-task-list.service"; -import {PtStatusesApiService} from "@api/pt-statuses-api.service"; -import {PtTaskPhasesApiService} from "@api/pt-task-phases-api.service"; -import {merge} from "rxjs"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; - -@Component({ - selector: 'worklenz-task-list-group-settings', - templateUrl: './task-list-group-settings.component.html', - styleUrls: ['./task-list-group-settings.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListGroupSettingsComponent { - @Input() group!: IPTTaskListGroup; - @Input() templateId: string | null = null; - @Input() categories: ITaskStatusCategory[] = []; - @Output() toggle = new EventEmitter(); - @Output() onCreateOrUpdate = new EventEmitter(); - - - protected edit = false; - protected isEditColProgress = false; - protected showMenu = false; - protected isGroupByStatus = false; - protected isGroupByPhases = false; - protected isAdmin = false; - - constructor( - private readonly cdr: ChangeDetectorRef, - private readonly auth: AuthService, - private readonly statusApi: PtStatusesApiService, - private readonly list: PtTaskListService, - private readonly ngZone: NgZone, - private readonly phaseApi: PtTaskPhasesApiService, - ) { - merge( - this.list.onGroupChange$, - this.list.onTaskAddOrDelete$ - ) - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.handleGroupProgressChange(); - }); - } - - ngOnInit() { - this.isGroupByStatus = this.list.getCurrentGroup().value === this.list.GROUP_BY_STATUS_VALUE; - this.isGroupByPhases = this.list.getCurrentGroup().value === this.list.GROUP_BY_PHASE_VALUE; - const session = this.auth.getCurrentSession(); - if (session) - this.isAdmin = !!(session.owner || session.is_admin); - } - - canDisplayActions() { - const currentGroup = this.list.getCurrentGroup().value; - if (currentGroup === this.list.GROUP_BY_PRIORITY_VALUE) return false; - return (this.isAdmin || this.isGroupByStatus || currentGroup === this.list.GROUP_BY_PHASE_VALUE) && this.group.name !== UNMAPPED; - } - - protected async changeStatusCategory(group: IPTTaskListGroup, categoryId?: string) { - if (!categoryId) return; - group.category_id = categoryId; - await this.onBlurEditColumn(group); - this.list.emitRefresh(); - } - - protected editGroupName() { - this.edit = true; - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - const selector = `#group-name-${this.group.id}`; - const input = document.querySelector(selector) as HTMLInputElement; - if (input) { - input.focus(); - input.select(); - } - }); - }); - } - - protected onToggleClick($event: MouseEvent) { - if (this.edit) return; - this.toggle.emit($event); - } - - private handleGroupProgressChange = () => { - const group = this.group; - if (!group) return; - - this.cdr.markForCheck(); - } - - protected async onBlurEditColumn(group: IPTTaskListGroup) { - if (!this.templateId || this.isEditColProgress) return; - - try { - this.isEditColProgress = true; - const groupBy = this.list.getCurrentGroup().value; - if (groupBy === this.list.GROUP_BY_STATUS_VALUE) { - await this.update(group); - } else if (groupBy === this.list.GROUP_BY_PHASE_VALUE) { - await this.updatePhase(group); - } - this.isEditColProgress = false; - this.edit = false; - } catch (e) { - log_error(e); - this.isEditColProgress = false; - } - - this.cdr.markForCheck(); - } - - protected async updateName(group: IPTTaskListGroup) { - if (!this.templateId || this.isEditColProgress) return; - - try { - this.isEditColProgress = true; - const groupBy = this.list.getCurrentGroup().value; - if (groupBy === this.list.GROUP_BY_STATUS_VALUE) { - await this.updateGroupName(group); - } else if (groupBy === this.list.GROUP_BY_PHASE_VALUE) { - await this.updatePhase(group); - } - this.isEditColProgress = false; - this.edit = false; - } catch (e) { - log_error(e); - this.isEditColProgress = false; - } - - this.cdr.markForCheck(); - } - - private async updateGroupName(group: IPTTaskListGroup) { - if (!group?.id || !this.templateId) return; - try { - const body = { - name: group.name, - template_id: this.templateId, - category_id: group.category_id - }; - const res = await this.statusApi.updateName(group.id, body); - if (res.done) { - const groups = this.list.groups; - const group = groups.find(p => p.id === res.body.id); - if (group) { - this.group.name = group.name = res.body.name || ''; - this.group.color_code = group.color_code = res.body.color_code || ''; - } - this.list.groups = groups; - } - } catch (e) { - // ignored - } - - this.cdr.markForCheck(); - } - - private async updatePhase(group: IPTTaskListGroup) { - if (!group?.id || !this.templateId) return; - try { - const body = { - id: group.id, - name: group.name - }; - const res = await this.phaseApi.update(this.templateId, body as ITaskPhase); - if (res.done) { - const phases = this.list.phases; - const phase = phases.find(p => p.id === res.body.id); - if (phase) { - this.group.name = phase.name = res.body.name; - this.group.color_code = phase.color_code = res.body.color_code; - } - this.list.phases = phases; - } - } catch (e) { - // ignored - } - - this.cdr.markForCheck(); - } - - private async update(group: IPTTaskListGroup) { - if (!this.isAdmin) return; - const body = { - name: group.name, - template_id: this.templateId as string, - category_id: group.category_id - }; - const res = await this.statusApi.update(group.id, body); - if (res.done) { - if (res.body.color_code != null) { - group.color_code = res.body.color_code + ALPHA_CHANNEL; - this.onCreateOrUpdate.emit(); - } - } - this.cdr.markForCheck(); - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-header/task-list-header.component.html b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-header/task-list-header.component.html deleted file mode 100644 index d8e4b534..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-header/task-list-header.component.html +++ /dev/null @@ -1,60 +0,0 @@ - -
- - -
- -
- - -
- - -
Task
- - - -
Description
-
- - - -
Labels
-
- - - -
- {{phaseLabel | ellipsis : 10}} - -
-
- - - -
Status
-
- - - -
Priority
-
- - - -
Estimation
-
- - - - - - - - - - diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-header/task-list-header.component.scss b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-header/task-list-header.component.scss deleted file mode 100644 index 6471b0c6..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-header/task-list-header.component.scss +++ /dev/null @@ -1,84 +0,0 @@ -.flex-row { - padding: 4px 11px; - background-color: #fafafa; - border-top: 1px solid #f0f0f0; - border-bottom: 1px solid #f0f0f0; - border-right: 1px solid #f0f0f0; - display: flex; - align-items: center; - flex-direction: row; -} - -.task-drag-handler { - padding: 0 0 0 4px !important; - width: 24px; - border-bottom: 1px solid #f0f0f0; - border-right: none !important; - position: sticky; - left: 0; - z-index: 1; -} - -.task-check { - text-align: center; - padding: 8px 6px 8px 0px !important; - position: sticky; - left: 24px; - z-index: 1; -} - -.task-arrow { - width: 24px; - padding: 0px !important; - display: flex; - align-items: center; - position: sticky; - border-right: 0; - left: 47px; - z-index: 1; -} - -.task-name { - width: 450px; - min-width: 450px; - position: sticky; - left: 71px; - z-index: 1; - - nz-filter-trigger { - margin-left: auto; - } - - &.left-0 { - left: 47px; - } - -} - -.task-description { - width: 225px; -} - -.task-labels { - width: 220px; -} - -.task-status { - width: 120px; -} - -.task-phase { - width: 150px; -} - -.task-priority { - width: 120px; -} - -.task-estimation { - width: 120px; -} - -.task-start-date, .task-due-date { - width: 150px; -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-header/task-list-header.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-header/task-list-header.component.spec.ts deleted file mode 100644 index d6f4108c..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-header/task-list-header.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskListHeaderComponent } from './task-list-header.component'; - -describe('TaskListHeaderComponent', () => { - let component: TaskListHeaderComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TaskListHeaderComponent] - }); - fixture = TestBed.createComponent(TaskListHeaderComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-header/task-list-header.component.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-header/task-list-header.component.ts deleted file mode 100644 index 7b7c8922..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/task-list-header/task-list-header.component.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - HostBinding, - Input, - Output -} from '@angular/core'; -import {ProjectPhasesService} from "@services/project-phases.service"; -import {AuthService} from "@services/auth.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {merge} from "rxjs"; -import {PtTaskListService} from "../../services/pt-task-list.service"; -import {PtTaskListHashMapService} from "../../services/pt-task-list-hash-map.service"; - -@Component({ - selector: 'worklenz-task-list-header', - templateUrl: './task-list-header.component.html', - styleUrls: ['./task-list-header.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TaskListHeaderComponent { -@HostBinding("class") headerCls = "flex-table header"; - - @Output() selectChange = new EventEmitter(); - @Output() phaseSettingsClick = new EventEmitter(); - - @Input() groupId!: string; - - checked = false; - indeterminate = false; - - get phaseLabel() { - return this.phasesService.label; - } - - constructor( - public readonly service: PtTaskListService, - private readonly map: PtTaskListHashMapService, - private readonly cdr: ChangeDetectorRef, - private readonly phasesService: ProjectPhasesService, - public readonly auth: AuthService - ) { - this.map.onDeselectAll$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.checked = false; - this.indeterminate = false; - this.cdr.markForCheck(); - }); - - this.phasesService.onLabelChange - .pipe(takeUntilDestroyed()) - .subscribe(value => { - this.cdr.markForCheck(); - }); - - merge(this.map.onSelect$, this.map.onDeselect$) - .pipe(takeUntilDestroyed()) - .subscribe(() => { - if (this.map.isAllDeselected(this.groupId)) { - this.checked = false; - this.indeterminate = false; - } else if (this.map.isAllSelected(this.groupId)) { - this.checked = true; - this.indeterminate = false; - } else { - this.indeterminate = true; - } - - this.cdr.markForCheck(); - }); - } - - onAllChecked(checked: boolean) { - this.selectChange?.emit(checked); - } - - protected onPhaseSettingsClick() { - this.phaseSettingsClick.emit(); - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/template-name/template-name.component.html b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/template-name/template-name.component.html deleted file mode 100644 index e179b4da..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/template-name/template-name.component.html +++ /dev/null @@ -1,10 +0,0 @@ -

{{templateName}}

- -Name cannot be empty! - diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/template-name/template-name.component.scss b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/template-name/template-name.component.scss deleted file mode 100644 index 7c5a0352..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/template-name/template-name.component.scss +++ /dev/null @@ -1,27 +0,0 @@ - -.temp-name-input { - color: rgba(0, 0, 0, 0.85); - font-weight: 600; - font-size: 18px; - line-height: 1.4; -} - -.error { - border-color: #ff4d4f; - box-shadow: none; -} - -.error-text { - position: absolute; - left: 0; - bottom: -16px; -} - -h4 { - transition: 0.25s all; - border: 1px solid transparent; - &:hover { - border: 1px solid #d9d9d9; - border-radius: 4px; - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/template-name/template-name.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/template-name/template-name.component.spec.ts deleted file mode 100644 index 983983ea..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/template-name/template-name.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TemplateNameComponent } from './template-name.component'; - -describe('TemplateNameComponent', () => { - let component: TemplateNameComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [TemplateNameComponent] - }); - fixture = TestBed.createComponent(TemplateNameComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/template-name/template-name.component.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/template-name/template-name.component.ts deleted file mode 100644 index a8c7bc1b..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/components/template-name/template-name.component.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - Input, - NgZone, OnInit, Renderer2, - ViewChild -} from '@angular/core'; -import {Socket} from "ngx-socket-io"; -import {SocketEvents} from "@shared/socket-events"; - -@Component({ - selector: 'worklenz-template-name', - templateUrl: './template-name.component.html', - styleUrls: ['./template-name.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TemplateNameComponent implements OnInit { - @ViewChild("input", {static: false}) input!: ElementRef; - @Input({required: true}) templateId: string | null = null; - @Input({required: true}) templateName: string | null = null; - - showInput = false - isEmpty = false; - - constructor( - private readonly socket: Socket, - private readonly ngZone: NgZone, - private readonly renderer: Renderer2, - private readonly cdr: ChangeDetectorRef - ) { - } - - ngOnInit() { - this.socket.on(SocketEvents.PT_NAME_CHANGE.toString(), this.handleResponse) - } - - focusInput() { - this.showInput = true; - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - if (this.input) { - this.input.nativeElement.focus(); - this.cdr.markForCheck(); - } - }, 100); - }) - } - - onBlur() { - if (this.validate()) { - this.changeName(); - } - } - - - validate() { - if (this.templateName?.trim() === "" || !this.templateName) { - this.isEmpty = true; - return false; - } - this.isEmpty = false; - return true; - } - - changeName() { - this.socket.emit( - SocketEvents.PT_NAME_CHANGE.toString(), JSON.stringify({ - template_id: this.templateId, - template_name: this.templateName - }) - ) - } - - private handleResponse = (response: { - template_id: string; - template_name: string | null; - }) => { - if (response) { - this.templateName = response.template_name; - this.showInput = false; - this.cdr.markForCheck(); - } - }; - -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/interfaces.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/interfaces.ts deleted file mode 100644 index 0d416b19..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/interfaces.ts +++ /dev/null @@ -1,111 +0,0 @@ -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {ITaskLabel} from "@interfaces/task-label"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITask} from "@interfaces/task"; - -export interface IPTTaskStatusCategory { - is_todo?: boolean; - is_doing?: boolean; - is_done?: boolean; -} -export interface IPTTask { - id?: string; - name?: string; - status?: string; - status_color?: string; - priority?: string; - start_date?: string; - end_date?: string; - total_hours?: number; - total_minutes?: number; - status_name?: string; - project_id?: string; - project_name?: string; - updated_at?: string; - name_color?: string; - sub_tasks_count?: number; - total_tasks_count?: number; - is_sub_task?: boolean; - parent_task_name?: string; - parent_task_id?: string; - show_handles?: boolean; - min?: number; - max?: number; - sort_order?: number; - color_code?: string; - priority_color?: string; - show_sub_tasks?: boolean; - sub_tasks?: IPTTask[]; - sub_tasks_loading?: boolean; - statuses?: ITaskStatusViewModel[]; - labels?: ITaskLabel[]; - all_labels?: ITaskLabel[]; - priority_value?: number; - description?: string; - completed_at?: string; - created_at?: string; - phase_id?: string; - phase_name?: string; - phase_color?: string; - priority_name?: string; - status_category?: IPTTaskStatusCategory; - total_time_string?: string -} - -export interface IPTTaskListContextMenuEvent { - event: MouseEvent, - task: IPTTask -} - -export interface IPTTaskListGroup { - id: string; - name: string; - color_code: string; - category_id?: string; - old_category_id?: string; - tasks: IPTTask[]; -} - -export interface IPTTaskListColumn { - id?: string; - name?: string; - key?: string; - index?: number; - pinned?: true; - project_id?: string; -} - -export interface IPTTaskListConfig { - id: string; - search: string | null; - projects: string | null; - count?: boolean; - parent_task?: string; - group?: string; - isSubtasksInclude: boolean; - field?: string, - order?: string, - statuses?: string, - members?: string, - priorities?: string, - labels?: string, - archived?: string -} - -export interface IPTTaskListContextMenuEvent { - event: MouseEvent, - task: IPTTask -} - -export interface IPTTaskCreateRequest extends ITask { - status_id?: string; - template_id?: string; - task_index?: number; - attachments?: string[]; - labels?: string[]; - parent_task_id?: string; - reporter_id?: string; - team_id?: string; - priority_id?: string; - phase_id?: string; -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/pipes/ellipsis-tooltip-title.pipe.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/pipes/ellipsis-tooltip-title.pipe.ts deleted file mode 100644 index be0cddf0..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/pipes/ellipsis-tooltip-title.pipe.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; - -@Pipe({ - name: 'ellipsisTooltipTitlePT' -}) -export class EllipsisTooltipTitlePipe2 implements PipeTransform { - - transform(value: string | undefined, limit: number): string { - if (!value) return ''; - return value.length > limit ? value : ''; - } - -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/pipes/end-name-check.pipe.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/pipes/end-name-check.pipe.ts deleted file mode 100644 index bc8b5228..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/pipes/end-name-check.pipe.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; -import {ITaskLabel} from "@interfaces/task-label"; - -@Pipe({ - name: 'endNameCheckPT' -}) -export class EndNameCheckPipe2 implements PipeTransform { - transform(value: ITaskLabel, ...args: unknown[]): unknown { - return !!(value.end && value.names); - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/pipes/sub-tasks-arrow-color.pipe.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/pipes/sub-tasks-arrow-color.pipe.ts deleted file mode 100644 index 448aa0eb..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/pipes/sub-tasks-arrow-color.pipe.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; -import {IPTTask} from "../interfaces"; - -@Pipe({ - name: 'subTasksArrowColorPT' -}) -export class SubTasksArrowColorPipe2 implements PipeTransform { - transform(value: IPTTask, ...args: unknown[]): string { - return !!value.sub_tasks_count ? '#191919' : 'rgba(0, 0, 0, 0.45)'; - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/pipes/sub-tasks-arrow-icon.pipe.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/pipes/sub-tasks-arrow-icon.pipe.ts deleted file mode 100644 index 831e7191..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/pipes/sub-tasks-arrow-icon.pipe.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; - -@Pipe({ - name: 'subTasksArrowIconPT' -}) -export class SubTasksArrowIconPipe2 implements PipeTransform { - transform(value?: boolean, ...args: unknown[]): string { - return value ? 'down' : 'right'; - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/pipes/truncate-if-long.pipe.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/pipes/truncate-if-long.pipe.ts deleted file mode 100644 index a1825e58..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/pipes/truncate-if-long.pipe.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; - -@Pipe({ - name: 'truncateIfLongPT' -}) -export class TruncateIfLongPipe2 implements PipeTransform { - transform(value?: string, len = 0): string { - if (!value) return ''; - return value.length > len ? `${value.slice(0, len)}...` : value; - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/project-template-edit-view.component.html b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/project-template-edit-view.component.html deleted file mode 100644 index 0604dbc5..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/project-template-edit-view.component.html +++ /dev/null @@ -1,147 +0,0 @@ -
-
- - - - - - - - -
- -
- -
- - -
-
- -
- - -
- -
-
-
- - - -
- - -
-
-
- - - - - -
- No tasks available -
- - - - - - -
- - - -
-
-   -
-
- - -
-
-
-
-
-
- -
-
- -
- -
-
-
-
-
-
-
- -
-
- - - - - - diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/project-template-edit-view.component.scss b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/project-template-edit-view.component.scss deleted file mode 100644 index 41fc90e6..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/project-template-edit-view.component.scss +++ /dev/null @@ -1,403 +0,0 @@ -.task-status-color { - border-width: 1.4px; - border-style: solid; - border-radius: 4px; - width: 1px; -} - -.task-name-color { - height: 20px; - margin: auto; - border-color: rgb(169, 169, 169); - background-color: rgb(169, 169, 169); - position: absolute; - top: 0; - bottom: 0; - left: 0; - border-width: 1.2px; -} - -.editable-cell { - white-space: nowrap; - max-width: 255px; - overflow: hidden; - text-overflow: ellipsis; -} - -nz-date-picker { - background: transparent; -} - -//.dropdown-animation { -// //transition: all .05s ease-in; -//} - -.expanded { - transform: rotate(-90deg); -} - -.dropdown-highlight { - padding: 1px; -} - -.highlight-col { - border: 1px solid #1890ff !important; -} - -.dropdown-highlight:hover { - background-color: rgba(208, 238, 250, 0.33); - border: #5587f5 1px solid; - border-radius: 3px; -} - -.pointer-text { - cursor: text; -} - -.plus-icon { - display: none; - position: absolute; - right: 0; - z-index: 9; - background-color: #e2e7ea; -} - -tr:hover .plus-icon { - display: block; -} - -.selected { - background: #1890ff14 !important; - - .task-name, - .task-drag-handler { - background: #1890ff14 !important; - } -} - -.sub-task-background-color { - background-color: #f5f5f58a; -} - -.drop-down-btn { - padding: 4px 11px; - - .anticon-caret-down { - color: rgba(0, 0, 0, 0.65); - } -} - -.status-color { - width: 11px; - height: 11px; - border-radius: 2px; - margin-right: 7px; -} - -div { - box-sizing: border-box; -} - -.flex-table { - display: flex; - width: max-content; -} - -.table-container { - overflow: auto; - display: flex; - // flex-direction: column; -} - -.tasks-table { - width: max-content; - margin-left: 3px; - border-right: 1px solid #f0f0f0; -} - -.column-trigger { - background-color: #F4F5F7; - height: 40px; - width: 40px; - justify-content: center; - display: flex; - align-items: center; -} - -.header { - margin-bottom: 0px; - position: sticky; - top: 0; - background-color: white; - z-index: 2; - - .flex-row { - padding: 4px 11px; - background-color: #fafafa; - border-top: 1px solid #f0f0f0; - border-bottom: 1px solid #f0f0f0; - border-right: 1px solid #f0f0f0; - display: flex; - align-items: center; - flex-direction: row; - } -} - -.br-right { - border-right: 1px solid #f0f0f0; -} - -.task-check { - text-align: center; - padding: 8px 6px 8px 0px !important; - position: sticky; - left: 24px; - z-index: 1; -} - -.task-key { - width: 85px; -} - -.task-arrow { - width: 24px; - padding: 0px !important; - display: flex; - align-items: center; - border: none !important; - position: sticky; - left: 47px; - z-index: 1; -} - -.task-name { - width: 450px; - min-width: 450px; - position: sticky; - left: 71px; - z-index: 1; - background-color: white; - - nz-filter-trigger { - margin-left: auto; - } -} - -.task-description { - width: 225px; -} - -.task-progress { - width: 80px; -} - -.task-members { - width: 160px; -} - -.task-labels { - width: 210px; -} - -.task-status { - width: 120px; -} - -.task-priority { - width: 120px; -} - -.task-time-tracking { - width: 120px; -} - -.task-estimation { - width: 120px; -} - -.task-start-date, .task-due-date, .task-completed-date, .task-created-date, .task-update-date { - width: 150px; -} - -.task-drag-handler { - padding: 0px 0px 0px 4px !important; - width: 24px; - border-bottom: 1px solid #f0f0f0; - border-right: none !important; - position: sticky; - left: 0px; -} - -.inner-subtask-create { - height: 42px; - display: flex; - align-items: center; - max-width: 1316px; - overflow: hidden; - background-color: rgb(250 250 250); - position: sticky; - left: 0; - border-bottom: 1px solid #eaeaea; - border-top: 1px solid #eaeaea; - @media(max-width: 1400px) { - max-width: 1136px; - } - @media(max-width: 1200px) { - max-width: 956px; - } - - &.highlight-col { - background-color: white; - } -} - -.new-subtask-divider { - width: 50px; - - &.divider-large { - width: 135px; - } -} - -worklenz-quick-task { - display: block; -} - - -.overflow-x-auto { - overflow-x: auto; - overflow-y: hidden; -} - -.panel { - padding: 0 0; - background-color: white; - max-height: calc(100% + 8px); - overflow: hidden; - transition: max-height 0.1s ease-out; - border-right: 1px solid #f0f0f0; - // &.show { - // height: auto; - // transition: height 0.05s ease-out; - // } -} - -.panel-left-border { - position: absolute; - content: ''; - top: 0; - bottom: 0; - width: 3px; - z-index: 3; - border-bottom-left-radius: 4px; -} - -.collapse.btn .collapse-icon { - transition: transform 0.1s; - transform: rotate(0); -} - -.collapse.btn.active .collapse-icon { - transition: transform 0.1s; - transform: rotate(90deg); -} - - -.drop-down-btn { - padding: 4px 11px; - - .anticon-caret-down { - color: #d9d9d9; - } -} - -.status-color { - width: 11px; - height: 11px; - border-radius: 2px; - margin-right: 7px; -} - -.drop-down-btn { - nz-badge { - margin-top: -2px; - } - - &.active { - color: #1890ff; - border-color: #1890ff; - background-color: #E6F7FF; - - .drop-down-btn { - .anticon-caret-down { - color: #1890ff; - } - } - } -} - -.ant-badge-count-sm { - font-size: 11px; -} - -.tab-name-edit { - // width: 0px; - overflow: hidden; - - span { - margin-right: 0px; - font-size: 14px; - } - - svg { - margin-right: 0px; - } -} - -.new-task-input { - // height: 32px; - padding-left: 4px; - padding-top: 4px; - padding-bottom: 4px; - border-bottom: 1px solid #f0f0f0; - border-top: 1px solid #f0f0f0; - max-width: 1316px; - overflow-x: auto; - @media(max-width: 1400px) { - max-width: 1136px; - } - @media(max-width: 1200px) { - max-width: 956px; - } -} - -.tasks-empty-placeholder { - width: 100%; - height: 42px; - background: #fafafa; -} - -.no-data-img-holder { - width: 100px; - margin-top: 42px; -} - -.add-field-button { - position: absolute; - top: 46px; - right: 0; -} - -.name-input { - max-width: 1270px; - width: 1270px; - display: block; - @media (max-width: 1400px) { - max-width: 1090px; - width: 1090px; - } - @media (max-width: 1200px) { - max-width: 910px; - width: 910px; - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/project-template-edit-view.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/project-template-edit-view.component.spec.ts deleted file mode 100644 index ed1ad786..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/project-template-edit-view.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ProjectTemplateEditViewComponent } from './project-template-edit-view.component'; - -describe('ProjectTemplateEditViewComponent', () => { - let component: ProjectTemplateEditViewComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ProjectTemplateEditViewComponent] - }); - fixture = TestBed.createComponent(ProjectTemplateEditViewComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/project-template-edit-view.component.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/project-template-edit-view.component.ts deleted file mode 100644 index e06415ef..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/project-template-edit-view.component.ts +++ /dev/null @@ -1,488 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, HostListener, NgZone, OnDestroy, - OnInit, - QueryList, Renderer2, - ViewChildren -} from '@angular/core'; -import {TaskListRowComponent} from "./task-list-row/task-list-row.component"; -import {ITaskStatusCategory} from "@interfaces/task-status-category"; -import {PtTaskListService} from "./services/pt-task-list.service"; -import {ActivatedRoute, Router} from "@angular/router"; -import {PtTaskListHashMapService} from "./services/pt-task-list-hash-map.service"; -import {Socket} from "ngx-socket-io"; -import {UtilsService} from "@services/utils.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {IPTTask, IPTTaskListConfig, IPTTaskListGroup} from "./interfaces"; -import {IGroupByOption, ITaskListGroupChangeResponse} from "../../modules/task-list-v2/interfaces"; -import {CdkDragDrop, moveItemInArray, transferArrayItem} from "@angular/cdk/drag-drop"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {deepClone} from "@shared/utils"; -import {AddTaskInputComponent} from "./components/add-task-input/add-task-input.component"; -import {PtTasksApiService} from "@api/pt-tasks-api.service"; -import {PtTaskPhasesApiService} from "@api/pt-task-phases-api.service"; -import {PtLabelsApiService} from "@api/pt-labels-api.service"; -import {PtStatusesApiService} from "@api/pt-statuses-api.service"; -import {PtPrioritiesApiService} from "@api/pt-priorities-api.service"; -import {SocketEvents} from "@shared/socket-events"; -import {AppService} from "@services/app.service"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-project-template-edit-view', - templateUrl: './project-template-edit-view.component.html', - styleUrls: ['./project-template-edit-view.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectTemplateEditViewComponent implements OnInit, OnDestroy { - @ViewChildren('row') rows!: QueryList; - @ViewChildren('scrollPanel') scrollPanels!: QueryList; - - scrollBy = 0; - - protected templateId: string | null = null; - templateName: string | null = ''; - protected searchValue: string | null = null; - protected templatesFilterString: string | null = null; - protected selected = false; - protected loadingGroups = false; - protected groupChanging = false; - protected displayPhaseModal = false; - protected displayStatusModal = false; - protected checked = false; - protected indeterminate = false; - - loadingFiltering = false; - loadingStatuses = false; - loadingPriorities = false; - loadingCategories = false; - loadingPhases = false; - loadingLabels = false; - - protected taskId: string | null = null; - - protected selectedTask: IPTTask | null = null; - protected groupIds: string[] = []; - protected categories: ITaskStatusCategory[] = []; - - protected get loading() { - return this.loadingGroups; - } - - protected get defaultStatus() { - return this.service.statuses.length - ? this.service.statuses[0].id as string - : null; - } - - protected get groups() { - return this.service.groups; - } - - constructor( - private readonly route: ActivatedRoute, - private readonly router: Router, - private readonly cdr: ChangeDetectorRef, - private readonly service: PtTaskListService, - private readonly ngZone: NgZone, - private readonly map: PtTaskListHashMapService, - private readonly socket: Socket, - private readonly renderer: Renderer2, - public readonly utils: UtilsService, - private readonly api: PtTasksApiService, - private readonly labelsApi: PtLabelsApiService, - private readonly statusesApi: PtStatusesApiService, - private readonly prioritiesApi: PtPrioritiesApiService, - private readonly phasesApi: PtTaskPhasesApiService, - private app: AppService, - private readonly auth: AuthService, - ) { - this.templateId = this.route.snapshot.paramMap.get("id"); - this.templateName = this.route.snapshot.paramMap.get("name"); - this.app.setTitle("Edit Template"); - - this.service.settemplateId(this.templateId as string); - this.service.onTaskAddOrDelete$ - .pipe(takeUntilDestroyed()) - .subscribe((value) => { - this.cdr.markForCheck(); - }); - this.service.onRefresh$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.cdr.markForCheck(); - }); - this.service.onRefreshSubtasksIncluded - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.cdr.markForCheck(); - }); - - } - - ngOnInit() { - this.service.isSubtasksIncluded = false; - this.init(true); - } - - init(loading: boolean) { - void Promise.all([ - this.getGroups(loading), - // this.getColumns(), - this.getLabels(), - this.getStatuses(), - this.getPriorities(), - this.getCategories(), - this.getPhases(), - ]); - this.socket.on(SocketEvents.PT_TASK_SORT_ORDER_CHANGE.toString(), this.handleSortOrderResponse); - } - - ngOnDestroy() { - this.ngZone.runOutsideAngular(() => { - this.service.reset(); - this.service.groups = []; - this.map.reset(); - }); - this.socket.removeListener(SocketEvents.PT_TASK_SORT_ORDER_CHANGE.toString(), this.handleSortOrderResponse); - } - - protected isGroupByPhase() { - return this.service.getCurrentGroup().value === this.service.GROUP_BY_PHASE_VALUE; - } - - private getConf(parentTaskId?: string): IPTTaskListConfig { - const config: IPTTaskListConfig = { - id: this.templateId as string, - search: this.searchValue, - projects: this.templatesFilterString, - group: this.service.getCurrentGroup().value, - isSubtasksInclude: false - }; - - if (parentTaskId) - config.parent_task = parentTaskId; - - return config; - } - - protected async displaySubTasks(task: IPTTask, row: TaskListRowComponent, groupId: string) { - if (!task.id && task.sub_tasks_loading) return; - - // Ignore loading sub-tasks from api when we know it's empty - if (!task.show_sub_tasks && task.sub_tasks_count === 0) { - task.show_sub_tasks = true; - task.sub_tasks = []; - return; - } - - task.sub_tasks_loading = true; - task.show_sub_tasks = !task.show_sub_tasks; - if (task.show_sub_tasks) { - task.sub_tasks = await this.getSubTasks(task); - for (const t of task.sub_tasks) { - this.map.add(groupId, t); - } - } else { - for (const t of task.sub_tasks || []) { - this.map.deselectTask(t); - } - task.sub_tasks = []; - } - - task.sub_tasks_loading = false; - - row.detectChanges(); - this.cdr.markForCheck(); - } - - protected toggleGroup(event: MouseEvent, panel: HTMLDivElement) { - this.ngZone.runOutsideAngular(() => { - const target = event.target as Element; - if (!target) return; - target.closest('.btn')?.classList.toggle("active"); - const maxHeight = panel.style.maxHeight === "0px" ? panel.scrollHeight + 8 + 'px' : "0px"; - this.renderer.setStyle(panel, "max-height", maxHeight); - }); - } - - protected trackById(index: number, item: IPTTask | IPTTaskListGroup) { - return item.id; - } - - protected onDrop(event: CdkDragDrop) { - const fromIndex = event.previousIndex; - const toIndex = event.currentIndex; - - const fromGroup = event.previousContainer.data; - const toGroup = event.container.data; - - // collapse button icon rotate - if (fromGroup.tasks.length == 0) { - this.ngZone.runOutsideAngular(() => { - const buttonElement = document.getElementById(`${event.previousContainer.id}`)?.closest("div")?.parentNode?.parentNode?.parentNode?.querySelector("button.collapse.active"); - buttonElement?.classList.remove('active'); - }); - } - const task = event.item.data; - const toPos = toGroup.tasks[toIndex]?.sort_order; - - this.socket.emit(SocketEvents.PT_TASK_SORT_ORDER_CHANGE.toString(), { - template_id: this.service.gettemplateId(), - from_index: fromGroup.tasks[fromIndex].sort_order, - to_index: toPos || toGroup.tasks[toGroup.tasks.length - 1]?.sort_order || -1, - to_last_index: !toPos, - from_group: fromGroup.id, - to_group: toGroup.id, - group_by: this.service.getCurrentGroup().value, - task, - team_id: this.auth.getCurrentSession()?.team_id - }); - - if (fromGroup.id === toGroup.id) { // same group - moveItemInArray(event.container.data.tasks, fromIndex, toIndex); - } else { - transferArrayItem( - event.previousContainer.data.tasks, - event.container.data.tasks, - event.previousIndex, - event.currentIndex - ); - - this.map.remove(task); - this.map.add(toGroup.id, task); - - this.service.emitGroupChange(toGroup.id, task.id as string, toGroup.color_code); - } - } - - protected async bulkUpdateSuccess() { - await this.getGroups(true); - } - - protected selectTasksInGroup(checked: boolean, group: IPTTaskListGroup) { - for (const task of group.tasks) { - if (checked) { - this.map.selectTask(task); - } else { - this.map.deselectTask(task); - } - } - } - - private mapTasks(groups: IPTTaskListGroup[]) { - for (const group of groups) { - this.map.registerGroup(group); - for (const task of group.tasks) { - if (task.start_date) task.start_date = new Date(task.start_date) as any; - if (task.end_date) task.end_date = new Date(task.end_date) as any; - } - } - } - - private toggleFocusCls(focused: boolean, element: HTMLElement) { - if (focused) { - this.renderer.addClass(element, this.service.HIGHLIGHT_COL_CLS); - } else { - this.renderer.removeClass(element, this.service.HIGHLIGHT_COL_CLS); - } - } - - onShowChange(show: boolean) { - if (!show) { - this.selectedTask = null; - } - } - - private handleNewTaskReceive(value: ITaskListGroupChangeResponse) { - if (value.isSubTask) { - const row = this.rows - .find(r => r.id === value.taskId); - if (row) { - row.detectChanges(); - } - } - this.cdr.markForCheck(); - } - - private handleSortOrderResponse = (tasks: IPTTask[]) => { - for (const element of tasks) { - const taskId = element.id; - if (taskId) { - const task = this.map.tasks.get(taskId); - if (task) { - task.sort_order = element.sort_order; - this.map.tasks.set(taskId, task); - } - } - } - }; - - protected handleFocusChange(focused: boolean, element: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - this.toggleFocusCls(focused, element); - }); - } - - protected quickTaskFocusChange(focused: boolean, td: HTMLElement, _ref: AddTaskInputComponent) { - this.ngZone.runOutsideAngular(() => { - this.toggleFocusCls(focused, td); - }); - } - - protected async onGroupByChange(group: IGroupByOption) { - this.service.setCurrentGroup(group); - this.groupChanging = true; - await this.getGroups(true); - setTimeout(() => { - this.groupChanging = false; - this.cdr.markForCheck(); - }, 100); // wait for animations to be finished - } - - async handleFilterSearch(searchText: string | null) { - this.loadingFiltering = true; - this.searchValue = searchText; - await this.getGroups(true); - this.loadingFiltering = false; - this.cdr.markForCheck(); - } - - private async getGroups(loading: boolean) { - if (!this.templateId) return; - try { - this.map.deselectAll(); - this.loadingGroups = loading; - const config = this.getConf(); - config.isSubtasksInclude = this.service.isSubtasksIncluded; - const res = await this.api.getTaskList(config) as IServerResponse; - if (res.done) { - const groups = deepClone(res.body); - this.groupIds = groups.map((g: IPTTaskListGroup) => g.id); - this.mapTasks(groups); - this.service.groups = groups; - } - this.loadingGroups = false; - } catch (e) { - this.loadingGroups = false; - } - this.cdr.markForCheck(); - } - - private async getSubTasks(task: IPTTask) { - let subTasks: IPTTask[] = []; - if (task?.id) { - try { - const config = this.getConf(task.id); - const res = await this.api.getTaskList(config) as IServerResponse; - if (res.done) subTasks = res.body; - } catch (e) { - // ignored - } - } - return subTasks; - } - - private async getStatuses() { - if (!this.templateId) return; - try { - this.loadingStatuses = true; - const res = await this.statusesApi.get(this.templateId); - if (res.done) - this.service.statuses = res.body; - this.loadingStatuses = false; - } catch (e) { - this.loadingStatuses = false; - } - } - - private async getPriorities() { - try { - this.loadingPriorities = true; - const res = await this.prioritiesApi.get(); - if (res.done) - this.service.priorities = res.body; - this.loadingPriorities = false; - } catch (e) { - this.loadingPriorities = false; - } - } - - private async getCategories() { - try { - this.loadingCategories = true; - const res = await this.statusesApi.getCategories(); - if (res.done) - this.categories = res.body; - this.loadingCategories = false; - } catch (e) { - this.loadingCategories = false; - } - } - - private async getPhases() { - if (!this.templateId) return; - try { - this.loadingPhases = true; - const res = await this.phasesApi.get(this.templateId); - if (res.done) - this.service.phases = res.body; - this.loadingPhases = false; - } catch (e) { - this.loadingPhases = false; - } - } - - protected async getLabels() { - try { - this.loadingLabels = true; - const res = await this.labelsApi.get(this.templateId); - if (res.done) - this.service.labels = res.body; - this.loadingLabels = false; - } catch (e) { - this.loadingLabels = false; - } - } - - @HostListener('scroll', ['$event.target']) - onScroll(target: HTMLElement) { - this.ngZone.runOutsideAngular(() => { - const cls = 'scrolling-panel'; - this.scrollBy = target.scrollLeft; - if (this.scrollBy > 0) { - target.classList.add(cls) - } else { - target.classList.remove(cls) - } - }); - } - - // eslint-disable-next-line @typescript-eslint/ban-types - debounce(func: Function, delay: number) { - let timeoutId: any; - return (...args: any[]) => { - clearTimeout(timeoutId); - timeoutId = setTimeout(() => func.apply(this, args), delay); - }; - } - - openAddColumnDrawer() { - this.displayPhaseModal = true; - } - - openStatusDrawer() { - this.displayStatusModal = true; - } - - onBack() { - this.router.navigate([`/worklenz/settings/project-templates`]); - } - - refreshWithoutLoading() { - this.init(false); - } - -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/services/pt-task-list-hash-map.service.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/services/pt-task-list-hash-map.service.ts deleted file mode 100644 index 9807b0d1..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/services/pt-task-list-hash-map.service.ts +++ /dev/null @@ -1,205 +0,0 @@ -import {Injectable} from '@angular/core'; -import {Subject} from "rxjs"; -import {IPTTask, IPTTaskListGroup} from "../interfaces"; - -@Injectable({ - providedIn: 'root' -}) -export class PtTaskListHashMapService { - private readonly _selectSbj$ = new Subject(); - private readonly _deselectSbj$ = new Subject(); - private readonly _deselectAllSbj$ = new Subject(); - - /** Map */ - private readonly _groupTaskMap = new Map(); - /** Map */ - private readonly _taskGroupIdsMap = new Map(); - /** Map */ - private readonly _selectedTasksMap = new Map(); - /** Map */ - private readonly _allTasksMap = new Map(); - /** Map(); - - private _selectedCount = 0; - - public get tasks() { - return this._allTasksMap; - } - - public get onSelect$() { - return this._selectSbj$.asObservable(); - } - - public get onDeselect$() { - return this._deselectSbj$.asObservable(); - } - - public get onDeselectAll$() { - return this._deselectAllSbj$.asObservable(); - } - - public reset() { - this._groupTaskMap.clear(); - this._taskGroupIdsMap.clear(); - this._selectedTasksMap.clear(); - this._allTasksMap.clear(); - this._subTasksMap.clear(); - this._selectedCount = 0; - } - - public registerGroup(group: IPTTaskListGroup) { - for (const task of group.tasks) { - this.add(group.id, task); - } - } - - public add(groupId: string, task: IPTTask) { - if (!task.id) return; - this.updateGroupTaskMap(groupId, task.id); - this._taskGroupIdsMap.set(task.id, groupId); - this._allTasksMap.set(task.id, task); - - if (task.parent_task_id) { - this.updateSubtasksMap(task.parent_task_id, task) - } - } - - public addGroupTask(groupId: string, task: IPTTask) { - if (!task.id) return; - this._taskGroupIdsMap.set(task.id, groupId); - } - - public has(taskId: string) { - return this._allTasksMap.has(taskId); - } - - public remove(task: IPTTask) { - if (!task.id) return; - this.deselectTask(task); - this._taskGroupIdsMap.get(task.id); - this._allTasksMap.delete(task.id); - } - - private updateGroupTaskMap(groupId: string, taskId: string, selected?: boolean) { - const map = this._groupTaskMap.get(groupId); - if (map) { - if (typeof selected === "boolean") { - map[taskId] = selected; - } else { - delete map[taskId]; - } - - this._groupTaskMap.set(groupId, map); - } else { - this._groupTaskMap.set(groupId, {[taskId]: selected || false}); - } - } - - private updateSubtasksMap(parentTaskId: string, task: IPTTask, selected?: boolean) { - const subtasks = this._subTasksMap.get(parentTaskId) || []; - const isParentTaskAvailable = subtasks.some((subtask) => subtask.id === task.id); - - // Only push the subtask if the parent task is not available - if (!isParentTaskAvailable) { - subtasks.push(task); - this._subTasksMap.set(parentTaskId, subtasks); - } - } - - public selectTask(task: IPTTask) { - if (this._selectedTasksMap.get(task.id as string)) return; - - this._selectedTasksMap.set(task.id as string, task); - this._selectedCount++; - - this.updateGroupTaskMap( - this._taskGroupIdsMap.get(task.id as string) as string, - task.id as string, - true - ); - - this._selectSbj$.next(task); - - } - - public deselectTask(task: IPTTask) { - if (this._selectedTasksMap.has(task.id as string)) { - this._selectedTasksMap.delete(task.id as string); - this._selectedCount--; - - this.updateGroupTaskMap( - this._taskGroupIdsMap.get(task.id as string) as string, - task.id as string, - false - ); - - this._deselectSbj$.next(task); - } - } - - private deselectLocalGroups() { - for (const [groupId, task] of this._groupTaskMap) { - for (const taskId in task) { - this.updateGroupTaskMap(groupId, taskId, false); - } - } - } - - public deselectAll() { - if (!this._selectedTasksMap.size) return; - - this.deselectLocalGroups(); - this._selectedTasksMap.clear(); - this._selectedCount = 0; - - this._deselectAllSbj$.next(); - } - - public isAllSelected(groupId: string) { - const tasks = this._groupTaskMap.get(groupId); - - if (tasks) { - for (const taskId in tasks) - if (!tasks[taskId]) return false; - return true; - } - - return false; - } - - public isAllDeselected(groupId: string) { - const tasks = this._groupTaskMap.get(groupId); - - if (tasks) { - for (const taskId in tasks) - if (tasks[taskId]) return false; - } - - return true; - } - - public getSelectedCount() { - return this._selectedCount; - } - - public getGroupId(taskId: string) { - return this._taskGroupIdsMap.get(taskId); - } - - public getSelectedTasks() { - const tasks = []; - for (const [, task] of this._selectedTasksMap.entries()) { - tasks.push(task); - } - return tasks; - } - - public getSelectedTaskIds(): string[] { - const tasks = []; - for (const [taskId] of this._selectedTasksMap.entries()) { - tasks.push(taskId); - } - return tasks; - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/services/pt-task-list.service.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/services/pt-task-list.service.ts deleted file mode 100644 index 471c7133..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/services/pt-task-list.service.ts +++ /dev/null @@ -1,308 +0,0 @@ -import {Injectable} from '@angular/core'; -import {BehaviorSubject, Subject} from "rxjs"; -import {IPTTask, IPTTaskListColumn, IPTTaskListContextMenuEvent, IPTTaskListGroup} from "../interfaces"; -import {IGroupByOption, ITaskListGroupChangeResponse} from "../../../modules/task-list-v2/interfaces"; -import {ITaskLabel} from "@interfaces/task-label"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {ITaskPrioritiesGetResponse} from "@interfaces/api-models/task-priorities-get-response"; -import {ITaskPhase} from "@interfaces/api-models/task-phase"; -import {Socket} from "ngx-socket-io"; -import {PtTaskListHashMapService} from "./pt-task-list-hash-map.service"; - -@Injectable({ - providedIn: 'root' -}) -export class PtTaskListService { - private readonly colsSbj$ = new Subject(); - private readonly labelsSbj$ = new Subject(); - private readonly statusesSbj$ = new Subject(); - private readonly prioritiesSbj$ = new Subject(); - private readonly contextMenuSbj$ = new Subject(); - - private readonly taskAddOrDeleteSbj$ = new BehaviorSubject(null); - private readonly refreshSbj$ = new Subject(); - private readonly groupChangeSbj$ = new Subject<{ groupId: string; taskId: string; color: string; }>(); - - private readonly phasesSbj$ = new Subject(); - private readonly updateGroupProgressSbj$ = new Subject<{ taskId: string; }>(); - private readonly refreshSubtasksIncludedSbj$ = new Subject(); - - public readonly HIGHLIGHT_COL_CLS = 'highlight-col'; - - public readonly GROUP_BY_STATUS_VALUE = "status"; - public readonly GROUP_BY_PRIORITY_VALUE = "priority"; - public readonly GROUP_BY_PHASE_VALUE = "phase"; - public readonly GROUP_BY_OPTIONS: IGroupByOption[] = [ - {label: "Status", value: this.GROUP_BY_STATUS_VALUE}, - {label: "Priority", value: this.GROUP_BY_PRIORITY_VALUE}, - {label: "Phase", value: this.GROUP_BY_PHASE_VALUE} - ]; - - public groups: IPTTaskListGroup[] = []; - - private _templateId: string | null = null; - public _cols: IPTTaskListColumn[] = []; - private _labels: ITaskLabel[] = []; - private _statuses: ITaskStatusViewModel[] = []; - private _priorities: ITaskPrioritiesGetResponse[] = []; - private _phases: ITaskPhase[] = []; - - public isSubtasksIncluded = false; - - private get _currentGroup(): IGroupByOption { - const key = localStorage.getItem("worklenz.pt-t-list.group_by"); - if (key) { - const g = this.GROUP_BY_OPTIONS.find(o => o.value === key); - if (g) - return g; - } - return this.GROUP_BY_OPTIONS[0]; - } - - private set _currentGroup(option) { - localStorage.setItem("worklenz.pt-t-list.group_by", option.value); - } - - - public set columns(value) { - this._cols = value; - this.emitColsChange(); - } - - public get columns() { - return this._cols; - } - - public set labels(value) { - this._labels = value; - this.labelsSbj$.next(); - } - - public get labels() { - return this._labels; - } - - public set priorities(value) { - this._priorities = value; - this.prioritiesSbj$.next(); - } - - public get priorities() { - return this._priorities; - } - - public set phases(value) { - this._phases = value; - this.phasesSbj$.next(); - } - - public get phases() { - return this._phases; - } - - get onColumnsChange$() { - return this.colsSbj$.asObservable(); - } - - get onLabelsChange$() { - return this.labelsSbj$.asObservable(); - } - - get onStatusesChange$() { - return this.statusesSbj$.asObservable(); - } - - get onPrioritiesChange$() { - return this.prioritiesSbj$.asObservable(); - } - - get onContextMenu$() { - return this.contextMenuSbj$.asObservable(); - } - - get onTaskAddOrDelete$() { - return this.taskAddOrDeleteSbj$.asObservable(); - } - - get onGroupChange$() { - return this.groupChangeSbj$.asObservable(); - } - - get onRefresh$() { - return this.refreshSbj$.asObservable(); - } - - get onPhaseChange$() { - return this.phasesSbj$.asObservable(); - } - - set statuses(value) { - this._statuses = value; - this.statusesSbj$.next(); - } - - get statuses() { - return this._statuses; - } - - get onRefreshSubtasksIncluded() { - return this.refreshSubtasksIncludedSbj$.asObservable(); - } - - constructor( - private readonly socket: Socket, - private readonly map: PtTaskListHashMapService - ) { - } - - public settemplateId(id: string) { - this._templateId = id; - } - - public gettemplateId() { - return this._templateId; - } - - public setCurrentGroup(group: IGroupByOption) { - this._currentGroup = group; - } - - public getCurrentGroup() { - return this._currentGroup; - } - - public emitColsChange() { - this.colsSbj$.next(); - } - - public emitOnContextMenu(event: MouseEvent, task: IPTTask) { - this.contextMenuSbj$.next({event, task}); - } - - public emitRefresh() { - this.refreshSbj$.next(); - } - - public emitGroupChange(groupId: string, taskId: string, color: string) { - this.groupChangeSbj$.next({groupId, taskId, color}); - } - - public emitTaskAddOrDelete(taskId: string, isSubTask: boolean) { - this.taskAddOrDeleteSbj$.next({ - taskId: taskId, - isSubTask: isSubTask - }); - } - - public emitRefreshSubtasksIncluded() { - this.refreshSubtasksIncludedSbj$.next(); - } - - public getGroupIdByGroupedColumn(task: IPTTask) { - const groupBy = this.getCurrentGroup().value; - if (groupBy === this.GROUP_BY_STATUS_VALUE) - return task.status as string; - - if (groupBy === this.GROUP_BY_PRIORITY_VALUE) - return task.priority as string; - - if (groupBy === this.GROUP_BY_PHASE_VALUE) - return task.phase_id as string; - - return null; - } - - public updateTaskGroup(task: IPTTask, insert = true) { - if (!task.id) return; - const groupId = this.getGroupIdByGroupedColumn(task); - if (groupId) { - // Delete the task from its current group - this.deleteTask(task.id); - // Add the task to the new group - this.addTask(task, groupId, insert); - } - } - - public deleteTask(taskId: string, index: number | null = null) { - - const groupId = this.map.getGroupId(taskId); - if (!groupId || !taskId) return; - - const group = this.groups.find(g => g.id === groupId); - if (!group) return; - - const task = this.map.getSelectedTasks().find(t => t.id === taskId); - if (task?.is_sub_task) { - const parentTask = group.tasks.find(t => t.id === task.parent_task_id); - if (parentTask) { - const index = parentTask.sub_tasks?.findIndex(t => t.id === task.id); - if (typeof index !== "undefined" && index !== -1) { - if (!parentTask.sub_tasks_count) parentTask.sub_tasks_count = 0; - parentTask.sub_tasks_count = Math.max(+parentTask.sub_tasks_count - 1, 0); - parentTask.sub_tasks?.splice(index, 1); - this.emitTaskAddOrDelete(parentTask.id as string, true); - } - } - this.map.remove(task); - } else { // If the task is not a sub-task, remove it from the group's task list. - const taskIndex = index ?? group.tasks.findIndex(t => t.id === taskId); - if (taskIndex !== -1) { - this.map.remove(group.tasks[taskIndex]); - group.tasks.splice(taskIndex, 1); - this.emitTaskAddOrDelete(taskId, false); - } - } - this.map.deselectAll(); - } - - public removeSubtask(taskId: string, index: number | null = null) { - const groupId = this.map.getGroupId(taskId); - if (!groupId || !taskId) return; - - // Find the group that contains the task. - const group = this.groups.find(g => g.id === groupId); - if (!group) return; - - const taskIndex = index ?? group.tasks.findIndex(t => t.id === taskId); - if (taskIndex !== -1) { - this.map.remove(group.tasks[taskIndex]); - group.tasks.splice(taskIndex, 1); - } - - this.map.deselectAll(); - } - - public addTask(task: IPTTask, groupId: string, insert = false) { - const group = this.groups.find(g => g.id === groupId); - if (group && task.id) { - if (task.parent_task_id) { - const parentTask = group.tasks.find(t => t.id === task.parent_task_id); - if (parentTask) { - if (!parentTask.sub_tasks_count) parentTask.sub_tasks_count = 0; - parentTask.sub_tasks_count = +parentTask.sub_tasks_count + 1; - parentTask.sub_tasks?.push(task); - } - } else { - if (insert) { - group.tasks.unshift(task); - } else { - group.tasks.push(task); - } - } - this.map.add(groupId, task); - this.emitTaskAddOrDelete(task.parent_task_id as string, !!task.parent_task_id); - } - } - - public reset() { - this._cols = []; - this._labels = []; - this._statuses = []; - this._priorities = []; - - this._templateId = null; - this.groups = []; - this.isSubtasksIncluded = false; - } - -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/task-list-row/task-list-row.component.html b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/task-list-row/task-list-row.component.html deleted file mode 100644 index 1da673e6..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/task-list-row/task-list-row.component.html +++ /dev/null @@ -1,120 +0,0 @@ -
-
- - -
-
- -
-
- - -
- -
- - -
-
- -
-
- - -
-
-
- -
-
-
- - - - {{ task.name }}   -
-
- - - {{task.sub_tasks_count}} - - - -
-
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/task-list-row/task-list-row.component.scss b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/task-list-row/task-list-row.component.scss deleted file mode 100644 index 75bc588f..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/task-list-row/task-list-row.component.scss +++ /dev/null @@ -1,360 +0,0 @@ -$highlight-color: #1890ff; - -:host { - position: relative; - display: table-row; - vertical-align: inherit; - border-color: inherit; - user-select: none; - - &:hover { - .plus-icon { - display: block; - } - - td { - background: #ecf0f3; - } - - .hidden-arrow { - display: flex !important; - } - } -} - -.hidden-arrow { - display: none !important; -} - -.dropdown-highlight:hover { - background-color: rgba(208, 238, 250, 0.33); - border: #5587f5 1px solid; - border-radius: 3px; -} - -.plus-icon { - display: none; - position: absolute; - right: 0; - z-index: 1; - top: 0; - bottom: 0; - height: 100%; - // margin-top: auto; - // margin-bottom: auto; -} - -.expanded { - transform: rotate(-90deg); -} - -.sub-tasks-arrow { - position: relative; - cursor: pointer; - left: 3px; - width: 16px; - padding: 2px; - border: 1px solid transparent; - z-index: 1; - - .sub-arrow { - width: 10px; - height: 10px; - color: #191919; - margin-left: -2px; - } -} - -.task-name-text { - border: 1px solid transparent; - padding-left: 2px; - border-radius: 4px; - - &:hover { - border: 1px solid #d9d9d9; - } -} - -.task-name { - border: 1px solid transparent; - - &:hover { - cursor: text; - background: #fff; - border-radius: 4px; - } -} - -.highlight-col { - border: 1px solid #1890ff !important; - - nz-date-picker { - box-shadow: none; - } -} - -.editable { - .add-button { - visibility: hidden; - } - - &:hover { - .add-button { - visibility: visible; - } - } -} - -.ant-popover { - width: 500px; -} - -.flex-table { - display: flex; -} - -.rows { - .flex-row { - padding: 3px 12px; - border-bottom: 1px solid #f0f0f0; - border-right: 1px solid #f0f0f0; - background: white; - display: flex; - align-items: center; - max-height: 50px; - border-radius: 0px; - } - - &:hover .flex-row { - background: #f8f7f9; - } -} - -.subtask { - .flex-row { - background: #fcfcfc; - } - -} - -.task-check { - text-align: center; - padding: 8px 6px 8px 0px !important; - border-left: none; - position: sticky; - left: 24px; - z-index: 1; -} - -.task-arrow { - width: 24px; - min-width: 24px; - padding: 8px 11px 8px 2px !important; - border-right: none !important; - position: sticky; - left: 47px; - z-index: 1; - - &.highlight-col { - border-top: 1px solid #188fff !important; - border-left: 1px solid #188fff !important; - border-bottom: 1px solid #188fff !important; - } -} - -.task-name { - width: 450px; - min-width: 450px; - position: sticky; - left: 71px; - z-index: 1; - border-radius: 0px; - padding-right: 65px !important; - - &.highlight-col { - border-top: 1px solid #188fff !important; - border-right: 1px solid #188fff !important; - border-bottom: 1px solid #188fff !important; - border-left: none !important; - } - - &.left-0 { - left: 47px; - } - -} - -.task-key { - width: 85px; - min-width: 85px; - padding-left: 4px !important; - padding-right: 4px !important; - justify-content: center; - - nz-tag { - padding-left: 4px; - padding-right: 4px; - max-width: 80px; - text-overflow: ellipsis; - overflow: hidden; - } -} - -.task-description { - width: 225px; - min-width: 225px; - overflow: hidden; - display: grid !important; -} - -.task-progress { - width: 80px; - min-width: 80px; -} - -.task-labels { - padding: 0px 0px !important; - - .editable { - padding: 6px 11px; - align-items: center; - display: flex; - } -} - -.task-members { - padding: 0px 0px !important; - - .editable { - padding: 6px 11px; - align-items: center; - display: flex; - } -} - -.task-members { - width: 160px; - min-width: 160px; -} - -.task-labels { - width: 220px; - min-width: 220px; -} - -.task-status { - width: 120px; - min-width: 120px; -} - -.task-phase { - width: 150px; - min-width: 150px; -} - -.task-priority { - width: 120px; - min-width: 120px; -} - -.task-time-tracking { - width: 120px; - min-width: 120px; -} - -.task-estimation { - width: 120px; - min-width: 120px; -} - -.task-start-date, .task-due-date, .task-completed-date, .task-created-date, .task-update-date { - width: 150px; - min-width: 150px; -} - -.task-due-date { - padding: 0px 0px !important; - - .editable { - align-items: center; - display: flex; - } -} - -.task-drag-handler { - padding: 0px 0px 0px 4px !important; - width: 24px; - min-width: 24px; - border-right: none !important; - position: sticky; - left: 0px; - z-index: 1; - background-color: white; -} - -.drag-handle { - cursor: grab; - opacity: 0.8; - - &:hover { - span { - color: rgb(24, 144, 255); - } - } - - &:active { - cursor: grabbing; - } -} - -.task-name-text { - width: 100%; - -webkit-line-clamp: 1; - display: -webkit-box; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; -} - -.inner-icon-cont { - // width: 100px; - width: max-content; - display: flex; - justify-content: flex-end; - align-items: center; - column-gap: 4px; -} - -.name-input { - padding: 5px 12px; - border-left: 1px solid transparent; -} - -.double-arrow { - line-height: 16px; - border: none; - cursor: pointer; -} - -.task-placeholder { - width: 100%; - height: 42px; - border: 1px dashed #d9d9d9; - background: #fafafa; -} - -.v-line { - background-color: #188fff !important; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: -5px; - width: 1px; - margin: auto; -} - -.double-arrow { - height: 16px; - margin-top: 4px; -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/task-list-row/task-list-row.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/task-list-row/task-list-row.component.spec.ts deleted file mode 100644 index 812b3e41..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/task-list-row/task-list-row.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TaskListRowComponent } from './task-list-row.component'; - -describe('TaskListRowComponent', () => { - let component: TaskListRowComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [TaskListRowComponent] - }); - fixture = TestBed.createComponent(TaskListRowComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/task-list-row/task-list-row.component.ts b/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/task-list-row/task-list-row.component.ts deleted file mode 100644 index 007dc9ef..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-template-edit-view/task-list-row/task-list-row.component.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { - ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - HostBinding, HostListener, - Input, NgZone, - Output, - Renderer2 -} from '@angular/core'; -import {IPTTask} from "../interfaces"; -import {PtTaskListService} from "../services/pt-task-list.service"; -import {Socket} from "ngx-socket-io"; -import {PtTaskListHashMapService} from "../services/pt-task-list-hash-map.service"; -import {UtilsService} from "@services/utils.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; -import {filter, merge} from "rxjs"; -import {SocketEvents} from "@shared/socket-events"; -import {ITaskListEstimationChangeResponse} from "../../../modules/task-list-v2/interfaces"; - -@Component({ - selector: 'worklenz-pt-task-list-row', - templateUrl: './task-list-row.component.html', - styleUrls: ['./task-list-row.component.scss'] -}) -export class TaskListRowComponent { - @Input({required: true}) task!: IPTTask; - @HostBinding("class") cls = "position-relative task-row"; - - @Output() onShowSubTasks = new EventEmitter(); - - private readonly highlight = 'highlight-col'; - protected readonly Number = Number; - - // Selected for edit - protected editId: string | null = null; - - protected selected = false; - - public get id() { - return this.task.id; - } - - constructor( - private readonly element: ElementRef, - private readonly renderer: Renderer2, - private readonly cdr: ChangeDetectorRef, - public readonly service: PtTaskListService, - private readonly socket: Socket, - private readonly map: PtTaskListHashMapService, - private readonly ngZone: NgZone, - public readonly utils: UtilsService - ) { - this.service.onColumnsChange$ - .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.markForCheck(); - }); - - merge( - this.map.onSelect$.pipe( - filter(value => value.id === this.id), - filter(() => !this.selected) - ), - this.map.onDeselect$.pipe( - filter(value => value.id === this.id), - filter(() => this.selected) - ), - this.map.onDeselectAll$.pipe( - filter(() => this.selected) - ) - ).pipe( - takeUntilDestroyed() - ).subscribe(value => { - this.selected = !this.selected; - this.toggleSelection(); - this.markForCheck(); - }); - } - - private toggleSelection() { - this.ngZone.runOutsideAngular(() => { - const cls = "selected"; - const ele = this.element.nativeElement; - - if (this.selected) { - this.renderer.addClass(ele, cls); - } else { - this.renderer.removeClass(ele, cls); - } - }); - } - - ngOnInit() { - this.registerSocketEvents(); - } - - ngOnDestroy() { - this.unregisterSocketEvents(); - } - - private registerSocketEvents() { - this.socket.on(SocketEvents.PT_TASK_NAME_CHANGE.toString(), this.handleNameChangeResponse); - this.socket.on(SocketEvents.PT_TASK_TIME_ESTIMATION_CHANGE.toString(), this.handleEstimationChangeResponse); - } - - private unregisterSocketEvents() { - this.socket.removeListener(SocketEvents.PT_TASK_NAME_CHANGE.toString(), this.handleNameChangeResponse); - this.socket.removeListener(SocketEvents.PT_TASK_TIME_ESTIMATION_CHANGE.toString(), this.handleEstimationChangeResponse); - } - - @HostListener("contextmenu", ["$event"]) - private onContextMenu(event: MouseEvent) { - this.service.emitOnContextMenu(event, this.task); - } - - focus(tr: HTMLDivElement) { - setTimeout(() => { - const element = tr.querySelector("input"); - element?.focus(); - }); - } - - onCheckChange(checked: boolean) { - if (checked) { - this.map.selectTask(this.task); - } else { - this.map.deselectTask(this.task); - } - - this.toggleSelection(); - } - - openSubTasks() { - this.onShowSubTasks?.emit(this.task); - } - - selectCol(element: HTMLDivElement) { - if (element.classList.contains(this.highlight)) return; - element.classList.add(this.highlight); - } - - deselectCol(element: HTMLDivElement) { - element.classList.remove(this.highlight); - this.editId = null; - } - - handleNameChange(data?: IPTTask) { - if (!data) return; - this.socket.emit(SocketEvents.PT_TASK_NAME_CHANGE.toString(), JSON.stringify({ - task_id: data.id, - name: data.name, - parent_task: this.task.parent_task_id - })); - this.editId = null; - } - - onTaskNameClick(event: MouseEvent, tr1: HTMLDivElement, task: IPTTask) { - event.stopPropagation(); - this.focus(tr1); - this.editId = task.id || null; - } - - public markForCheck() { - this.cdr.markForCheck(); - } - - public detectChanges() { - this.cdr.detectChanges(); - } - - public onDragStart() { - this.map.deselectAll(); - this.detectChanges(); - } - - private handleNameChangeResponse = (response: { id: string; parent_task: string; name: string; }) => { - if (!response) return; - if (this.id !== response.id) return; - - if (this.task && this.task.name != response.name) { - this.task.name = response.name; - this.markForCheck(); - } - }; - - private handleEstimationChangeResponse = (response: ITaskListEstimationChangeResponse) => { - if (response.id === this.id) { - this.task.total_time_string = response.total_time_string; - this.cdr.markForCheck(); - } - }; - -} diff --git a/worklenz-frontend/src/app/administrator/settings/project-templates/project-templates.component.html b/worklenz-frontend/src/app/administrator/settings/project-templates/project-templates.component.html deleted file mode 100644 index 4ccac6de..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-templates/project-templates.component.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - Name - - - - - - {{data.name}} - -
- - - - -
- - - -
-
-
diff --git a/worklenz-frontend/src/app/administrator/settings/project-templates/project-templates.component.scss b/worklenz-frontend/src/app/administrator/settings/project-templates/project-templates.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/settings/project-templates/project-templates.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/project-templates/project-templates.component.spec.ts deleted file mode 100644 index 09d56ebc..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-templates/project-templates.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ProjectTemplatesComponent } from './project-templates.component'; - -describe('ProjectTemplatesComponent', () => { - let component: ProjectTemplatesComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ProjectTemplatesComponent] - }); - fixture = TestBed.createComponent(ProjectTemplatesComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/project-templates/project-templates.component.ts b/worklenz-frontend/src/app/administrator/settings/project-templates/project-templates.component.ts deleted file mode 100644 index ddd43af0..00000000 --- a/worklenz-frontend/src/app/administrator/settings/project-templates/project-templates.component.ts +++ /dev/null @@ -1,63 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit} from '@angular/core'; -import {Router} from "@angular/router"; -import {log_error} from "@shared/utils"; -import {ProjectTemplateApiService} from "@api/project-template-api.service"; -import {ICustomTemplate} from "@interfaces/api-models/project-template"; - -@Component({ - selector: 'worklenz-project-templates', - templateUrl: './project-templates.component.html', - styleUrls: ['./project-templates.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProjectTemplatesComponent implements OnInit{ - loading = false; - - projectTemplates: ICustomTemplate[] = [] - - constructor( - private readonly router: Router, - private readonly cdr: ChangeDetectorRef, - private readonly api: ProjectTemplateApiService - ) { - } - - ngOnInit() { - void this.get(); - } - - async get() { - try { - this.loading = true; - const res = await this.api.getWorklenzCustomTemplates(); - if (res.done) { - this.projectTemplates = res.body; - this.loading = false; - this.cdr.markForCheck(); - } - this.loading = false; - this.cdr.markForCheck(); - } catch (e) { - log_error(e) - this.cdr.markForCheck(); - } - } - - editTemplate(id: string | undefined, name: string | undefined) { - if (!id || !name) return; - this.router.navigate([`/worklenz/settings/project-templates/edit/${id}/${name}`]); - } - - async deleteTemplate(id: string | undefined) { - if (!id) return; - try { - const res = await this.api.delete(id); - if (res.done) { - void this.get(); - } - } catch (e) { - log_error(e); - } - } - -} diff --git a/worklenz-frontend/src/app/administrator/settings/settings-routing.module.ts b/worklenz-frontend/src/app/administrator/settings/settings-routing.module.ts deleted file mode 100644 index ae11656e..00000000 --- a/worklenz-frontend/src/app/administrator/settings/settings-routing.module.ts +++ /dev/null @@ -1,64 +0,0 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {SettingsComponent} from "./settings/settings.component"; -import {ProfileComponent} from "./profile/profile.component"; -import {TeamsComponent} from "./teams/teams.component"; -import {ChangePasswordComponent} from "./change-password/change-password.component"; -import {TeamOwnerOrAdminGuard} from "../../guards/team-owner-or-admin-guard.service"; -import {NonGoogleAccountGuard} from "../../guards/non-google-account.guard"; -import {LanguageAndRegionComponent} from "./language-and-region/language-and-region.component"; -import {LabelsComponent} from "./labels/labels.component"; -import {TaskTemplatesComponent} from "./task-templates/task-templates.component"; -import {TeamMembersComponent} from "./team-members/team-members.component"; -import {ProjectTemplatesComponent} from "./project-templates/project-templates.component"; -import {ProjectTemplateEditViewComponent} from "./project-template-edit-view/project-template-edit-view.component"; - -const routes: Routes = [ - { - path: "", - component: SettingsComponent, - children: [ - {path: "", redirectTo: "profile", pathMatch: "full"}, - {path: "profile", component: ProfileComponent}, - {path: "language-and-region", component: LanguageAndRegionComponent}, - {path: "labels", canActivate: [TeamOwnerOrAdminGuard], component: LabelsComponent}, - { - path: "categories", - canActivate: [TeamOwnerOrAdminGuard], - loadChildren: () => import("./categories/categories.module").then(m => m.CategoriesModule) - }, - { - path: "clients", - canActivate: [TeamOwnerOrAdminGuard], - loadChildren: () => import("./clients/clients.module").then(m => m.ClientsModule) - }, - { - path: "job-titles", - canActivate: [TeamOwnerOrAdminGuard], - loadChildren: () => import("./job-titles/job-titles.module").then(m => m.JobTitlesModule) - }, - { - path: "notifications", - loadChildren: () => import("./notification-settings/notification-settings.module").then(m => m.NotificationSettingsModule) - }, - {path: "teams", component: TeamsComponent}, - {path: "team-members", canActivate: [TeamOwnerOrAdminGuard], component: TeamMembersComponent}, - {path: "password", canActivate: [NonGoogleAccountGuard], component: ChangePasswordComponent}, - {path: "task-templates", canActivate: [TeamOwnerOrAdminGuard], component: TaskTemplatesComponent}, - {path: "project-templates", canActivate: [TeamOwnerOrAdminGuard], component: ProjectTemplatesComponent}, - ] - }, - { - path: "project-templates/edit/:id/:name", - canActivate: [TeamOwnerOrAdminGuard], - component: ProjectTemplateEditViewComponent - }, -] -; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class SettingsRoutingModule { -} diff --git a/worklenz-frontend/src/app/administrator/settings/settings.module.ts b/worklenz-frontend/src/app/administrator/settings/settings.module.ts deleted file mode 100644 index c3b90d99..00000000 --- a/worklenz-frontend/src/app/administrator/settings/settings.module.ts +++ /dev/null @@ -1,182 +0,0 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; - -import {SettingsRoutingModule} from './settings-routing.module'; -import {SettingsComponent} from './settings/settings.component'; -import {NzLayoutModule} from 'ng-zorro-antd/layout'; -import {NzCardModule} from "ng-zorro-antd/card"; -import {NzPageHeaderModule} from 'ng-zorro-antd/page-header'; -import {NzBreadCrumbModule} from 'ng-zorro-antd/breadcrumb'; -import {NzIconModule} from 'ng-zorro-antd/icon'; -import {NzTabsModule} from "ng-zorro-antd/tabs"; -import {ProfileComponent} from './profile/profile.component'; -import {FormsModule, ReactiveFormsModule} from "@angular/forms"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzSpinModule} from "ng-zorro-antd/spin"; -import {TeamsComponent} from './teams/teams.component'; -import {NzTableModule} from "ng-zorro-antd/table"; -import {NzModalModule} from "ng-zorro-antd/modal"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzMenuModule} from "ng-zorro-antd/menu"; -import {ChangePasswordComponent} from './change-password/change-password.component'; -import {NzSpaceModule} from "ng-zorro-antd/space"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzSkeletonModule} from "ng-zorro-antd/skeleton"; -import {LanguageAndRegionComponent} from './language-and-region/language-and-region.component'; -import {NzSelectModule} from "ng-zorro-antd/select"; -import {NzUploadModule} from "ng-zorro-antd/upload"; -import {FromNowPipe} from "@pipes/from-now.pipe"; -import {ToggleMenuButtonComponent} from "../components/toggle-menu-button/toggle-menu-button.component"; -import {LabelsComponent} from './labels/labels.component'; -import {NzBadgeModule} from "ng-zorro-antd/badge"; -import {NzDropDownModule} from "ng-zorro-antd/dropdown"; -import {NzTagModule} from "ng-zorro-antd/tag"; -import {TaskTemplatesComponent} from './task-templates/task-templates.component'; -import {TaskTemplateDrawerComponent} from "../components/task-template-drawer/task-template-drawer.component"; -import {NzPopconfirmModule} from "ng-zorro-antd/popconfirm"; -import {TeamMembersComponent} from './team-members/team-members.component'; -import {TeamMembersFormComponent} from "../components/team-members-form/team-members-form.component"; -import {NzAvatarModule} from "ng-zorro-antd/avatar"; -import {SearchByNamePipe} from "@pipes/search-by-name.pipe"; -import { ProjectTemplatesComponent } from './project-templates/project-templates.component'; -import { ProjectTemplateEditViewComponent } from './project-template-edit-view/project-template-edit-view.component'; -import {SafeStringPipe} from "@pipes/safe-string.pipe"; -import {TasksProgressBarComponent} from "@admin/components/tasks-progress-bar/tasks-progress-bar.component"; -import {TaskListV2Module} from "../modules/task-list-v2/task-list-v2.module"; -import {CdkDrag, CdkDragHandle, CdkDropList} from "@angular/cdk/drag-drop"; -import { TaskListGroupSettingsComponent } from './project-template-edit-view/components/task-list-group-settings/task-list-group-settings.component'; -import { TaskListHeaderComponent } from './project-template-edit-view/components/task-list-header/task-list-header.component'; -import {EllipsisPipe} from "@pipes/ellipsis.pipe"; -import {NzCheckboxModule} from "ng-zorro-antd/checkbox"; -import {AddTaskInputComponent} from "./project-template-edit-view/components/add-task-input/add-task-input.component"; -import {TaskListRowComponent} from "./project-template-edit-view/task-list-row/task-list-row.component"; -import{SubTasksArrowColorPipe2} from "./project-template-edit-view/pipes/sub-tasks-arrow-color.pipe"; -import {SubTasksArrowIconPipe2} from "./project-template-edit-view/pipes/sub-tasks-arrow-icon.pipe"; -import { TaskDescriptionComponent } from './project-template-edit-view/components/row/task-description/task-description.component'; -import { TaskLabelsComponent } from './project-template-edit-view/components/row/task-labels/task-labels.component'; -import {WithAlphaPipe} from "@pipes/with-alpha.pipe"; -import {EllipsisTooltipTitlePipe2} from "./project-template-edit-view/pipes/ellipsis-tooltip-title.pipe"; -import {EndNameCheckPipe2} from "./project-template-edit-view/pipes/end-name-check.pipe"; -import { TaskPhaseComponent } from './project-template-edit-view/components/row/task-phase/task-phase.component'; -import { TaskStatusComponent } from './project-template-edit-view/components/row/task-status/task-status.component'; -import { TaskPriorityComponent } from './project-template-edit-view/components/row/task-priority/task-priority.component'; -import { TaskEstimationComponent } from './project-template-edit-view/components/row/task-estimation/task-estimation.component'; -import { TaskStartDateComponent } from './project-template-edit-view/components/row/task-start-date/task-start-date.component'; -import { TaskEndDateComponent } from './project-template-edit-view/components/row/task-end-date/task-end-date.component'; -import {TruncateIfLongPipe2} from "./project-template-edit-view/pipes/truncate-if-long.pipe"; -import {NzDatePickerModule} from "ng-zorro-antd/date-picker"; -import {DateFormatterPipe} from "@pipes/date-formatter.pipe"; -import {RxFor} from "@rx-angular/template/for"; -import {NzPipesModule} from "ng-zorro-antd/pipes"; -import {TaskPriorityLabelComponent} from "@admin/components/task-priority-label/task-priority-label.component"; -import {EditorComponent} from "@tinymce/tinymce-angular"; -import {NzInputNumberModule} from "ng-zorro-antd/input-number"; -import { ContextMenuComponent } from './project-template-edit-view/components/context-menu/context-menu.component'; -import { GroupFilterComponent } from './project-template-edit-view/components/group-filter/group-filter.component'; -import { PhaseSettingsDrawerComponent } from './project-template-edit-view/components/phase-settings-drawer/phase-settings-drawer.component'; -import { StatusSettingsDrawerComponent } from './project-template-edit-view/components/status-settings-drawer/status-settings-drawer.component'; -import {NzDrawerModule} from "ng-zorro-antd/drawer"; -import {NzDividerModule} from "ng-zorro-antd/divider"; -import { TemplateNameComponent } from './project-template-edit-view/components/template-name/template-name.component'; - -@NgModule({ - declarations: [ - SettingsComponent, - ProfileComponent, - TeamsComponent, - ChangePasswordComponent, - LanguageAndRegionComponent, - LabelsComponent, - TaskTemplatesComponent, - TeamMembersComponent, - ProjectTemplatesComponent, - ProjectTemplateEditViewComponent, - TaskListGroupSettingsComponent, - TaskListHeaderComponent, - SubTasksArrowColorPipe2, - SubTasksArrowIconPipe2, - TaskListRowComponent, - TaskDescriptionComponent, - TaskLabelsComponent, - EllipsisTooltipTitlePipe2, - EndNameCheckPipe2, - TaskPhaseComponent, - TaskStatusComponent, - TaskPriorityComponent, - TaskEstimationComponent, - TaskStartDateComponent, - TaskEndDateComponent, - TruncateIfLongPipe2, - ContextMenuComponent, - GroupFilterComponent, - PhaseSettingsDrawerComponent, - StatusSettingsDrawerComponent, - TemplateNameComponent - ], - imports: [ - CommonModule, - SettingsRoutingModule, - NzLayoutModule, - NzCardModule, - NzPageHeaderModule, - NzBreadCrumbModule, - NzIconModule, - NzTabsModule, - ReactiveFormsModule, - NzFormModule, - NzInputModule, - NzButtonModule, - NzSpinModule, - NzTableModule, - NzModalModule, - NzTypographyModule, - NzMenuModule, - NzSpaceModule, - NzToolTipModule, - NzSkeletonModule, - NzSelectModule, - FormsModule, - NzUploadModule, - FromNowPipe, - ToggleMenuButtonComponent, - NzBadgeModule, - NzDropDownModule, - NzTagModule, - TaskTemplateDrawerComponent, - NzPopconfirmModule, - TeamMembersFormComponent, - NzAvatarModule, - SearchByNamePipe, - SafeStringPipe, - TasksProgressBarComponent, - TaskListV2Module, - CdkDropList, - EllipsisPipe, - NzCheckboxModule, - AddTaskInputComponent, - CdkDrag, - CdkDragHandle, - WithAlphaPipe, - NzDatePickerModule, - DateFormatterPipe, - RxFor, - NzPipesModule, - TaskPriorityLabelComponent, - EditorComponent, - NzInputNumberModule, - NzDrawerModule, - NzDividerModule, - ], - providers: [SearchByNamePipe], - exports : [ - SubTasksArrowColorPipe2, - SubTasksArrowIconPipe2, - EllipsisTooltipTitlePipe2, - EndNameCheckPipe2, - TruncateIfLongPipe2 - ] -}) -export class SettingsModule { -} diff --git a/worklenz-frontend/src/app/administrator/settings/settings.service.spec.ts b/worklenz-frontend/src/app/administrator/settings/settings.service.spec.ts deleted file mode 100644 index 359cb6b7..00000000 --- a/worklenz-frontend/src/app/administrator/settings/settings.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { SettingsService } from './settings.service'; - -describe('SettingsService', () => { - let service: SettingsService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(SettingsService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/settings.service.ts b/worklenz-frontend/src/app/administrator/settings/settings.service.ts deleted file mode 100644 index 07f15a66..00000000 --- a/worklenz-frontend/src/app/administrator/settings/settings.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Injectable } from '@angular/core'; -import {Subject} from "rxjs"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; - -@Injectable({ - providedIn: 'root' -}) -export class SettingsService { - - private readonly newMemberCreatedSbj$ = new Subject(); - - get onNewMemberCreated() { - return this.newMemberCreatedSbj$.asObservable(); - } - - public emitNewMemberCreated() { - this.newMemberCreatedSbj$.next(); - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/settings/settings.component.html b/worklenz-frontend/src/app/administrator/settings/settings/settings.component.html deleted file mode 100644 index 3c179a64..00000000 --- a/worklenz-frontend/src/app/administrator/settings/settings/settings.component.html +++ /dev/null @@ -1,23 +0,0 @@ -
- - Settings - - - -
    -
  • -
-
- - - -
-
diff --git a/worklenz-frontend/src/app/administrator/settings/settings/settings.component.scss b/worklenz-frontend/src/app/administrator/settings/settings/settings.component.scss deleted file mode 100644 index bab62ed1..00000000 --- a/worklenz-frontend/src/app/administrator/settings/settings/settings.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -[nz-submenu] { - transition: none !important; -} diff --git a/worklenz-frontend/src/app/administrator/settings/settings/settings.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/settings/settings.component.spec.ts deleted file mode 100644 index 0ac2bed5..00000000 --- a/worklenz-frontend/src/app/administrator/settings/settings/settings.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {SettingsComponent} from './settings.component'; - -describe('SettingsComponent', () => { - let component: SettingsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [SettingsComponent] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(SettingsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/settings/settings.component.ts b/worklenz-frontend/src/app/administrator/settings/settings/settings.component.ts deleted file mode 100644 index 37fbe6c8..00000000 --- a/worklenz-frontend/src/app/administrator/settings/settings/settings.component.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { AppService } from '@services/app.service'; -import { AuthService } from "@services/auth.service"; -import { ISettingsNavigationItem } from "@interfaces/settings-navigation-item"; - -@Component({ - selector: 'worklenz-settings', - templateUrl: './settings.component.html', - styleUrls: ['./settings.component.scss'] -}) -export class SettingsComponent implements OnInit { - navigation: ISettingsNavigationItem[] = []; - - constructor( - private auth: AuthService, - private app: AppService - ) { - this.app.setTitle('Settings'); - } - - get profile() { - return this.auth.getCurrentSession(); - } - - ngOnInit() { - this.buildNavigation(); - } - - isOwnerOrAdmin() { - return this.profile?.owner || this.profile?.is_admin; - } - - private buildNavigation() { - this.navigation = []; - - this.navigation.push({ label: 'Profile', icon: 'user', url: 'profile' }); - this.navigation.push({ label: 'Notifications', icon: 'notification', url: 'notifications' }); - - if (this.isOwnerOrAdmin()) { - this.navigation.push({ label: 'Clients', icon: 'user-switch', url: 'clients' }); - this.navigation.push({ label: 'Job Titles', icon: 'idcard', url: 'job-titles' }); - this.navigation.push({ label: 'Labels', icon: 'tags', url: 'labels' }); - this.navigation.push({ label: 'Categories', icon: 'group', url: 'categories' }); - this.navigation.push({ label: 'Project Templates', icon: 'file-zip', url: 'project-templates' }); - this.navigation.push({ label: 'Task Templates', icon: 'profile', url: 'task-templates' }); - this.navigation.push({ label: 'Team Members', icon: 'team', url: 'team-members' }); - - - this.navigation.push({ label: 'Teams', icon: 'bank', url: 'teams' }); - } - - if (!this.profile?.is_google) - this.navigation.push({ label: 'Change Password', icon: 'lock', url: 'password' }); - - this.navigation.push({ label: 'Language & Region', icon: 'global', url: 'language-and-region' }); - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/task-templates/task-templates.component.html b/worklenz-frontend/src/app/administrator/settings/task-templates/task-templates.component.html deleted file mode 100644 index 22833a47..00000000 --- a/worklenz-frontend/src/app/administrator/settings/task-templates/task-templates.component.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - Name - Created - - - - - - {{data.name}} - {{data.created_at | fromNow}} - -
- - - - -
- - - -
-
-
- - diff --git a/worklenz-frontend/src/app/administrator/settings/task-templates/task-templates.component.scss b/worklenz-frontend/src/app/administrator/settings/task-templates/task-templates.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/settings/task-templates/task-templates.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/task-templates/task-templates.component.spec.ts deleted file mode 100644 index 8ed9b025..00000000 --- a/worklenz-frontend/src/app/administrator/settings/task-templates/task-templates.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TaskTemplatesComponent} from './task-templates.component'; - -describe('TaskTemplatesComponent', () => { - let component: TaskTemplatesComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TaskTemplatesComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TaskTemplatesComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/task-templates/task-templates.component.ts b/worklenz-frontend/src/app/administrator/settings/task-templates/task-templates.component.ts deleted file mode 100644 index 5f9cf781..00000000 --- a/worklenz-frontend/src/app/administrator/settings/task-templates/task-templates.component.ts +++ /dev/null @@ -1,86 +0,0 @@ -import {Component} from '@angular/core'; -import {FormBuilder, FormGroup, Validators} from "@angular/forms"; -import {ProfileSettingsApiService} from "@api/profile-settings-api.service"; -import {AppService} from "@services/app.service"; -import {MenuService} from "@services/menu.service"; -import {log_error} from "@shared/utils"; -import {TaskTemplatesService} from "@api/task-templates.service"; -import {ITaskTemplatesGetResponse} from "@interfaces/api-models/task-templates-get-response"; - -@Component({ - selector: 'worklenz-task-templates', - templateUrl: './task-templates.component.html', - styleUrls: ['./task-templates.component.scss'] -}) -export class TaskTemplatesComponent { - form!: FormGroup; - taskTemplates: ITaskTemplatesGetResponse[] = []; - - loading = false; - updating = false; - drawerVisible = false; - - private editingTeamId: string | null = null; - selectedTemplateId: string = ''; - - constructor( - private api: TaskTemplatesService, - private settingsApi: ProfileSettingsApiService, - private fb: FormBuilder, - private app: AppService, - public menu: MenuService - ) { - this.app.setTitle("Task Templates"); - this.form = this.fb.group({ - name: [null, Validators.required] - }); - } - - ngOnInit(): void { - this.getTaskTemplates().then(r => r); - } - - async getTaskTemplates() { - try { - this.loading = true; - const res = await this.api.get(); - if (res.done) { - this.taskTemplates = res.body; - } - this.loading = false; - } catch (e) { - this.loading = false; - log_error(e); - } - } - - closeModal() { - this.drawerVisible = false; - } - - editTemplate(id: string | undefined) { - if (!id) return; - this.selectedTemplateId = id; - this.drawerVisible = true; - } - - taskTemplateCancel(visible: boolean) { - this.drawerVisible = visible; - this.selectedTemplateId = ''; - } - - onCreateOrUpdate() { - this.getTaskTemplates().then(r => r); - } - - async deleteTemplate(id: string = '') { - try { - const res = await this.api.delete(id); - if (res.done) { - void this.getTaskTemplates(); - } - } catch (e) { - - } - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/team-members/team-members.component.html b/worklenz-frontend/src/app/administrator/settings/team-members/team-members.component.html deleted file mode 100644 index a7c479ae..00000000 --- a/worklenz-frontend/src/app/administrator/settings/team-members/team-members.component.html +++ /dev/null @@ -1,123 +0,0 @@ -
- - {{ total || 0 }} Member{{total > 1 ? 's' : ''}} - - - -
- - - - - - -
- - - -
-
-
- - - - - - - Name - - Projects - Email - Team Access - - - - - - - - - {{ data.name }} - - (Deactivated) - - - {{ data.projects_count || 0 }} - - - {{data.email}} - - - - {{data.email}} (Pending Invitation) - - - - {{data.role_name}} - {{data.role_name}} - {{data.role_name}} - - -
- - - -
- -
- - -
-
- - - -
-
-
-
- - diff --git a/worklenz-frontend/src/app/administrator/settings/team-members/team-members.component.scss b/worklenz-frontend/src/app/administrator/settings/team-members/team-members.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/administrator/settings/team-members/team-members.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/team-members/team-members.component.spec.ts deleted file mode 100644 index 80667169..00000000 --- a/worklenz-frontend/src/app/administrator/settings/team-members/team-members.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TeamMembersComponent} from './team-members.component'; - -describe('TeamMembersComponent', () => { - let component: TeamMembersComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TeamMembersComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TeamMembersComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/team-members/team-members.component.ts b/worklenz-frontend/src/app/administrator/settings/team-members/team-members.component.ts deleted file mode 100644 index 8db5a720..00000000 --- a/worklenz-frontend/src/app/administrator/settings/team-members/team-members.component.ts +++ /dev/null @@ -1,168 +0,0 @@ -import {Component} from '@angular/core'; -import {FormBuilder, FormGroup} from '@angular/forms'; -import {NzTableQueryParams} from "ng-zorro-antd/table"; - -import {TeamMembersApiService} from '@api/team-members-api.service'; -import {JobTitlesApiService} from '@api/job-titles-api.service'; -import {AppService} from '@services/app.service'; -import {AuthService} from '@services/auth.service'; -import {IPaginationComponent} from "@interfaces/pagination-component"; -import {ITeamMembersViewModel} from "@interfaces/api-models/team-members-view-model"; -import {Router} from "@angular/router"; -import {AvatarNamesMap, DEFAULT_PAGE_SIZE} from "@shared/constants"; -import {UtilsService} from "@services/utils.service"; -import {log_error} from "@shared/utils"; -import {ITeamMemberViewModel} from "@interfaces/api-models/team-members-get-response"; -import {SettingsService} from "../settings.service"; -import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; - -@Component({ - selector: 'worklenz-team-members', - templateUrl: './team-members.component.html', - styleUrls: ['./team-members.component.scss'] -}) -export class TeamMembersComponent implements IPaginationComponent { - searchForm!: FormGroup; - - model: ITeamMembersViewModel = {}; - - showTeamMemberModal = false; - loading = false; - - selectedMemberId: string | null = null; - - // Table sorting & pagination - total = 0; - pageSize = DEFAULT_PAGE_SIZE; - pageIndex = 1; - paginationSizes = [5, 10, 15, 20, 50, 100]; - sortField: string | null = null; - sortOrder: string | null = null; - - constructor( - private auth: AuthService, - private api: TeamMembersApiService, - private jobTitlesApi: JobTitlesApiService, - private fb: FormBuilder, - private app: AppService, - private router: Router, - private utilsService: UtilsService, - private settingsService: SettingsService - ) { - this.app.setTitle('Team Members'); - - this.searchForm = this.fb.group({ - search: [], - }); - this.searchForm.valueChanges.subscribe(() => { - this.search(); - }); - this.settingsService.onNewMemberCreated.pipe(takeUntilDestroyed()).subscribe(() => { - this.getTeamMembers(); - }) - } - - get profile() { - return this.auth.getCurrentSession(); - } - - async getTeamMembers() { - try { - this.loading = true; - const res = await this.api.get(this.pageIndex, this.pageSize, this.sortField, this.sortOrder, this.searchForm.value.search); - if (res.done) { - this.model = res.body; - this.total = this.model.total || 0; - - this.utilsService.handleLastIndex(this.total, this.model.data?.length || 0, this.pageIndex, - index => { - this.pageIndex = index; - this.getTeamMembers(); - }); - } - this.loading = false; - } catch (e) { - log_error(e); - this.loading = false; - } - } - - editMember(id?: string) { - this.selectedMemberId = id || null; - this.showTeamMemberModal = true; - } - - reset() { - this.selectedMemberId = null; - } - - async deleteTeamMember(id: string | undefined, email: string | undefined) { - if (!id || !email) return; - try { - const res = await this.api.delete(id, email); - if (res.done) { - await this.getTeamMembers(); - if (this.auth.getCurrentSession()?.team_member_id === id) { - window.location.reload(); - } - } - } catch (e) { - log_error(e); - } - } - - selectValue(target: HTMLInputElement) { - if (target) target.select(); - } - - async search() { - void this.getTeamMembers(); - } - - selectMember(id?: string) { - if (!id) return; - // this.router.navigate([`/worklenz/team/member/${id}`]); - this.editMember(id); - } - - getColor(name?: string) { - return AvatarNamesMap[name?.charAt(0).toUpperCase() || 'A']; - } - - async onQueryParamsChange(params: NzTableQueryParams) { - const {pageSize, pageIndex, sort} = params; - this.pageIndex = pageIndex; - this.pageSize = pageSize; - - const currentSort = sort.find(item => item.value !== null); - - this.sortField = (currentSort && currentSort.key) || null; - this.sortOrder = (currentSort && currentSort.value) || "descend"; - - await this.getTeamMembers(); - } - - openAddMemberForm() { - this.showTeamMemberModal = true; - } - - refresh() { - void this.getTeamMembers(); - } - - handleOnCreateOrUpdate(event: number) { - this.getTeamMembers(); - } - - async toggleMemberActiveStatus(member: ITeamMemberViewModel) { - if (!member.id) return; - try { - const res = await this.api.toggleMemberActiveStatus(member.id, member.active as boolean, member.email as string); - if (res.done) { - await this.getTeamMembers(); - } - } catch (e) { - log_error(e); - } - } -} diff --git a/worklenz-frontend/src/app/administrator/settings/teams/teams.component.html b/worklenz-frontend/src/app/administrator/settings/teams/teams.component.html deleted file mode 100644 index f1977d17..00000000 --- a/worklenz-frontend/src/app/administrator/settings/teams/teams.component.html +++ /dev/null @@ -1,90 +0,0 @@ - - {{teams.length ? teams.length + 1 : 1}} Teams - - - - - - - - - - - Name - Created - Owns By - - - - - - {{currentTeam.created_at | fromNow}} - {{currentTeam.owns_by}} - -
- - - -
- - - - {{data.name}} - {{data.created_at | fromNow}} - {{data.owns_by}} - -
- - - -
- - - -
-
-
- - - - - - -
-
- -
- No teams found. -
-
- - - - - - - - -
- - Name - - - - -
- -
-
diff --git a/worklenz-frontend/src/app/administrator/settings/teams/teams.component.scss b/worklenz-frontend/src/app/administrator/settings/teams/teams.component.scss deleted file mode 100644 index e602fd99..00000000 --- a/worklenz-frontend/src/app/administrator/settings/teams/teams.component.scss +++ /dev/null @@ -1,4 +0,0 @@ -.no-data-img-holder { - width: 100px; - margin-top: 42px; -} diff --git a/worklenz-frontend/src/app/administrator/settings/teams/teams.component.spec.ts b/worklenz-frontend/src/app/administrator/settings/teams/teams.component.spec.ts deleted file mode 100644 index f5dbcb93..00000000 --- a/worklenz-frontend/src/app/administrator/settings/teams/teams.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TeamsComponent} from './teams.component'; - -describe('TeamsComponent', () => { - let component: TeamsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TeamsComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(TeamsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/administrator/settings/teams/teams.component.ts b/worklenz-frontend/src/app/administrator/settings/teams/teams.component.ts deleted file mode 100644 index 3b3f88cb..00000000 --- a/worklenz-frontend/src/app/administrator/settings/teams/teams.component.ts +++ /dev/null @@ -1,94 +0,0 @@ -import {Component, OnInit} from '@angular/core'; -import {TeamsApiService} from "@api/teams-api.service"; -import {FormBuilder, FormGroup, Validators} from "@angular/forms"; -import {ProfileSettingsApiService} from "@api/profile-settings-api.service"; -import {ITeamGetResponse} from "@interfaces/api-models/team-get-response"; -import {AppService} from "@services/app.service"; -import {MenuService} from "@services/menu.service"; -import {log_error} from "@shared/utils"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: 'worklenz-teams', - templateUrl: './teams.component.html', - styleUrls: ['./teams.component.scss'] -}) -export class TeamsComponent implements OnInit { - form!: FormGroup; - teams: ITeamGetResponse[] = []; - currentTeam: ITeamGetResponse | null = null; - - loading = false; - updating = false; - showTeamEditModal = false; - - private editingTeamId: string | null = null; - - constructor( - private teamsApi: TeamsApiService, - private settingsApi: ProfileSettingsApiService, - private fb: FormBuilder, - private app: AppService, - public menu: MenuService, - private readonly auth: AuthService - ) { - this.app.setTitle("Teams"); - this.form = this.fb.group({ - name: [null, Validators.required] - }); - } - - ngOnInit(): void { - this.getTeams(); - } - - async getTeams() { - try { - this.loading = true; - const res = await this.teamsApi.get(); - if (res.done) { - this.teams = res.body.filter(t => t.id !== this.auth.getCurrentSession()?.team_id); - this.currentTeam = res.body.filter(t => t.id === this.auth.getCurrentSession()?.team_id)[0]; - } - this.loading = false; - } catch (e) { - this.loading = false; - log_error(e); - } - } - - async updateTeamName(id: string, name: string) { - try { - if (!id || !name) return; - this.updating = true; - const res = await this.settingsApi.updateTeamName(id, {name}); - if (res.done) { - window.location.reload(); - } else { - this.updating = false; - } - } catch (e) { - this.updating = false; - log_error(e); - } - } - - closeModal() { - this.showTeamEditModal = false; - this.editingTeamId = null; - this.form.reset(); - } - - async handleOk() { - if (this.form.valid && this.editingTeamId) { - await this.updateTeamName(this.editingTeamId, this.form.controls["name"].value); - } - } - - editTeam(team: ITeamGetResponse | undefined) { - if (!team || !team.id) return; - this.showTeamEditModal = true; - this.editingTeamId = team.id; - this.form.controls["name"].setValue(team.name); - } -} diff --git a/worklenz-frontend/src/app/app-routing.module.ts b/worklenz-frontend/src/app/app-routing.module.ts deleted file mode 100644 index 977f78b1..00000000 --- a/worklenz-frontend/src/app/app-routing.module.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {NgModule} from "@angular/core"; -import {RouterModule, Routes} from "@angular/router"; -import {AuthGuard} from "./guards/auth.guard"; -import {AuthenticateComponent} from "./authenticate/authenticate.component"; -import {SessionExpiredComponent} from "./session-expired/session-expired.component"; -import {NotFoundComponent} from "./errors/not-found/not-found.component"; -import {NotAuthorizedComponent} from "./errors/not-authorized/not-authorized.component"; -import {TeamMemberGuard} from "./guards/team-member.guard"; -import {LoginCheckGuard} from "./guards/login-check.guard"; - -const routes: Routes = [ - {path: "", redirectTo: "auth", pathMatch: "full"}, - { - path: "auth", - canActivate: [LoginCheckGuard], - loadChildren: () => import("./auth/auth.module").then(m => m.AuthModule) - }, - {path: "authenticate", component: AuthenticateComponent}, - {path: "session-expired", component: SessionExpiredComponent}, - { - path: "worklenz", - canActivate: [AuthGuard, TeamMemberGuard], - loadChildren: () => import("./administrator/administrator.module").then(m => m.AdministratorModule) - }, - {path: 'unauthorized', component: NotAuthorizedComponent}, - {path: "**", component: NotFoundComponent} -]; - -@NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule] -}) -export class AppRoutingModule { -} diff --git a/worklenz-frontend/src/app/app.component.html b/worklenz-frontend/src/app/app.component.html deleted file mode 100644 index 0680b43f..00000000 --- a/worklenz-frontend/src/app/app.component.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/worklenz-frontend/src/app/app.component.scss b/worklenz-frontend/src/app/app.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/app.component.spec.ts b/worklenz-frontend/src/app/app.component.spec.ts deleted file mode 100644 index 36196238..00000000 --- a/worklenz-frontend/src/app/app.component.spec.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {TestBed} from "@angular/core/testing"; -import {RouterTestingModule} from "@angular/router/testing"; -import {AppComponent} from "./app.component"; - -describe("AppComponent", () => { - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ - RouterTestingModule - ], - declarations: [ - AppComponent - ], - }).compileComponents(); - }); - - it("should create the app", () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app).toBeTruthy(); - }); - - it(`should have as title 'worklenz'`, () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app.title).toEqual("worklenz"); - }); - - it("should render title", () => { - const fixture = TestBed.createComponent(AppComponent); - fixture.detectChanges(); - const compiled = fixture.nativeElement as HTMLElement; - expect(compiled.querySelector(".content span")?.textContent).toContain("worklenz app is running!"); - }); -}); diff --git a/worklenz-frontend/src/app/app.component.ts b/worklenz-frontend/src/app/app.component.ts deleted file mode 100644 index c8d9617e..00000000 --- a/worklenz-frontend/src/app/app.component.ts +++ /dev/null @@ -1,44 +0,0 @@ -import {Component} from "@angular/core"; -import {ActivatedRoute, NavigationEnd, RouteConfigLoadEnd, RouteConfigLoadStart, Router} from "@angular/router"; - -import {AppService} from "@services/app.service"; -import {AuthService} from "@services/auth.service"; - -@Component({ - selector: "worklenz-root", - templateUrl: "./app.component.html", - styleUrls: ["./app.component.scss"] -}) -export class AppComponent { - title = "worklenz"; - - constructor( - private route: ActivatedRoute, - private router: Router, - private app: AppService, - private auth: AuthService - ) { - this.router.events.subscribe(async event => { - if (event instanceof NavigationEnd) { - const authorized = await this.auth.authorize(); - if (!authorized) - await this.handleSessionExpiry(event); - } - - // Listening for lazy loading modules load end - if (event instanceof RouteConfigLoadStart) { - if (event.route.path) - this.app.setLoadingPath(event.route.path); - } else if (event instanceof RouteConfigLoadEnd) { - this.app.setLoadingPath(null); - } - }); - } - - private async handleSessionExpiry(event: NavigationEnd) { - const isAuthRoute = event.url.includes('/auth'); - const isSessionExpired = event.url.includes('/session-expired'); - if (!isAuthRoute && !isSessionExpired) - await this.router.navigate(['/session-expired']) - } -} diff --git a/worklenz-frontend/src/app/app.module.ts b/worklenz-frontend/src/app/app.module.ts deleted file mode 100644 index 0c28efee..00000000 --- a/worklenz-frontend/src/app/app.module.ts +++ /dev/null @@ -1,68 +0,0 @@ -import {NgModule} from '@angular/core'; -import {BrowserModule} from '@angular/platform-browser'; -import {FormsModule} from '@angular/forms'; -import {ServiceWorkerModule} from '@angular/service-worker'; -import {registerLocaleData} from '@angular/common'; -import en from '@angular/common/locales/en'; -import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http'; -import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; -import {SocketIoConfig, SocketIoModule} from 'ngx-socket-io'; - -import {environment} from '../environments/environment'; - -import {AppRoutingModule} from './app-routing.module'; -import {AppComponent} from './app.component'; -import {HTTPInterceptor} from './interceptors/http.interceptor'; -import {NzNotificationModule} from 'ng-zorro-antd/notification'; -import {NzModalModule} from "ng-zorro-antd/modal"; -import {en_US, NZ_I18N} from "ng-zorro-antd/i18n"; -import {NzAlertModule} from "ng-zorro-antd/alert"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; - -registerLocaleData(en); - -const config: SocketIoConfig = { - url: environment.production ? '' : 'ws://localhost:3000', - options: { - transports: ['websocket'], - path: "/socket", - upgrade: true - } -}; - -@NgModule({ - declarations: [ - AppComponent - ], - imports: [ - BrowserModule, - BrowserAnimationsModule, - FormsModule, - SocketIoModule.forRoot(config), - ServiceWorkerModule.register('ngsw-worker.js', { - enabled: environment.production, - // Register the ServiceWorker as soon as the app is stable - // or after 30 seconds (whichever comes first). - registrationStrategy: 'registerWhenStable:30000' - }), - HttpClientModule, - NzModalModule, - NzNotificationModule, - AppRoutingModule, - NzAlertModule, - NzTypographyModule - ], - providers: [ - {provide: NZ_I18N, useValue: en_US}, - { - provide: HTTP_INTERCEPTORS, - useClass: HTTPInterceptor, - multi: true - } - ], - exports: [ - ], - bootstrap: [AppComponent] -}) -export class AppModule { -} diff --git a/worklenz-frontend/src/app/auth/auth-routing.module.ts b/worklenz-frontend/src/app/auth/auth-routing.module.ts deleted file mode 100644 index e1918de2..00000000 --- a/worklenz-frontend/src/app/auth/auth-routing.module.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {NgModule} from "@angular/core"; -import {RouterModule, Routes} from "@angular/router"; -import {LoginComponent} from "./login/login.component"; -import {SignupComponent} from "./signup/signup.component"; -import {LayoutComponent} from "./layout/layout.component"; -import {ResetPasswordComponent} from "./reset-password/reset-password.component"; -import {VerifyResetEmailComponent} from './verify-reset-email/verify-reset-email.component'; - -const routes: Routes = [ - { - path: "", - component: LayoutComponent, - children: [ - {path: "", redirectTo: "login", pathMatch: "full"}, - {path: "login", component: LoginComponent}, - {path: "signup", component: SignupComponent}, - ] - }, - { - path: "", - children: [ - {path: "reset-password", component: ResetPasswordComponent}, - {path: "verify-reset-email/:user/:hash", component: VerifyResetEmailComponent}, - {path: "**", redirectTo: "login", pathMatch: "full"} - ] - } -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class AuthRoutingModule { -} diff --git a/worklenz-frontend/src/app/auth/auth.module.ts b/worklenz-frontend/src/app/auth/auth.module.ts deleted file mode 100644 index 444712af..00000000 --- a/worklenz-frontend/src/app/auth/auth.module.ts +++ /dev/null @@ -1,56 +0,0 @@ -import {NgModule} from "@angular/core"; -import {CommonModule, NgOptimizedImage} from "@angular/common"; - -import {AuthRoutingModule} from "./auth-routing.module"; -import {LoginComponent} from "./login/login.component"; -import {SignupComponent} from "./signup/signup.component"; -import {ResetPasswordComponent} from "./reset-password/reset-password.component"; -import {LayoutComponent} from "./layout/layout.component"; -import {FormsModule, ReactiveFormsModule} from "@angular/forms"; -import {TeamNameComponent} from './team-name/team-name.component'; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {NzIconModule} from "ng-zorro-antd/icon"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzCheckboxModule} from "ng-zorro-antd/checkbox"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; -import {NzTabsModule} from "ng-zorro-antd/tabs"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzSpinModule} from "ng-zorro-antd/spin"; -import {VerifyResetEmailComponent} from './verify-reset-email/verify-reset-email.component'; -import {NzResultModule} from "ng-zorro-antd/result"; -import {NzPopoverModule} from "ng-zorro-antd/popover"; -import {NzProgressModule} from "ng-zorro-antd/progress"; - - -@NgModule({ - declarations: [ - LoginComponent, - SignupComponent, - ResetPasswordComponent, - LayoutComponent, - TeamNameComponent, - VerifyResetEmailComponent - ], - imports: [ - CommonModule, - AuthRoutingModule, - FormsModule, - ReactiveFormsModule, - NzFormModule, - NzInputModule, - NzIconModule, - NzButtonModule, - NzCheckboxModule, - NzTypographyModule, - NzTabsModule, - NzToolTipModule, - NzSpinModule, - NzResultModule, - NgOptimizedImage, - NzPopoverModule, - NzProgressModule - ] -}) -export class AuthModule { -} diff --git a/worklenz-frontend/src/app/auth/layout/layout.component.html b/worklenz-frontend/src/app/auth/layout/layout.component.html deleted file mode 100644 index e409997f..00000000 --- a/worklenz-frontend/src/app/auth/layout/layout.component.html +++ /dev/null @@ -1,19 +0,0 @@ -
-
-
- -
-
-
-
- -
-
-
-
- -
-
-
-
-
diff --git a/worklenz-frontend/src/app/auth/layout/layout.component.scss b/worklenz-frontend/src/app/auth/layout/layout.component.scss deleted file mode 100644 index b5f2e6b7..00000000 --- a/worklenz-frontend/src/app/auth/layout/layout.component.scss +++ /dev/null @@ -1,25 +0,0 @@ -.bg-ant-grey { - background: #FAFAFA; - position: absolute; - bottom: 0; - top: 0; - left: 0; - right: 0; - z-index: 0; -} - -.w-360 { - width: 450px; - z-index: 1; -} - -.border-radius-4 { - border: 1px solid #FAFAFA; - border-radius: 4px; -} - -@media(max-width: 660px) { - .w-360 { - width: 90%; - } -} diff --git a/worklenz-frontend/src/app/auth/layout/layout.component.spec.ts b/worklenz-frontend/src/app/auth/layout/layout.component.spec.ts deleted file mode 100644 index 6ae7eaef..00000000 --- a/worklenz-frontend/src/app/auth/layout/layout.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {ComponentFixture, TestBed} from "@angular/core/testing"; - -import {LayoutComponent} from "./layout.component"; - -describe("LayoutComponent", () => { - let component: LayoutComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [LayoutComponent] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(LayoutComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it("should create", () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/auth/layout/layout.component.ts b/worklenz-frontend/src/app/auth/layout/layout.component.ts deleted file mode 100644 index fb510fca..00000000 --- a/worklenz-frontend/src/app/auth/layout/layout.component.ts +++ /dev/null @@ -1,42 +0,0 @@ -import {Component, OnInit} from "@angular/core"; -import {ActivatedRoute, Router} from "@angular/router"; - -import {AuthService} from "@services/auth.service"; -import {AppService} from "@services/app.service"; - -@Component({ - selector: "worklenz-layout", - templateUrl: "./layout.component.html", - styleUrls: ["./layout.component.scss"] -}) -export class LayoutComponent implements OnInit { - - constructor( - private readonly auth: AuthService, - private readonly router: Router, - private readonly route: ActivatedRoute, - private readonly app: AppService - ) { - const error = this.route.snapshot.queryParamMap.get("error"); - if (error) { - this.displayError(error); - this.removeErrorQueryParam(); - } - } - - async ngOnInit() { - if (this.auth.isAuthenticated()) - await this.router.navigate(["/worklenz"]); - } - - private displayError(error: string) { - this.app.notify('Authentication failed!', error, false); - } - - private removeErrorQueryParam() { - void this.router.navigate([], { - relativeTo: this.route, - queryParams: {} - }); - } -} diff --git a/worklenz-frontend/src/app/auth/login/login.component.html b/worklenz-frontend/src/app/auth/login/login.component.html deleted file mode 100644 index 98f6a94a..00000000 --- a/worklenz-frontend/src/app/auth/login/login.component.html +++ /dev/null @@ -1,62 +0,0 @@ - -
- Worklenz -
-
- Log Into your account. -
- -
diff --git a/worklenz-frontend/src/app/auth/login/login.component.scss b/worklenz-frontend/src/app/auth/login/login.component.scss deleted file mode 100644 index 7d77f32d..00000000 --- a/worklenz-frontend/src/app/auth/login/login.component.scss +++ /dev/null @@ -1,11 +0,0 @@ -.login-form-margin { - margin-bottom: 16px; -} - -.login-form-forgot { - float: right; -} - -.login-form-button { - width: 100%; -} diff --git a/worklenz-frontend/src/app/auth/login/login.component.spec.ts b/worklenz-frontend/src/app/auth/login/login.component.spec.ts deleted file mode 100644 index 32faddf1..00000000 --- a/worklenz-frontend/src/app/auth/login/login.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {ComponentFixture, TestBed} from "@angular/core/testing"; - -import {LoginComponent} from "./login.component"; - -describe("LoginComponent", () => { - let component: LoginComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [LoginComponent] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(LoginComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it("should create", () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/auth/login/login.component.ts b/worklenz-frontend/src/app/auth/login/login.component.ts deleted file mode 100644 index 3d508b7c..00000000 --- a/worklenz-frontend/src/app/auth/login/login.component.ts +++ /dev/null @@ -1,150 +0,0 @@ -import {AfterViewInit, Component, ElementRef, NgZone, OnInit, ViewChild} from '@angular/core'; -import {ActivatedRoute, Router} from '@angular/router'; -import {FormBuilder, FormGroup, Validators} from '@angular/forms'; - -import {AppService} from '@services/app.service'; -import {IAuthorizeResponse} from '@interfaces/api-models/authorize-response'; -import {AuthService} from '@services/auth.service'; -import {AuthApiService} from '@api/auth-api.service'; -import {log_error} from "@shared/utils"; -import {WORKLENZ_REDIRECT_PROJ_KEY} from "@shared/constants"; - -@Component({ - selector: 'worklenz-login', - templateUrl: './login.component.html', - styleUrls: ['./login.component.scss'] -}) -export class LoginComponent implements OnInit, AfterViewInit { - @ViewChild("emailInput") emailInput!: ElementRef; - - form!: FormGroup; - - validating = true; - loading = false; - passwordVisible = false; - loadingGoogle = false; - - teamId: string | null = null; - - set projectId(value: string | null) { - if (!value) { - localStorage.removeItem(WORKLENZ_REDIRECT_PROJ_KEY); - return; - } - localStorage.setItem(WORKLENZ_REDIRECT_PROJ_KEY, value); - } - - get projectId() { - return localStorage.getItem(WORKLENZ_REDIRECT_PROJ_KEY); - } - - constructor( - private readonly app: AppService, - private readonly router: Router, - private readonly api: AuthApiService, - private readonly auth: AuthService, - private readonly route: ActivatedRoute, - private readonly fb: FormBuilder, - private readonly ngZone: NgZone - ) { - this.app.setTitle('Login'); - this.teamId = this.route.snapshot.queryParamMap.get("team"); - this.projectId = this.route.snapshot.queryParamMap.get('project'); - - this.form = this.fb.group({ - email: [null, [Validators.required, Validators.email]], - password: [null, [Validators.required]], - remember: [false] - }); - } - - get teamIdQueryParam() { - const params = [ - `team=${this.teamId}` - ]; - - if (this.projectId) - params.push(`project=${this.projectId}`); - - return this.teamId ? `?${params.join('&')}` : ''; - } - - async ngOnInit() { - const session = this.auth.getCurrentSession(); - if (session && !session.setup_completed) { - void this.router.navigate(['/worklenz/setup']); - return; - } - const authorized = await this.authorize(); - if (!authorized) { - this.validating = false; - } - } - - ngAfterViewInit() { - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.emailInput?.nativeElement.focus(); - }, 250); - }); - } - - public async authorize(): Promise { - try { - const res: IAuthorizeResponse = await this.api.authorize(); - if (res.authenticated) { - this.auth.setCurrentSession(res.user); - await this.router.navigate(["/worklenz/home"]); - return true; - } - return false; - } catch (e) { - log_error(e); - return false; - } - } - - async login() { - if (this.loading) return; - if (this.form.valid) { - this.loading = true; - try { - await this.api.login({ - email: this.form.controls['email'].value, - password: this.form.controls['password'].value, - team_id: this.teamId || undefined, - project_id: this.projectId || undefined - }); - const authorized = await this.authorize(); - this.loading = false; - if (authorized) { - await this.router.navigate(['/authenticate']); - } else { - this.app.notify('Login failed!', 'Please check your email & password and retry.', false); - } - } catch (e) { - log_error(e); - this.loading = false; - this.app.notify('Login failed!', 'An unknown error has occurred. Please try again.', false); - } - } else { - Object.values(this.form.controls).forEach(control => { - if (control.invalid) { - control.markAsDirty(); - control.updateValueAndValidity({onlySelf: true}); - } - }); - } - } - - signInWithGoogle() { - if (this.loadingGoogle) return; - this.loadingGoogle = true; - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - window.location.href = `/secure/google${this.teamIdQueryParam}`; - }, 1000); - }); - } - -} diff --git a/worklenz-frontend/src/app/auth/reset-password/reset-password.component.html b/worklenz-frontend/src/app/auth/reset-password/reset-password.component.html deleted file mode 100644 index abc01a01..00000000 --- a/worklenz-frontend/src/app/auth/reset-password/reset-password.component.html +++ /dev/null @@ -1,55 +0,0 @@ -
-
-
-
-
-
-
-
- Worklenz -
- - - - -
-
- Please enter your email address. You will receive a link to create a new password via email. -
-
- -
-
- -
-
-
-
-
-
- -
diff --git a/worklenz-frontend/src/app/auth/reset-password/reset-password.component.scss b/worklenz-frontend/src/app/auth/reset-password/reset-password.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/auth/reset-password/reset-password.component.spec.ts b/worklenz-frontend/src/app/auth/reset-password/reset-password.component.spec.ts deleted file mode 100644 index e3723678..00000000 --- a/worklenz-frontend/src/app/auth/reset-password/reset-password.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {ComponentFixture, TestBed} from "@angular/core/testing"; - -import {ResetPasswordComponent} from "./reset-password.component"; - -describe("ResetPasswordComponent", () => { - let component: ResetPasswordComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ResetPasswordComponent] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(ResetPasswordComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it("should create", () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/auth/reset-password/reset-password.component.ts b/worklenz-frontend/src/app/auth/reset-password/reset-password.component.ts deleted file mode 100644 index 81c3d074..00000000 --- a/worklenz-frontend/src/app/auth/reset-password/reset-password.component.ts +++ /dev/null @@ -1,78 +0,0 @@ -import {Component, ElementRef, ViewChild} from '@angular/core'; -import {FormBuilder, FormGroup, Validators} from '@angular/forms'; -import {AppService} from '@services/app.service'; -import {ActivatedRoute, Router} from '@angular/router'; -import {AuthApiService} from '@api/auth-api.service'; -import {AuthService} from '@services/auth.service'; -import {IAuthorizeResponse} from '@interfaces/api-models/authorize-response'; -import {log_error} from "@shared/utils"; - -@Component({ - selector: 'worklenz-reset-password', - templateUrl: './reset-password.component.html', - styleUrls: ['./reset-password.component.scss'] -}) -export class ResetPasswordComponent { - @ViewChild('emailInput') emailInput!: ElementRef; - - form!: FormGroup; - - loading = false; - informationSent = false; - - teamId: string | null = null; - - constructor( - private app: AppService, - private router: Router, - private api: AuthApiService, - private auth: AuthService, - private route: ActivatedRoute, - private fb: FormBuilder - ) { - this.app.setTitle('Reset Password'); - - this.teamId = this.route.snapshot.queryParamMap.get('team'); - this.form = this.fb.group({ - email: [null, [Validators.required, Validators.email]] - }); - } - - async ngOnInit() { - await this.authorize(); - } - - public async authorize(): Promise { - try { - const res: IAuthorizeResponse = await this.api.authorize(); - if (res.authenticated) { - this.auth.setCurrentSession(res.user); - await this.router.navigate(['/worklenz/home']); - return true; - } - return false; - } catch (e) { - log_error(e); - return false; - } - } - - async resetPassword() { - if (this.loading) return; - if (this.form.valid) { - this.loading = true; - try { - const res = await this.api.resetPassword({email: this.form.controls['email'].value}); - if (res.done) - this.informationSent = true; - this.loading = false; - } catch (e) { - log_error(e); - this.loading = false; - this.app.notify('Reset password failed!', 'An unknown error has occurred. Please try again.', false); - } - } else { - this.app.displayErrorsOf(this.form); - } - } -} diff --git a/worklenz-frontend/src/app/auth/signup/signup.component.html b/worklenz-frontend/src/app/auth/signup/signup.component.html deleted file mode 100644 index c8b5144e..00000000 --- a/worklenz-frontend/src/app/auth/signup/signup.component.html +++ /dev/null @@ -1,79 +0,0 @@ -
- Worklenz -
- -
Create your account.
- - - - diff --git a/worklenz-frontend/src/app/auth/signup/signup.component.scss b/worklenz-frontend/src/app/auth/signup/signup.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/auth/signup/signup.component.spec.ts b/worklenz-frontend/src/app/auth/signup/signup.component.spec.ts deleted file mode 100644 index 1aa1bd5d..00000000 --- a/worklenz-frontend/src/app/auth/signup/signup.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {ComponentFixture, TestBed} from "@angular/core/testing"; - -import {SignupComponent} from "./signup.component"; - -describe("SignupComponent", () => { - let component: SignupComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [SignupComponent] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(SignupComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it("should create", () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/auth/signup/signup.component.ts b/worklenz-frontend/src/app/auth/signup/signup.component.ts deleted file mode 100644 index b44fa0f6..00000000 --- a/worklenz-frontend/src/app/auth/signup/signup.component.ts +++ /dev/null @@ -1,170 +0,0 @@ -import {AfterViewInit, Component, ElementRef, NgZone, ViewChild} from '@angular/core'; -import {FormBuilder, FormGroup, Validators} from '@angular/forms'; -import {ActivatedRoute, Router} from '@angular/router'; - -import {AppService} from '@services/app.service'; -import {IUserSignUpRequest} from '@interfaces/api-models/user-sign-up-request'; -import {AuthApiService} from '@api/auth-api.service'; -import {log_error} from "@shared/utils"; -import {PASSWORD_POLICY, WORKLENZ_REDIRECT_PROJ_KEY} from "@shared/constants"; -import {IPasswordValidityResult} from "@interfaces/password-validity-result"; - -@Component({ - selector: 'worklenz-signup', - templateUrl: './signup.component.html', - styleUrls: ['./signup.component.scss'] -}) -export class SignupComponent implements AfterViewInit { - @ViewChild("nameInput") nameInput!: ElementRef; - - form!: FormGroup; - - loading = false; - validating = false; - passwordVisible = false; - loadingGoogle = false; - - email: string | null = null; // invited user's email - name: string | null = null; // invited user's name - teamId: string | null = null; // invited team id - teamMemberId: string | null = null; - passwordStrength: IPasswordValidityResult | null = null; - - readonly passwordPolicy = PASSWORD_POLICY; - - set projectId(value: string | null) { - if (!value) { - localStorage.removeItem(WORKLENZ_REDIRECT_PROJ_KEY); - return; - } - localStorage.setItem(WORKLENZ_REDIRECT_PROJ_KEY, value); - } - - get projectId() { - return localStorage.getItem(WORKLENZ_REDIRECT_PROJ_KEY); - } - - get hasPassword() { - return this.form.controls["password"].valid; - } - - model: IUserSignUpRequest | null = null; - - constructor( - private readonly app: AppService, - private readonly router: Router, - private readonly fb: FormBuilder, - private readonly api: AuthApiService, - private readonly route: ActivatedRoute, - private readonly ngZone: NgZone - ) { - this.app.setTitle('Signup'); - - this.email = this.route.snapshot.queryParamMap.get("email"); - this.name = this.route.snapshot.queryParamMap.get("name"); - this.teamId = this.route.snapshot.queryParamMap.get("team"); - this.teamMemberId = this.route.snapshot.queryParamMap.get("user"); - this.projectId = this.route.snapshot.queryParamMap.get('project'); - - this.form = this.fb.group({ - name: [this.name, [Validators.required]], - email: [this.email, [Validators.required, Validators.email]], - password: [null, [Validators.required]] - }); - - this.form.controls["password"].valueChanges.subscribe(value => { - void this.checkPasswordStrength(value); - }); - } - - get invitationQueryParams() { - const params = [ - `team=${this.teamId}`, - `teamMember=${this.teamMemberId}` - ]; - if (this.projectId) - params.push(`project=${this.projectId}`); - return this.teamId && this.teamMemberId ? `?${params.join('&')}` : ''; - } - - ngAfterViewInit() { - void this.checkPasswordStrength(this.form.value.password); - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - this.nameInput?.nativeElement.focus(); - }, 250); - }); - } - - async validate() { - if (this.validating) return; - if (this.form.valid) { - this.validating = true; - try { - const body = { - name: this.form.controls['name'].value, - email: this.form.controls['email'].value, - password: this.form.controls['password'].value - }; - const res = await this.api.signupCheck(body); - - this.validating = false; - if (res.done) { - this.model = body; - void this.signupWithEmail(); - } - } catch (e) { - log_error(e); - this.validating = false; - this.app.notify('Signup failed!', 'An unknown error has occurred. Please try again.', false); - } - } else { - this.app.displayErrorsOf(this.form); - } - } - - signUpWithGoogle() { - this.loadingGoogle = true; - this.ngZone.runOutsideAngular(() => { - setTimeout(() => { - window.location.href = `/secure/google${this.invitationQueryParams}`; - }, 1000); - }); - } - - private async signupWithEmail() { - if (this.loading) return; - if (!this.model) return; - try { - this.loading = true; - this.model.team_name = this.model?.name; - - if (this.teamId) - this.model.team_id = this.teamId; - if (this.teamMemberId) - this.model.team_member_id = this.teamMemberId; - if (this.projectId) - this.model.project_id = this.projectId; - - await this.api.signup(this.model); - const res = await this.api.authorize(); - if (res.authenticated) { - await this.router.navigate(["/authenticate"]); - } - this.loading = false; - } catch (e) { - this.loading = false; - } - } - - private async checkPasswordStrength(password: string) { - try { - const res = await this.api.checkPasswordStrength(password); - if (res.done) { - this.passwordStrength = res.body; - } - } catch (e) { - // ignored - } - } -} diff --git a/worklenz-frontend/src/app/auth/team-name/team-name.component.html b/worklenz-frontend/src/app/auth/team-name/team-name.component.html deleted file mode 100644 index 66b496a9..00000000 --- a/worklenz-frontend/src/app/auth/team-name/team-name.component.html +++ /dev/null @@ -1,19 +0,0 @@ - diff --git a/worklenz-frontend/src/app/auth/team-name/team-name.component.scss b/worklenz-frontend/src/app/auth/team-name/team-name.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/auth/team-name/team-name.component.spec.ts b/worklenz-frontend/src/app/auth/team-name/team-name.component.spec.ts deleted file mode 100644 index 0f0d7632..00000000 --- a/worklenz-frontend/src/app/auth/team-name/team-name.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TeamNameComponent} from './team-name.component'; - -describe('TeamNameComponent', () => { - let component: TeamNameComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [TeamNameComponent] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(TeamNameComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/auth/team-name/team-name.component.ts b/worklenz-frontend/src/app/auth/team-name/team-name.component.ts deleted file mode 100644 index bff511c8..00000000 --- a/worklenz-frontend/src/app/auth/team-name/team-name.component.ts +++ /dev/null @@ -1,84 +0,0 @@ -import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; -import {FormBuilder, FormGroup, Validators} from "@angular/forms"; -import {ActivatedRoute, Router} from "@angular/router"; - -import {IUserSignUpRequest} from "@interfaces/api-models/user-sign-up-request"; -import {AppService} from "@services/app.service"; -import {TeamMembersApiService} from "@api/team-members-api.service"; -import {AuthApiService} from "@api/auth-api.service"; - -@Component({ - selector: 'worklenz-team-name', - templateUrl: './team-name.component.html', - styleUrls: ['./team-name.component.scss'] -}) -export class TeamNameComponent implements OnInit { - @Input() model: IUserSignUpRequest | null = null; - @Output() back: EventEmitter = new EventEmitter(); - - form!: FormGroup; - - loading = false; - showBackButton = false; - - teamId: string | null = null; - teamMemberId: string | null = null; - - constructor( - private api: TeamMembersApiService, - private authApi: AuthApiService, - private fb: FormBuilder, - private router: Router, - private route: ActivatedRoute, - private app: AppService - ) { - this.teamId = this.route.snapshot.queryParamMap.get("team"); - this.teamMemberId = this.route.snapshot.queryParamMap.get('user'); - } - - get invitationQueryParams() { - return this.teamId && this.teamMemberId - ? `?team=${this.teamId}&teamMember=${this.teamMemberId}` : ''; - } - - ngOnInit() { - this.form = this.fb.group({ - team_name: [null, [Validators.required]] - }); - } - - async proceedRegistration() { - this.loginWithEmail(); - } - - private async loginWithEmail() { - if (this.loading) return; - if (!this.model) return; - - if (this.form.valid) { - try { - this.loading = true; - this.model.team_name = this.form.controls["team_name"].value; - if (this.teamId) - this.model.team_id = this.teamId; - if (this.teamMemberId) - this.model.team_member_id = this.teamMemberId; - - const res = await this.authApi.signup(this.model); - - this.showBackButton = !res.authenticated; - - if (res.authenticated) { - await this.router.navigate(["/authenticate"]); - } - - this.loading = false; - } catch (e) { - this.loading = false; - } - } else { - this.app.displayErrorsOf(this.form); - } - } - -} diff --git a/worklenz-frontend/src/app/auth/verify-reset-email/verify-reset-email.component.html b/worklenz-frontend/src/app/auth/verify-reset-email/verify-reset-email.component.html deleted file mode 100644 index 9de130b6..00000000 --- a/worklenz-frontend/src/app/auth/verify-reset-email/verify-reset-email.component.html +++ /dev/null @@ -1,51 +0,0 @@ -
-
-
-
-
-
-
-
- Worklenz -
-
- Enter your new password -
-
- -
-
-
-
-
-
-
- -
diff --git a/worklenz-frontend/src/app/auth/verify-reset-email/verify-reset-email.component.scss b/worklenz-frontend/src/app/auth/verify-reset-email/verify-reset-email.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/auth/verify-reset-email/verify-reset-email.component.spec.ts b/worklenz-frontend/src/app/auth/verify-reset-email/verify-reset-email.component.spec.ts deleted file mode 100644 index 863e5c88..00000000 --- a/worklenz-frontend/src/app/auth/verify-reset-email/verify-reset-email.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {VerifyResetEmailComponent} from './verify-reset-email.component'; - -describe('VerifyResetEmailComponent', () => { - let component: VerifyResetEmailComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [VerifyResetEmailComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(VerifyResetEmailComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/auth/verify-reset-email/verify-reset-email.component.ts b/worklenz-frontend/src/app/auth/verify-reset-email/verify-reset-email.component.ts deleted file mode 100644 index 1d3c6723..00000000 --- a/worklenz-frontend/src/app/auth/verify-reset-email/verify-reset-email.component.ts +++ /dev/null @@ -1,74 +0,0 @@ -import {Component, OnInit} from '@angular/core'; -import {ActivatedRoute, Router} from '@angular/router'; -import {FormBuilder, FormGroup, Validators} from "@angular/forms"; -import {AppService} from "@services/app.service"; -import {IUpdatePasswordRequest} from "@interfaces/api-models/verify-reset-email"; -import {AuthApiService} from "@api/auth-api.service"; -import {log_error} from "@shared/utils"; -import {PASSWORD_POLICY} from "@shared/constants"; - -@Component({ - selector: 'worklenz-verify-reset-email', - templateUrl: './verify-reset-email.component.html', - styleUrls: ['./verify-reset-email.component.scss'] -}) -export class VerifyResetEmailComponent implements OnInit { - hash: string = ''; - user: string = ''; - - form!: FormGroup; - - loading = false; - passwordVisible = false; - validating = false; - - model: IUpdatePasswordRequest = {}; - - readonly passwordPolicy = PASSWORD_POLICY; - - constructor( - private app: AppService, - private api: AuthApiService, - private fb: FormBuilder, - private route: ActivatedRoute, - private router: Router, - ) { - this.app.setTitle('Reset Password'); - this.form = this.fb.group({ - password: [null, [Validators.required]] - }); - } - - ngOnInit(): void { - this.route.paramMap.subscribe((data) => { - this.hash = data.get("hash") || ""; - this.user = data.get("user") || ""; - }) - } - - async resetPassword() { - if (this.validating) return; - if (this.form.valid) { - this.validating = true; - try { - const body: IUpdatePasswordRequest = { - password: this.form.controls['password'].value, - hash: this.hash, - user: this.user - }; - const res = await this.api.updateNewPassword(body); - if (res.done) { - await this.router.navigate(["/auth/login"]); - } - - this.validating = false; - } catch (e) { - log_error(e); - this.validating = false; - this.app.notify('Reset password failed!', 'An unknown error has occurred. Please try again.', false); - } - } else { - this.app.displayErrorsOf(this.form); - } - } -} diff --git a/worklenz-frontend/src/app/authenticate/authenticate.component.html b/worklenz-frontend/src/app/authenticate/authenticate.component.html deleted file mode 100644 index b9754093..00000000 --- a/worklenz-frontend/src/app/authenticate/authenticate.component.html +++ /dev/null @@ -1 +0,0 @@ -

Authenticating...

diff --git a/worklenz-frontend/src/app/authenticate/authenticate.component.scss b/worklenz-frontend/src/app/authenticate/authenticate.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/authenticate/authenticate.component.spec.ts b/worklenz-frontend/src/app/authenticate/authenticate.component.spec.ts deleted file mode 100644 index 0f044b2f..00000000 --- a/worklenz-frontend/src/app/authenticate/authenticate.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {AuthenticateComponent} from './authenticate.component'; - -describe('AuthenticateComponent', () => { - let component: AuthenticateComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [AuthenticateComponent] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(AuthenticateComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/authenticate/authenticate.component.ts b/worklenz-frontend/src/app/authenticate/authenticate.component.ts deleted file mode 100644 index 58236a8d..00000000 --- a/worklenz-frontend/src/app/authenticate/authenticate.component.ts +++ /dev/null @@ -1,57 +0,0 @@ -import {Component, OnInit} from '@angular/core'; -import {Router} from "@angular/router"; - -import {IAuthorizeResponse} from "@interfaces/api-models/authorize-response"; -import {AppService} from "@services/app.service"; -import {AuthService} from "@services/auth.service"; -import {AuthApiService} from "@api/auth-api.service"; -import {log_error} from "@shared/utils"; -import {WORKLENZ_REDIRECT_PROJ_KEY} from "@shared/constants"; - -@Component({ - selector: 'worklenz-authenticate', - templateUrl: './authenticate.component.html', - styleUrls: ['./authenticate.component.scss'], - standalone: true -}) -export class AuthenticateComponent implements OnInit { - - constructor( - private readonly api: AuthApiService, - private readonly router: Router, - private readonly auth: AuthService, - private readonly app: AppService - ) { - this.app.setTitle("Authenticating..."); - } - - async ngOnInit() { - await this.authorize(); - } - - public async authorize() { - try { - const res: IAuthorizeResponse = await this.api.authorize(); - if (res.authenticated) { - this.auth.setCurrentSession(res.user); - this.handleSuccessRedirect(); - return; - } - } catch (e) { - log_error(e); - } - - await this.router.navigate(["/"]); - } - - private handleSuccessRedirect() { - const project = localStorage.getItem(WORKLENZ_REDIRECT_PROJ_KEY); - if (project) { - localStorage.removeItem(WORKLENZ_REDIRECT_PROJ_KEY); - window.location.href = `/worklenz/projects/${project}`; - return; - } - - window.location.href = "/worklenz"; - } -} diff --git a/worklenz-frontend/src/app/directives/infinite-scroll-trigger.directive.spec.ts b/worklenz-frontend/src/app/directives/infinite-scroll-trigger.directive.spec.ts deleted file mode 100644 index 3d5db9b7..00000000 --- a/worklenz-frontend/src/app/directives/infinite-scroll-trigger.directive.spec.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {InfiniteScrollTriggerDirective} from './infinite-scroll-trigger.directive'; - -describe('InfiniteScrollTriggerDirective', () => { - it('should create an instance', () => { - // @ts-ignore - const directive = new InfiniteScrollTriggerDirective(); - expect(directive).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/directives/infinite-scroll-trigger.directive.ts b/worklenz-frontend/src/app/directives/infinite-scroll-trigger.directive.ts deleted file mode 100644 index c4b40e11..00000000 --- a/worklenz-frontend/src/app/directives/infinite-scroll-trigger.directive.ts +++ /dev/null @@ -1,100 +0,0 @@ -import {Directive, ElementRef, Input} from '@angular/core'; -import {filter, ReplaySubject, Subscription} from "rxjs"; -import {InfiniteScrollDirective} from "ngx-infinite-scroll"; - -@Directive({ - selector: '[infiniteScrollTrigger]', - standalone: true -}) -export class InfiniteScrollTriggerDirective { - @Input() set items(items: any[]) { - this.items$.next(items); - } - - items$ = new ReplaySubject(1); // so new subscribers won't "miss" the previous value - itemSub!: Subscription; // so we can unsubscribe later! - - constructor( - private elementRef: ElementRef, - private infiniteScroll: InfiniteScrollDirective - ) { - } - - ngOnInit() { - this.itemSub = this.items$ - .asObservable() - .pipe( - // ignore until we've loaded some items - filter((items) => !!items && items.length > 0), - // wait for container to re-render the new items list - // delay(1) - ) - .subscribe(() => this.doOverflowCheck()); - } - - ngOnDestroy() { - // Don't forget to unsubscribe! - this.itemSub.unsubscribe(); - } - - containerDoesNotOverflow(): boolean { - // The element property is private... don't make this a habit. - const container = this.resolveContainerElement( - this.infiniteScroll.infiniteScrollContainer, - this.infiniteScroll.scrollWindow, - this.elementRef.nativeElement, - this.infiniteScroll.scrollWindow - ); - - if (!container) { - return false; - } - - // The infiniteScroll directive allows horizontal scrolling - // If true, check if the container overflows horizontally - if (this.infiniteScroll.horizontal) { - return container.clientWidth === container.scrollWidth; - } - - return container.clientHeight === container.scrollHeight; - } - - doOverflowCheck(): void { - if (this.containerDoesNotOverflow()) { - this.infiniteScroll.scrolled.emit(); - } - } - - private resolveContainerElement( - selector: string | any, - scrollWindow: any, - defaultElement: any, - fromRoot: boolean - ): any { - const hasWindow = - window && !!window.document && window.document.documentElement; - let container = hasWindow && scrollWindow ? window : defaultElement; - if (selector) { - const containerIsString = - selector && hasWindow && typeof selector === 'string'; - container = containerIsString - ? this.findElement(selector, defaultElement.nativeElement, fromRoot) - : selector; - if (!container) { - throw new Error( - 'ngx-infinite-scroll {resolveContainerElement()}: selector for' - ); - } - } - return container; - } - - private findElement( - selector: string | any, - customRoot: ElementRef | any, - fromRoot: boolean - ) { - const rootEl = fromRoot ? window.document : customRoot; - return rootEl.querySelector(selector); - } -} diff --git a/worklenz-frontend/src/app/directives/lazy-for.directive.spec.ts b/worklenz-frontend/src/app/directives/lazy-for.directive.spec.ts deleted file mode 100644 index bbf5060a..00000000 --- a/worklenz-frontend/src/app/directives/lazy-for.directive.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {LazyForDirective} from './lazy-for.directive'; - -describe('LazyForDirective', () => { - it('should create an instance', () => { - const directive = new LazyForDirective(); - expect(directive).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/directives/lazy-for.directive.ts b/worklenz-frontend/src/app/directives/lazy-for.directive.ts deleted file mode 100644 index 684f7cd7..00000000 --- a/worklenz-frontend/src/app/directives/lazy-for.directive.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { - Directive, - DoCheck, - Input, - IterableDiffer, - IterableDiffers, - OnInit, - TemplateRef, - ViewContainerRef -} from '@angular/core'; - -@Directive({ - selector: '[lazyFor]', - standalone: true -}) -export class LazyForDirective implements DoCheck, OnInit { - - lazyForContainer: HTMLElement | null = null; - - itemHeight!: number; - itemTagName!: string; - - @Input() - set lazyForOf(list: any[]) { - this.list = list; - - if (list) { - this.differ = this.iterableDiffers.find(list).create(); - - if (this.initialized) { - this.update(); - } - } - } - - private templateElem!: HTMLElement; - - private beforeListElem!: HTMLElement; - private afterListElem!: HTMLElement; - - private list: any[] = []; - - private initialized = false; - private firstUpdate = true; - - private differ!: IterableDiffer; - - private lastChangeTriggeredByScroll = false; - - constructor(private vcr: ViewContainerRef, - private tpl: TemplateRef, - private iterableDiffers: IterableDiffers) { - } - - ngOnInit() { - this.templateElem = this.vcr.element.nativeElement; - - this.lazyForContainer = this.templateElem.parentElement; - - //Adding an event listener will trigger ngDoCheck whenever the event fires so we don't actually need to call - //update here. - this.lazyForContainer?.addEventListener('scroll', () => { - this.lastChangeTriggeredByScroll = true; - }); - - this.initialized = true; - } - - ngDoCheck() { - if (this.differ && Array.isArray(this.list)) { - - if (this.lastChangeTriggeredByScroll) { - this.update(); - this.lastChangeTriggeredByScroll = false; - } else { - const changes = this.differ.diff(this.list); - - if (changes !== null) { - this.update(); - } - } - } - } - - /** - * List update - * - * @returns {void} - */ - private update(): void { - - //Can't run the first update unless there is an element in the list - if (this.list.length === 0) { - this.vcr.clear(); - if (!this.firstUpdate) { - this.beforeListElem.style.height = '0'; - this.afterListElem.style.height = '0'; - } - return; - } - - if (this.firstUpdate) { - this.onFirstUpdate(); - } - - if (!this.lazyForContainer) return; - - const listHeight = this.lazyForContainer?.clientHeight; - const scrollTop = this.lazyForContainer?.scrollTop; - - //The height of anything inside the container but above the lazyFor content - const fixedHeaderHeight = - (this.beforeListElem.getBoundingClientRect().top - this.beforeListElem.scrollTop) - - (this.lazyForContainer.getBoundingClientRect().top - this.lazyForContainer.scrollTop); - - //This needs to run after the scrollTop is retrieved. - this.vcr.clear(); - - let listStartI = Math.floor((scrollTop - fixedHeaderHeight) / this.itemHeight); - listStartI = this.limitToRange(listStartI, 0, this.list.length); - - let listEndI = Math.ceil((scrollTop - fixedHeaderHeight + listHeight) / this.itemHeight); - listEndI = this.limitToRange(listEndI, -1, this.list.length - 1); - - for (let i = listStartI; i <= listEndI; i++) { - this.vcr.createEmbeddedView(this.tpl, { - $implicit: this.list[i], - index: i - }); - } - - this.beforeListElem.style.height = `${listStartI * this.itemHeight}px`; - this.afterListElem.style.height = `${(this.list.length - listEndI - 1) * this.itemHeight}px`; - } - - /** - * First update. - * - * @returns {void} - */ - private onFirstUpdate(): void { - - let sampleItemElem: HTMLElement; - if (this.itemHeight === undefined || this.itemTagName === undefined) { - this.vcr.createEmbeddedView(this.tpl, { - $implicit: this.list[0], - index: 0 - }); - sampleItemElem = this.templateElem.nextSibling; - } - - if (this.itemHeight === undefined) { - // @ts-ignore - this.itemHeight = sampleItemElem.clientHeight; - } - - if (this.itemTagName === undefined) { - // @ts-ignore - this.itemTagName = sampleItemElem.tagName; - } - - this.beforeListElem = document.createElement(this.itemTagName); - // @ts-ignore - this.templateElem.parentElement.insertBefore(this.beforeListElem, this.templateElem); - - this.afterListElem = document.createElement(this.itemTagName); - // @ts-ignore - this.templateElem.parentElement.insertBefore(this.afterListElem, this.templateElem.nextSibling); - - // If you want to use
  • elements - if (this.itemTagName.toLowerCase() === 'li') { - this.beforeListElem.style.listStyleType = 'none'; - this.afterListElem.style.listStyleType = 'none'; - } - - this.firstUpdate = false; - } - - /** - * Limit To Range - * - * @param {number} num - Element number. - * @param {number} min - Min element number. - * @param {number} max - Max element number. - * - * @returns {number} - */ - private limitToRange(num: number, min: number, max: number) { - return Math.max( - Math.min(num, max), - min - ); - } -} diff --git a/worklenz-frontend/src/app/errors/not-authorized/not-authorized.component.html b/worklenz-frontend/src/app/errors/not-authorized/not-authorized.component.html deleted file mode 100644 index f9babe7d..00000000 --- a/worklenz-frontend/src/app/errors/not-authorized/not-authorized.component.html +++ /dev/null @@ -1,5 +0,0 @@ - -
    - -
    -
    diff --git a/worklenz-frontend/src/app/errors/not-authorized/not-authorized.component.scss b/worklenz-frontend/src/app/errors/not-authorized/not-authorized.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/errors/not-authorized/not-authorized.component.spec.ts b/worklenz-frontend/src/app/errors/not-authorized/not-authorized.component.spec.ts deleted file mode 100644 index ea8ea4bf..00000000 --- a/worklenz-frontend/src/app/errors/not-authorized/not-authorized.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {NotAuthorizedComponent} from './not-authorized.component'; - -describe('NotAuthorizedComponent', () => { - let component: NotAuthorizedComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [NotAuthorizedComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(NotAuthorizedComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/errors/not-authorized/not-authorized.component.ts b/worklenz-frontend/src/app/errors/not-authorized/not-authorized.component.ts deleted file mode 100644 index 6adf158b..00000000 --- a/worklenz-frontend/src/app/errors/not-authorized/not-authorized.component.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {Component} from '@angular/core'; -import {NzResultModule} from "ng-zorro-antd/result"; -import {RouterLink} from "@angular/router"; -import {NzButtonModule} from "ng-zorro-antd/button"; - -@Component({ - selector: 'worklenz-not-authorized', - templateUrl: './not-authorized.component.html', - styleUrls: ['./not-authorized.component.scss'], - imports: [ - NzResultModule, - RouterLink, - NzButtonModule - ], - standalone: true -}) -export class NotAuthorizedComponent { - -} diff --git a/worklenz-frontend/src/app/errors/not-found/not-found.component.html b/worklenz-frontend/src/app/errors/not-found/not-found.component.html deleted file mode 100644 index e12ade33..00000000 --- a/worklenz-frontend/src/app/errors/not-found/not-found.component.html +++ /dev/null @@ -1,5 +0,0 @@ - -
    - -
    -
    diff --git a/worklenz-frontend/src/app/errors/not-found/not-found.component.scss b/worklenz-frontend/src/app/errors/not-found/not-found.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/errors/not-found/not-found.component.spec.ts b/worklenz-frontend/src/app/errors/not-found/not-found.component.spec.ts deleted file mode 100644 index c168fad1..00000000 --- a/worklenz-frontend/src/app/errors/not-found/not-found.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {NotFoundComponent} from './not-found.component'; - -describe('NotFoundComponent', () => { - let component: NotFoundComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [NotFoundComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(NotFoundComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/errors/not-found/not-found.component.ts b/worklenz-frontend/src/app/errors/not-found/not-found.component.ts deleted file mode 100644 index 8d17e02a..00000000 --- a/worklenz-frontend/src/app/errors/not-found/not-found.component.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {Component} from '@angular/core'; -import {NzResultModule} from "ng-zorro-antd/result"; -import {RouterLink} from "@angular/router"; -import {NzButtonModule} from "ng-zorro-antd/button"; - -@Component({ - selector: 'worklenz-not-found', - templateUrl: './not-found.component.html', - styleUrls: ['./not-found.component.scss'], - imports: [ - NzResultModule, - RouterLink, - NzButtonModule - ], - standalone: true -}) -export class NotFoundComponent { - -} diff --git a/worklenz-frontend/src/app/guards/auth.guard.spec.ts b/worklenz-frontend/src/app/guards/auth.guard.spec.ts deleted file mode 100644 index f40bf976..00000000 --- a/worklenz-frontend/src/app/guards/auth.guard.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {TestBed} from '@angular/core/testing'; - -import {AuthGuard} from './auth.guard'; - -describe('AuthGuard', () => { - let guard: AuthGuard; - - beforeEach(() => { - TestBed.configureTestingModule({}); - guard = TestBed.inject(AuthGuard); - }); - - it('should be created', () => { - expect(guard).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/guards/auth.guard.ts b/worklenz-frontend/src/app/guards/auth.guard.ts deleted file mode 100644 index 79cc719d..00000000 --- a/worklenz-frontend/src/app/guards/auth.guard.ts +++ /dev/null @@ -1,30 +0,0 @@ -import {inject, Injectable} from '@angular/core'; -import {ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot, UrlTree} from '@angular/router'; -import {Observable} from 'rxjs'; - -import {AuthService} from "@services/auth.service"; - -@Injectable({ - providedIn: 'root' -}) -class AuthGuardService { - constructor( - private auth: AuthService, - private router: Router - ) { - } - - canActivate( - _route: ActivatedRouteSnapshot, - _state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { - const authenticated = this.auth.isAuthenticated(); - if (!authenticated) { - return this.router.navigate(["/auth/login"]); - } - return authenticated; - } -} - -export const AuthGuard: CanActivateFn = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean => { - return inject(AuthGuardService).canActivate(next, state); -} diff --git a/worklenz-frontend/src/app/guards/login-check.guard.spec.ts b/worklenz-frontend/src/app/guards/login-check.guard.spec.ts deleted file mode 100644 index e7534752..00000000 --- a/worklenz-frontend/src/app/guards/login-check.guard.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {TestBed} from '@angular/core/testing'; - -import {LoginCheckGuard} from './login-check.guard'; - -describe('LoginCheckGuard', () => { - let guard: LoginCheckGuard; - - beforeEach(() => { - TestBed.configureTestingModule({}); - guard = TestBed.inject(LoginCheckGuard); - }); - - it('should be created', () => { - expect(guard).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/guards/login-check.guard.ts b/worklenz-frontend/src/app/guards/login-check.guard.ts deleted file mode 100644 index f5339115..00000000 --- a/worklenz-frontend/src/app/guards/login-check.guard.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {inject, Injectable} from '@angular/core'; -import {ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot, UrlTree} from '@angular/router'; -import {Observable} from 'rxjs'; -import {AuthService} from "@services/auth.service"; - -@Injectable({ - providedIn: 'root' -}) -class LoginCheckGuardService { - constructor( - private auth: AuthService, - private router: Router - ) { - } - - canActivate( - _route: ActivatedRouteSnapshot, - _state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { - const authenticated = this.auth.isAuthenticated(); - if (authenticated) { - return this.router.navigate(["/worklenz"]); - } - return true; - } -} - -export const LoginCheckGuard: CanActivateFn = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean => { - return inject(LoginCheckGuardService).canActivate(next, state); -} diff --git a/worklenz-frontend/src/app/guards/non-google-account.guard.spec.ts b/worklenz-frontend/src/app/guards/non-google-account.guard.spec.ts deleted file mode 100644 index 12e3968d..00000000 --- a/worklenz-frontend/src/app/guards/non-google-account.guard.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {TestBed} from '@angular/core/testing'; - -import {NonGoogleAccountGuard} from './non-google-account.guard'; - -describe('NonGoogleAccountGuard', () => { - let guard: NonGoogleAccountGuard; - - beforeEach(() => { - TestBed.configureTestingModule({}); - guard = TestBed.inject(NonGoogleAccountGuard); - }); - - it('should be created', () => { - expect(guard).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/guards/non-google-account.guard.ts b/worklenz-frontend/src/app/guards/non-google-account.guard.ts deleted file mode 100644 index 82da7ed3..00000000 --- a/worklenz-frontend/src/app/guards/non-google-account.guard.ts +++ /dev/null @@ -1,28 +0,0 @@ -import {inject, Injectable} from '@angular/core'; -import {ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot, UrlTree} from '@angular/router'; -import {Observable} from 'rxjs'; -import {AuthService} from "@services/auth.service"; - -@Injectable({ - providedIn: 'root' -}) -class NonGoogleAccountGuardService { - constructor( - private auth: AuthService, - private router: Router - ) { - } - - canActivate( - _route: ActivatedRouteSnapshot, - _state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { - const session = this.auth.getCurrentSession(); - if (session?.is_google) - return this.router.navigate(['/worklenz']); - return true; - } -} - -export const NonGoogleAccountGuard: CanActivateFn = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean => { - return inject(NonGoogleAccountGuardService).canActivate(next, state); -} diff --git a/worklenz-frontend/src/app/guards/team-member.guard.spec.ts b/worklenz-frontend/src/app/guards/team-member.guard.spec.ts deleted file mode 100644 index 994a3eab..00000000 --- a/worklenz-frontend/src/app/guards/team-member.guard.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {TestBed} from '@angular/core/testing'; - -import {TeamMemberGuard} from './team-member.guard'; - -describe('TeamMemberGuard', () => { - let guard: TeamMemberGuard; - - beforeEach(() => { - TestBed.configureTestingModule({}); - guard = TestBed.inject(TeamMemberGuard); - }); - - it('should be created', () => { - expect(guard).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/guards/team-member.guard.ts b/worklenz-frontend/src/app/guards/team-member.guard.ts deleted file mode 100644 index 6585ece0..00000000 --- a/worklenz-frontend/src/app/guards/team-member.guard.ts +++ /dev/null @@ -1,31 +0,0 @@ -import {inject, Injectable} from '@angular/core'; -import {ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot, UrlTree} from '@angular/router'; -import {Observable} from 'rxjs'; -import {AuthService} from "@services/auth.service"; - -@Injectable({ - providedIn: 'root' -}) -class TeamMemberGuardService { - constructor( - private auth: AuthService, - private router: Router - ) { - } - - canActivate( - _route: ActivatedRouteSnapshot, - _state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { - const session = this.auth.getCurrentSession(); - if (session?.is_admin || session?.owner) return true; - const canAccess = !!session?.is_member; - if (!canAccess) - return this.router.navigate(['/worklenz']); - return true; - } - -} - -export const TeamMemberGuard: CanActivateFn = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean => { - return inject(TeamMemberGuardService).canActivate(next, state); -} diff --git a/worklenz-frontend/src/app/guards/team-name.guard.spec.ts b/worklenz-frontend/src/app/guards/team-name.guard.spec.ts deleted file mode 100644 index 37e52f64..00000000 --- a/worklenz-frontend/src/app/guards/team-name.guard.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {TestBed} from '@angular/core/testing'; - -import {TeamNameGuard} from './team-name.guard'; - -describe('TeamNameGuard', () => { - let guard: TeamNameGuard; - - beforeEach(() => { - TestBed.configureTestingModule({}); - guard = TestBed.inject(TeamNameGuard); - }); - - it('should be created', () => { - expect(guard).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/guards/team-name.guard.ts b/worklenz-frontend/src/app/guards/team-name.guard.ts deleted file mode 100644 index c68b9fc8..00000000 --- a/worklenz-frontend/src/app/guards/team-name.guard.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {inject, Injectable} from '@angular/core'; -import {ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot, UrlTree} from '@angular/router'; -import {Observable} from 'rxjs'; -import {AuthService} from "@services/auth.service"; - -@Injectable({ - providedIn: 'root' -}) -class TeamNameGuardService { - constructor( - private auth: AuthService, - private router: Router - ) { - } - - canActivate( - _route: ActivatedRouteSnapshot, - _state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { - const session = this.auth.getCurrentSession(); - if (!session?.setup_completed) - return this.router.navigate(['/worklenz/setup']); - return true; - } - -} - -export const TeamNameGuard: CanActivateFn = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean => { - return inject(TeamNameGuardService).canActivate(next, state); -} diff --git a/worklenz-frontend/src/app/guards/team-owner-or-admin-guard.service.ts b/worklenz-frontend/src/app/guards/team-owner-or-admin-guard.service.ts deleted file mode 100644 index 271b84ca..00000000 --- a/worklenz-frontend/src/app/guards/team-owner-or-admin-guard.service.ts +++ /dev/null @@ -1,30 +0,0 @@ -import {inject, Injectable} from '@angular/core'; -import {ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot, UrlTree} from '@angular/router'; -import {Observable} from 'rxjs'; -import {AuthService} from '@services/auth.service'; - -@Injectable({ - providedIn: 'root' -}) -class TeamOwnerOrAdminGuardService { - constructor( - private auth: AuthService, - private router: Router - ) { - } - - canActivate( - _route: ActivatedRouteSnapshot, - _state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { - const isProjects = _route.routeConfig?.path === "projects"; - const canAccess = !!this.auth.getCurrentSession()?.owner || !!this.auth.getCurrentSession()?.is_admin; - if (!canAccess && !isProjects) - return this.router.navigate(['/worklenz']); - return true; - } - -} - -export const TeamOwnerOrAdminGuard: CanActivateFn = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean => { - return inject(TeamOwnerOrAdminGuardService).canActivate(next, state); -} diff --git a/worklenz-frontend/src/app/guards/team-owner-or-admin.guard.spec.ts b/worklenz-frontend/src/app/guards/team-owner-or-admin.guard.spec.ts deleted file mode 100644 index 67d911eb..00000000 --- a/worklenz-frontend/src/app/guards/team-owner-or-admin.guard.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {TestBed} from '@angular/core/testing'; - -import {TeamOwnerOrAdminGuard} from './team-owner-or-admin-guard.service'; - -describe('TeamOwnerGuard', () => { - let guard: TeamOwnerOrAdminGuard; - - beforeEach(() => { - TestBed.configureTestingModule({}); - guard = TestBed.inject(TeamOwnerOrAdminGuard); - }); - - it('should be created', () => { - expect(guard).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/guards/team-owner.guard.spec.ts b/worklenz-frontend/src/app/guards/team-owner.guard.spec.ts deleted file mode 100644 index 487de51f..00000000 --- a/worklenz-frontend/src/app/guards/team-owner.guard.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {TestBed} from '@angular/core/testing'; - -import {TeamOwnerGuard} from './team-owner.guard'; - -describe('TeamOwnerGuard', () => { - let guard: TeamOwnerGuard; - - beforeEach(() => { - TestBed.configureTestingModule({}); - guard = TestBed.inject(TeamOwnerGuard); - }); - - it('should be created', () => { - expect(guard).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/guards/team-owner.guard.ts b/worklenz-frontend/src/app/guards/team-owner.guard.ts deleted file mode 100644 index 705037aa..00000000 --- a/worklenz-frontend/src/app/guards/team-owner.guard.ts +++ /dev/null @@ -1,30 +0,0 @@ -import {inject, Injectable} from '@angular/core'; -import {ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot, UrlTree} from '@angular/router'; -import {Observable} from 'rxjs'; -import {AuthService} from "@services/auth.service"; - -@Injectable({ - providedIn: 'root' -}) -class TeamOwnerGuardService { - constructor( - private auth: AuthService, - private router: Router - ) { - } - - canActivate( - _route: ActivatedRouteSnapshot, - _state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { - const isProjects = _route.routeConfig?.path === "projects"; - const canAccess = !!this.auth.getCurrentSession()?.owner; - if (!canAccess && !isProjects) - return this.router.navigate(['/worklenz']); - return true; - } - -} - -export const TeamOwnerGuard: CanActivateFn = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean => { - return inject(TeamOwnerGuardService).canActivate(next, state); -} diff --git a/worklenz-frontend/src/app/guards/team-url.guard.spec.ts b/worklenz-frontend/src/app/guards/team-url.guard.spec.ts deleted file mode 100644 index 0b57fb6a..00000000 --- a/worklenz-frontend/src/app/guards/team-url.guard.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {TestBed} from '@angular/core/testing'; - -import {TeamUrlGuard} from './team-url.guard'; - -describe('TeamUrlGuard', () => { - let guard: TeamUrlGuard; - - beforeEach(() => { - TestBed.configureTestingModule({}); - guard = TestBed.inject(TeamUrlGuard); - }); - - it('should be created', () => { - expect(guard).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/guards/team-url.guard.ts b/worklenz-frontend/src/app/guards/team-url.guard.ts deleted file mode 100644 index 703fa75e..00000000 --- a/worklenz-frontend/src/app/guards/team-url.guard.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {inject, Injectable} from '@angular/core'; -import {ActivatedRouteSnapshot, CanActivateFn, RouterStateSnapshot, UrlTree} from '@angular/router'; -import {Observable} from 'rxjs'; - -@Injectable({ - providedIn: 'root' -}) -class TeamUrlGuardService { - canActivate( - _route: ActivatedRouteSnapshot, - _state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { - return true; - } - -} - -export const TeamUrlGuard: CanActivateFn = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean => { - return inject(TeamUrlGuardService).canActivate(next, state); -} diff --git a/worklenz-frontend/src/app/interceptors/http.interceptor.spec.ts b/worklenz-frontend/src/app/interceptors/http.interceptor.spec.ts deleted file mode 100644 index 248936c3..00000000 --- a/worklenz-frontend/src/app/interceptors/http.interceptor.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {TestBed} from '@angular/core/testing'; - -import {HTTPInterceptor} from './http.interceptor'; - -describe('HTTPInterceptor', () => { - beforeEach(() => TestBed.configureTestingModule({ - providers: [ - HTTPInterceptor - ] - })); - - it('should be created', () => { - const interceptor: HTTPInterceptor = TestBed.inject(HTTPInterceptor); - expect(interceptor).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/interceptors/http.interceptor.ts b/worklenz-frontend/src/app/interceptors/http.interceptor.ts deleted file mode 100644 index a7cd0564..00000000 --- a/worklenz-frontend/src/app/interceptors/http.interceptor.ts +++ /dev/null @@ -1,80 +0,0 @@ -import {Injectable} from '@angular/core'; -import { - HttpErrorResponse, - HttpEvent, - HttpHandler, - HttpInterceptor, - HttpRequest, - HttpResponse -} from '@angular/common/http'; -import {catchError, map, throwError} from 'rxjs'; - -import {AppService} from "@services/app.service"; - -export class HttpError { - static BadRequest = 400; - static Unauthorized = 401; - static Forbidden = 403; - static NotFound = 404; - static TimeOut = 408; - static Conflict = 409; - static InternalServerError = 500; -} - -@Injectable() -export class HTTPInterceptor implements HttpInterceptor { - constructor( - private app: AppService - ) { - } - - intercept(request: HttpRequest, next: HttpHandler) { - return next.handle(request).pipe( - map((event: HttpEvent) => { - if (event instanceof HttpResponse) { - if (event.body) { - if (event.body.message) { - if (event.body.message.charAt(0) !== "$") - this.app.notify(event.body.title || "", event.body.message, event.body.done); - } else if (event.body.auth_error) { - this.app.notify(event.body.title, event.body.auth_error, false); - } - } - } - return event; - }), - catchError((error: HttpErrorResponse) => { - this.showError(error.status, error.error?.message || ''); - return throwError(() => error); - }) - ); - } - - private showError(status: number, message = '') { - switch (status) { - case HttpError.BadRequest: - this.app.notify('Bad Request 400', message, false); - break; - - case HttpError.Unauthorized: - this.app.notify('Unauthorized 401', message, false); - break; - - case HttpError.NotFound: - this.app.notify('Not Found 404', message, false); - break; - - case HttpError.TimeOut: - this.app.notify('TimeOut 408', message, false); - break; - - case HttpError.Forbidden: - this.app.notify('Forbidden 403', message, false); - break; - - case HttpError.InternalServerError: - this.app.notify('Internal Server Error 500', message, false); - break; - } - } -} diff --git a/worklenz-frontend/src/app/interfaces/account-center.ts b/worklenz-frontend/src/app/interfaces/account-center.ts deleted file mode 100644 index 4a8ebc4e..00000000 --- a/worklenz-frontend/src/app/interfaces/account-center.ts +++ /dev/null @@ -1,193 +0,0 @@ -export interface IOrganization { - name?: string; - owner_name?: string; - email?: string; - contact_number?: string; - contact_number_secondary?: string; -} - -export interface IOrganizationAdmin { - name?: string; - email?: string; - is_owner?: boolean; -} - -export interface IOrganizationUser { - name?: string; - email?: string; - is_owner?: boolean; - is_admin?: boolean; - color_code?: string; - avatar_url?: string; - last_logged?: string; - user_id?: string; -} - -export interface IOrganizationTeamMember { - id?: string; - user_id?: string; - name?: string; - avatar_url?: string; - color_code?: string; - email?: string; - role_id?: string; - role_name?: string; - created_at?: string; -} - -export interface IOrganizationTeam { - id?: string; - name?: string; - created_at?: string; - members_count?: number; - names?: any; - team_members?: IOrganizationTeamMember[]; -} - -export enum ISubscriptionStatus { - DELETED = "deleted", - PAUSED = "paused", - ACTIVE = "active", - PASTDUE = "past_due", - TRIALING = "trialing", - LIFE_TIME_DEAL = "life_time_deal" -} - - -export interface IBillingAccountInfo { - billing_type?: string; - cancel_url?: string; - cancellation_effective_date?: string; - contact_no?: string; - contact_number_secondary?: string; - default_currency?: string; - default_storage?: number; - default_trial_storage?: number; - email?: string; - expire_date_string?: string; - name?: string; - paused_from?: string; - plan_name?: string; - plan_id?: string; - remainingStorage?: number; - status?: ISubscriptionStatus; - trial_in_progress?: boolean; - trial_expire_date?: string; - valid_till_date?: string; - unit_price?: number; - unit_price_per_month?: number; - usedPercentage?: number; - usedStorage?: number; - is_custom?: boolean; - is_ltd_user?: boolean; - ltd_users?: number; -} - -export interface IPricingPlans { - monthly_plan_id?: string | null; - monthly_plan_name?: string; - annual_plan_id?: string | null; - annual_plan_name?: string; - team_member_limit?: string; - projects_limit?: string; - free_tier_storage?: string; -} - -export interface IPaddleCheckoutParams { - custom_message?: string; - customer_country?: string; - customer_email?: string; - customer_postcode?: string; - discountable?: number; - expires?: string; - image_url?: string; - is_recoverable?: number; - marketing_consent?: number; - passthrough?: string; - prices?: Array; - product_id?: number; - quantity_variable?: number; - quantity?: number; - recurring_prices?: string; - return_url?: string; - title?: string; - trial_days?: number; - vat_company_name?: string; - vat_number?: string; - webhook_url?: string; - coupon_code?: string; - vat_street?: string; -} - -export interface IUpgradeSubscriptionPlanResponse { - params: IPaddleCheckoutParams; - sandbox: boolean; - vendor_id: string; -} - -export interface IBillingConfiguration { - name?: string; - email?: string; - organization_name?: string; - contact_number?: string; - address_line_1?: string; - address_line_2?: string; - city?: string; - state?: string; - postal_code?: string; - country?: string; -} - -export interface IBillingTransaction { - subscription_payment_id?: string; - event_time?: string; - next_bill_date?: string; - currency?: string; - receipt_url?: string; - payment_method?: string; - payment_status?: string; -} - -export interface IStorageInfo { - storage?: string; - default_trial_storage?: string; - plan_name?: string; - trial_expire_date?: string; - trial_in_progress?: boolean; - storage_addon_price?: string; - storage_addon_size?: string; -} - -export interface IBillingCharge { - amount?: number; - end_date?: string; - name?: string; - quantity?: number; - start_date?: string; - status?: string; - unit_price?: number; - currency?: string; -} - -export interface IBillingAccountStorage { - used?: number; - total?: number; - used_percent?: number; - remaining?: number; -} - -export interface IBillingConfigurationCountry { - id?: string; - name: string; - code?: string; -} - -export interface IBillingModifier { - subscription_id?: string; - created_at?: string; -} - -export interface IBillingChargesResponse { - plan_charges?: IBillingCharge[]; - modifiers?: IBillingModifier[]; -} diff --git a/worklenz-frontend/src/app/interfaces/allocation-view-model.ts b/worklenz-frontend/src/app/interfaces/allocation-view-model.ts deleted file mode 100644 index d4842ed5..00000000 --- a/worklenz-frontend/src/app/interfaces/allocation-view-model.ts +++ /dev/null @@ -1,17 +0,0 @@ -export interface IAllocationProject { - name?: string; - color_code?: string; - status_name?: string; - status_color_code?: string; - status_icon?: string; - all_tasks_count?: number; - completed_tasks_count?: number; - progress?: number; - total?: string; - time_logs?: Array<{ time_logged: string; }>; -} - -export interface IAllocationViewModel { - projects: Array; - users: Array<{ name: string; }>; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/activity-log.ts b/worklenz-frontend/src/app/interfaces/api-models/activity-log.ts deleted file mode 100644 index 73c34d48..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/activity-log.ts +++ /dev/null @@ -1,5 +0,0 @@ -import {IActivityLog} from '@interfaces/personal-overview'; - -export interface IActivityLogGetRequest extends IActivityLog { - -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/activity-logs-get-response.ts b/worklenz-frontend/src/app/interfaces/api-models/activity-logs-get-response.ts deleted file mode 100644 index 2a148128..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/activity-logs-get-response.ts +++ /dev/null @@ -1,59 +0,0 @@ -export interface IActivityLogsUser { - avatar_url?: string; - color_code?: string; - name?: string; - user_id?: string; -} - -export interface IActivityLogsLabel { - color_code?: string; - name?: string; -} - -export interface IActivityLogsStatus { - color_code?: string; - name?: string; -} - -export enum IActivityLogAttributeTypes { - NAME = "name", - STATUS = "status", - ASSIGNEES = "assignee", - END_DATE = "end_date", - START_DATE = "start_date", - PRIORITY = "priority", - PHASE = "phase", - ESTIMATION = "estimation", - LABEL = "label", - DESCRIPTION = "description", - ATTACHMENT = "attachment", - COMMENT = "comment", - ARCHIVE = "archive", -} - -export interface IActivityLog { - attribute_type?: string; - created_at?: string; - current?: string; - log_text?: string; - log_type?: string; - previous?: string; - task_id?: string; - done_by?: IActivityLogsUser; - assigned_user?: IActivityLogsUser; - label_data?: IActivityLogsLabel; - previous_status?: IActivityLogsLabel; - next_status?: IActivityLogsLabel; - previous_priority?: IActivityLogsLabel; - next_priority?: IActivityLogsLabel; - previous_phase: IActivityLogsLabel; - next_phase: IActivityLogsLabel; -} - -export interface IActivityLogsResponse { - avatar_url?: string; - color_code?: string; - created_at?: string; - logs?: IActivityLog[]; - name?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/authorize-response.ts b/worklenz-frontend/src/app/interfaces/api-models/authorize-response.ts deleted file mode 100644 index ee4acaa8..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/authorize-response.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {IUser} from "../user"; - -export interface IAuthorizeResponse { - authenticated: boolean; - user: IUser; - auth_error: string; - message: string; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/bulk-assign-request.ts b/worklenz-frontend/src/app/interfaces/api-models/bulk-assign-request.ts deleted file mode 100644 index 39e26348..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/bulk-assign-request.ts +++ /dev/null @@ -1,12 +0,0 @@ -import {ITaskAssignee} from "@interfaces/task-assignee"; - -export interface IBulkAssignRequest { - tasks: string[]; - project_id: string; -} - -export interface IBulkAssignMembersRequest { - tasks: string[]; - project_id: string; - members: ITaskAssignee[]; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/bulk-delete-tasks-request.ts b/worklenz-frontend/src/app/interfaces/api-models/bulk-delete-tasks-request.ts deleted file mode 100644 index 511af59b..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/bulk-delete-tasks-request.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface BulkDeleteTasksRequest { -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-archive-request.ts b/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-archive-request.ts deleted file mode 100644 index cd719ce6..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-archive-request.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface IBulkTasksArchiveRequest { - tasks: string[]; - project_id: string; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-delete-request.ts b/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-delete-request.ts deleted file mode 100644 index 335cc30e..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-delete-request.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface IBulkTasksDeleteRequest { - tasks: string[]; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-delete-response.ts b/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-delete-response.ts deleted file mode 100644 index 3c1606ea..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-delete-response.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface IBulkTasksDeleteResponse { - deleted_tasks: string[]; - counts?: { - total_tasks: number; - total_completed: number; - } -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-labels-request.ts b/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-labels-request.ts deleted file mode 100644 index 282f2032..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-labels-request.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {ITaskLabel} from "@interfaces/task-label"; - -export interface IBulkTasksLabelsRequest { - tasks: string[]; - text: string | null; - labels: ITaskLabel[]; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-phase-change-request.ts b/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-phase-change-request.ts deleted file mode 100644 index 2ab4a2bc..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-phase-change-request.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface IBulkTasksPhaseChangeRequest { - tasks: string[]; - phase_id: string; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-priority-change-request.ts b/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-priority-change-request.ts deleted file mode 100644 index 90d5b79f..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-priority-change-request.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface IBulkTasksPriorityChangeRequest { - tasks: string[]; - priority_id: string; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-status-change-request.ts b/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-status-change-request.ts deleted file mode 100644 index 92004a0d..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/bulk-tasks-status-change-request.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface IBulkTasksStatusChangeRequest { - tasks: string[]; - status_id: string; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/client-view-model.ts b/worklenz-frontend/src/app/interfaces/api-models/client-view-model.ts deleted file mode 100644 index a0da8d35..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/client-view-model.ts +++ /dev/null @@ -1,5 +0,0 @@ -import {IClient} from "../client"; - -export interface IClientViewModel extends IClient { - projects_count?: number; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/clients-view-model.ts b/worklenz-frontend/src/app/interfaces/api-models/clients-view-model.ts deleted file mode 100644 index d902eb82..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/clients-view-model.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {IClientViewModel} from "@interfaces/api-models/client-view-model"; - -export interface IClientsViewModel { - total?: number; - data?: IClientViewModel[]; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/gantt.ts b/worklenz-frontend/src/app/interfaces/api-models/gantt.ts deleted file mode 100644 index e5ee345c..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/gantt.ts +++ /dev/null @@ -1,57 +0,0 @@ -// import {ActionCompleteArgs, IGanttData} from "@syncfusion/ej2-angular-gantt"; -// -// export interface IGanttRoadMapTask { -// EndDate: string | null -// StartDate: string | null; -// subtasks: Subtask[]; -// TaskID: string; -// TaskName: string; -// } -// -// export interface Subtask { -// Duration: number; -// EndDate: string | null; -// Progress: number; -// StartDate: string | null; -// TaskID: number; -// TaskName: string; -// } -// -// export interface IGanttRow extends IGanttData { -// TaskID?: string; -// project_member?: boolean; -// } -// -// export interface IActionCompletedExt extends ActionCompleteArgs { -// dropIndex?: number; -// dropRecord: any; -// fromIndex: number; -// taskBarEditAction?: string; -// } -// -// export interface IEventMarker { -// label?: string; -// day?: Date | string; -// } -// -// export enum IGanttActions { -// CELL_EDITING = 'CellEditing', -// TASKBAR_EDITING = 'TaskbarEditing' -// } -// -// export enum IGanttRequestTypes { -// SAVE = 'save', -// BEFORE_OPEN_ADD_DIALOG = 'beforeOpenAddDialog', -// BEFORE_OPEN_EDIT_DIALOG = 'beforeOpenEditDialog', -// ROW_DROPPED = 'rowDropped' -// } -// -// export enum ITaskbarEditActions { -// RIGHT_RESIZE = 'RightResizing', -// LEFT_RESIZE = 'LeftResizing', -// CHILD_DRAG = 'ChildDrag' -// } -// -// export interface IProjectPhaseLabel { -// phase_label?: string | null; -// } diff --git a/worklenz-frontend/src/app/interfaces/api-models/inline-member.ts b/worklenz-frontend/src/app/interfaces/api-models/inline-member.ts deleted file mode 100644 index 7212b8bf..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/inline-member.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface InlineMember { - name: string; - end?: boolean; - names?: string; - color_code?: string; - avatar_url?: string; - team_member_id?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/job-titles-view-model.ts b/worklenz-frontend/src/app/interfaces/api-models/job-titles-view-model.ts deleted file mode 100644 index eed05058..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/job-titles-view-model.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {IJobTitle} from "@interfaces/job-title"; - -export interface IJobTitlesViewModel { - total?: number; - data?: IJobTitle[]; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/local-session.ts b/worklenz-frontend/src/app/interfaces/api-models/local-session.ts deleted file mode 100644 index 8e557347..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/local-session.ts +++ /dev/null @@ -1,28 +0,0 @@ -import {IUser} from "../user"; - -export interface WorklenzAlert { - description: string; - type: "success" | "info" | "warning" | "error"; -} - -export interface ILocalSession extends IUser { - team_id?: string; - team_name?: string; - owner?: boolean; - demo_data?: boolean; - is_admin?: boolean; - is_member?: boolean; - build_v?: string; - is_google?: boolean; - setup_completed?: boolean; - my_setup_completed?: boolean; - timezone?: string; - timezone_name?: string; - avatar_url?: string; - joined_date?: string; - last_updated?: string; - user_no?: number; - team_member_id?: string; - alerts?: Array; - is_expired?: boolean; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/my-dashboard-all-tasks-view-model.ts b/worklenz-frontend/src/app/interfaces/api-models/my-dashboard-all-tasks-view-model.ts deleted file mode 100644 index bbc7296a..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/my-dashboard-all-tasks-view-model.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {InlineMember} from "@interfaces/api-models/inline-member"; - -export interface IMyDashboardProjectTask { - id: string; - name?: string; - project_name?: string; - project_color?: string; - status_color?: string; - project_id?: string; - status?: string; - start_date?: string; - end_date?: string; - done?: boolean; - names?: InlineMember[]; -} - -export interface IMyDashboardAllTasksViewModel { - total?: number; - data?: IMyDashboardProjectTask[]; -} - -export interface IMyDashboardMyTask { - id: string; - name?: string; - project_name?: string; - project_color?: string; - status_color?: string; - project_id?: string; - status?: string; - start_date?: string; - end_date?: string; - names?: InlineMember[]; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/organization-team-get-request.ts b/worklenz-frontend/src/app/interfaces/api-models/organization-team-get-request.ts deleted file mode 100644 index e130aba6..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/organization-team-get-request.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {IOrganizationTeam} from "@interfaces/account-center"; - -export interface IOrganizationTeamGetRequest { - total?: number; - data?: IOrganizationTeam[]; - current_team_data?: IOrganizationTeam -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/organization-users-get-request.ts b/worklenz-frontend/src/app/interfaces/api-models/organization-users-get-request.ts deleted file mode 100644 index 4338701e..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/organization-users-get-request.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {IOrganizationUser} from "@interfaces/account-center"; - -export interface IOrganizationUsersGetRequest { - total?: number; - data?: IOrganizationUser[]; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/project-comment-create-request.ts b/worklenz-frontend/src/app/interfaces/api-models/project-comment-create-request.ts deleted file mode 100644 index 8b4657e8..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/project-comment-create-request.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {IMentionMember} from "@interfaces/project-comments"; - -export interface IProjectCommentsCreateRequest { - project_id?: string; - project_name?:string; - team_id?: string; - team_name?: string; - content?: string; - mentions?: IMentionMember[]; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/project-create-request.ts b/worklenz-frontend/src/app/interfaces/api-models/project-create-request.ts deleted file mode 100644 index d1dc9d09..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/project-create-request.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {IProject} from "../project"; - -export interface IProjectCreateRequest extends IProject { - client_name?: string | null; - status_id?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/project-insights.ts b/worklenz-frontend/src/app/interfaces/api-models/project-insights.ts deleted file mode 100644 index a49bf96b..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/project-insights.ts +++ /dev/null @@ -1,54 +0,0 @@ -export interface IProjectInsightsGetRequest { - total_tasks_count?: number; - archived_tasks_count?: number; - sub_tasks_count?: number; - completed_tasks_count?: number; - pending_tasks_count?: number; - todo_tasks_count?: number; - last_week_count?: number; - overdue_count?: number; - todo_tasks_color_code?: string; - pending_tasks_color_code?: string; - completed_tasks_color_code?: string; - total_estimated_hours_string?: string; - total_logged_hours_string?: string; - total_estimated_hours?: number; - total_logged_hours?: number; - overlogged_hours?: string; -} - -export interface IProjectLogs { - description?: string; - created_at?: string; -} - -export interface IProjectMemberStats { - total_members_count?: number; - unassigned_members?: number; - overdue_members?: number; -} - -export interface IInsightTasks { - id?: string; - name?: string; - start_date?: string; - end_date?: string; - completed_at?: string; - status?: string; - status_id?: string; - status_color?: string; - updated_at?: string; - total_minutes?: string; - overlogged_time?: string; - days_overdue?: number; - is_overdue?: boolean; - parent_task_id?: string; -} - -export interface IDeadlineTaskStats { - deadline_tasks_count?: number; - deadline_logged_hours?: number; - deadline_logged_hours_string?: string; - project_end_date?: string; - tasks?: IInsightTasks[]; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/project-members-view-model.ts b/worklenz-frontend/src/app/interfaces/api-models/project-members-view-model.ts deleted file mode 100644 index 512775fe..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/project-members-view-model.ts +++ /dev/null @@ -1,18 +0,0 @@ -export interface IProjectMemberViewModel { - id?: string; - name?: string; - email?: string; - access?: string; - pending_invitation?: boolean; - all_tasks_count?: number; - completed_tasks_count?: number; - progress?: number; - job_title?: string; - avatar_url?: string; - team_member_id?: string; -} - -export interface IProjectMembersViewModel { - total?: number; - data?: IProjectMemberViewModel[]; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/project-tasks-view-model.ts b/worklenz-frontend/src/app/interfaces/api-models/project-tasks-view-model.ts deleted file mode 100644 index e94e5af7..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/project-tasks-view-model.ts +++ /dev/null @@ -1,89 +0,0 @@ -import {InlineMember} from "@interfaces/api-models/inline-member"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; -import {ITaskLabel} from "@interfaces/task-label"; - -export interface ITaskAssignee { - team_member_id: any; - id: string; - project_member_id: string; - name: string -} - -export interface ITaskStatusCategory { - is_todo?: boolean; - is_doing?: boolean; - is_done?: boolean; -} - -export interface IProjectTask { - id?: string; - name?: string; - task_key?: string; - assignees?: ITaskAssignee[]; - names?: InlineMember[]; - reporter?: string; - status?: string; - status_color?: string; - priority?: string; - start_date?: string; - end_date?: string; - total_hours?: number; - total_minutes?: number; - total_minutes_spent?: number; - progress?: number; - overdue?: boolean; - time_spent_string?: string; - comments_count?: number; - has_subscribers?: boolean; - attachments_count?: number; - status_name?: string; - total_time_string?: string; - due_in?: string; - time_spent?: { hours?: number, minutes?: number }; - project_id?: string; - project_name?: string; - updated_at?: string; - name_color?: string; - sub_tasks_count?: number; - total_tasks_count?: number; - is_sub_task?: boolean; - parent_task_name?: string; - parent_task_id?: string; - show_handles?: boolean; - min?: number; - max?: number; - sort_order?: number; - color_code?: string; - priority_color?: string; - show_sub_tasks?: boolean; - sub_tasks?: IProjectTask[]; - sub_tasks_loading?: boolean; - statuses?: ITaskStatusViewModel[]; - labels?: ITaskLabel[]; - all_labels?: ITaskLabel[]; - archived?: boolean; - complete_ratio?: number; - completed_count?: number; - priority_value?: number; - is_overdue?: boolean; - timer_start_time?: number; - description?: string; - completed_at?: string; - created_at?: string; - phase_id?: string; - phase_name?: string; - phase_color?: string; - priority_name?: string; - status_category?: ITaskStatusCategory; - overdue_days?: string | null; - overlogged_time_string?: string; - offset_from?: number; - width?: number; - isVisible?: boolean, - estimated_string?: string -} - -export interface IProjectTasksViewModel { - total?: number; - data?: IProjectTask[]; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/project-template.ts b/worklenz-frontend/src/app/interfaces/api-models/project-template.ts deleted file mode 100644 index 1c9cf4df..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/project-template.ts +++ /dev/null @@ -1,45 +0,0 @@ -export interface IWorklenzTemplate { - id?: string; - name?: string; -} - -interface IPhase { - name?: string; - color_code?: string; -} - -interface IStatus { - name?: string; - color_code?: string; -} - -interface IPriority { - name?: string; - color_code?: string; -} - -interface ILabel { - name?: string; - color_code?: string; -} - -interface ITemplateTask { - name?: string; -} - -export interface IProjectTemplate { - image_url?: string; - description?: string; - phases?: IPhase[]; - status?: IStatus[]; - priorities?: IPriority[]; - labels?: ILabel[]; - tasks?: ITemplateTask[]; -} - -export interface ICustomTemplate { - id?: string; - name?: string; - color_code?: string; - selected?: boolean; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/project-view-model.ts b/worklenz-frontend/src/app/interfaces/api-models/project-view-model.ts deleted file mode 100644 index 17da18b6..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/project-view-model.ts +++ /dev/null @@ -1,43 +0,0 @@ -import {IProject} from "@interfaces/project"; -import {ITeamMemberViewModel} from "@interfaces/api-models/team-members-get-response"; -import {ITask} from "@interfaces/task"; -import {InlineMember} from "@interfaces/api-models/inline-member"; - -export interface IProjectViewModel extends IProject { - client_name?: string; - project_owner?: string; - updated_at?: string; - updated_at_string?: string; - status?: string; - status_color?: string; - status_icon?: string; - start_date?: string; - end_date?: string; - phase_label?: string; - category_name?: string; - category_color?: string; - category_id?: string; - - task_count?: number; - members_count?: number; - progress?: number; - all_tasks_count?: number; - completed_tasks_count?: number; - - favorite?: boolean; - archived?: boolean; - loading?: boolean; - collapsed?: boolean; - subscribed?: boolean; - - members?: ITeamMemberViewModel[]; - owner?: ITeamMemberViewModel | null; - tasks?: ITask[]; - names?: Array; - - project_manager?: ITeamMemberViewModel | null; - project_manager_id?: string | null; - - team_member_default_view? : string; - -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/projects-get-response.ts b/worklenz-frontend/src/app/interfaces/api-models/projects-get-response.ts deleted file mode 100644 index 3795e8e9..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/projects-get-response.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {IProject} from '../project'; -import {ITeamMemberViewModel} from './team-members-get-response'; -import {ITask} from '../task'; -import {InlineMember} from "@interfaces/api-models/inline-member"; - -export interface IProjectsOverviewGetResponse extends IProject { - client_name?: string; - project_owner?: string; - task_count?: number; - members_count?: number; - done_task_count?: number; - pending_task_count?: number; - progress?: number; - all_tasks_count?: number; - completed_tasks_count?: number; - members?: ITeamMemberViewModel[]; - tasks?: ITask[]; - names?: Array; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/projects-view-model.ts b/worklenz-frontend/src/app/interfaces/api-models/projects-view-model.ts deleted file mode 100644 index cbd2eb83..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/projects-view-model.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {IProjectViewModel} from "@interfaces/api-models/project-view-model"; - -export interface IProjectsViewModel { - total?: number; - data?: IProjectViewModel[] -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/reporting.ts b/worklenz-frontend/src/app/interfaces/api-models/reporting.ts deleted file mode 100644 index f6e63457..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/reporting.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - IReportingMemberCurrentlyDoing, - IReportingMemberOverdueTask, - IReportingMemberProjectTaskLate, - IReportingMemberRecentLogged, - IReportingProjectsCustom -} from "@interfaces/reporting"; - -export interface IReportingProjectsCustomGetResponse { - total?: number; - data?: IReportingProjectsCustom[] -} - -export interface IReportingMemberOverviewGetRequest { - logged_tasks?: IReportingMemberRecentLogged[]; - current_tasks?: IReportingMemberCurrentlyDoing[]; - overdue_tasks?: IReportingMemberOverdueTask[]; -} - -export interface IReportingMemberProjectResponse { - overloggedTasks: IReportingMemberProjectTaskLate[]; - earlyTasks: IReportingMemberProjectTaskLate[]; - lateTasks: IReportingMemberProjectTaskLate[]; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/reset-password-request.ts b/worklenz-frontend/src/app/interfaces/api-models/reset-password-request.ts deleted file mode 100644 index 532f573d..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/reset-password-request.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface IResetPasswordRequest { - email?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/task-attachment-view-model.ts b/worklenz-frontend/src/app/interfaces/api-models/task-attachment-view-model.ts deleted file mode 100644 index 4c9cea7f..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/task-attachment-view-model.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface ITaskAttachmentViewModel { - id?: string; - name?: string; - url?: string; - size?: string; - type?: string; - created_at?: string; - task_name?: string; - task_key?: string; - uploader_name?: string; -} - -export interface IProjectAttachmentsViewModel { - total?: number; - data?: ITaskAttachmentViewModel[]; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/task-attachment.ts b/worklenz-frontend/src/app/interfaces/api-models/task-attachment.ts deleted file mode 100644 index c6ce5700..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/task-attachment.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface ITaskAttachment { - /** Base64 string */ - file: string; - file_name: string; - project_id: string; - size: number; - task_id?: string | null; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/task-comment-view-model.ts b/worklenz-frontend/src/app/interfaces/api-models/task-comment-view-model.ts deleted file mode 100644 index b3d3178f..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/task-comment-view-model.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {ITaskComment} from "@interfaces/task-comment"; - -export interface ITaskCommentViewModel extends ITaskComment { - is_edited?: boolean; - member_name?: string; - team_member_id?: string; - avatar_url?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/task-comments-create-request.ts.ts b/worklenz-frontend/src/app/interfaces/api-models/task-comments-create-request.ts.ts deleted file mode 100644 index 7bf90cb9..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/task-comments-create-request.ts.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface ITaskCommentsCreateRequest { - task_id?: string; - content?: string; - mentions?: any[]; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/task-create-request.ts b/worklenz-frontend/src/app/interfaces/api-models/task-create-request.ts deleted file mode 100644 index 606be2df..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/task-create-request.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {ITask} from '../task'; - -export interface ITaskCreateRequest extends ITask { - status_id?: string; - project_id?: string; - task_index?: number; - attachments?: string[]; - labels?: string[]; - parent_task_id?: string; - reporter_id?: string; - team_id?: string; - priority_id?: string; - phase_id?: string; - chart_start?: string; - offset?: number; - width?: number; - is_dragged?: boolean; -} - -export interface IHomeTaskCreateRequest extends ITask { - name: string; - project_id?: string; - reporter_id?: string; - team_id?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/task-get-response.ts b/worklenz-frontend/src/app/interfaces/api-models/task-get-response.ts deleted file mode 100644 index 9fa06061..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/task-get-response.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ITask} from '@interfaces/task'; -import {IGanttChartTasks, IGanttDateRange, IGanttMonthRange, IGanttWeekRange} from '@interfaces/gantt-chart'; - -export interface ITaskGetRequest extends ITask { - start: string; - end: string; - progress: number; - priority: string; -} - -export interface ITaskByRangeGetRequest extends ITask { - dates?: IGanttDateRange[], - weeks?: IGanttWeekRange[], - months?: IGanttMonthRange[], - tasks?: IGanttChartTasks[] -} - -export interface IProjectRoadmapGetRequest extends ITask { - dates?: IGanttDateRange[], - weeks?: IGanttWeekRange[], - months?: IGanttMonthRange[], - tasks?: ITask[] -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/task-list-config.ts b/worklenz-frontend/src/app/interfaces/api-models/task-list-config.ts deleted file mode 100644 index 452fab05..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/task-list-config.ts +++ /dev/null @@ -1,18 +0,0 @@ -export interface ITaskListConfig { - id: string; - index: number; - size: number; - field: string | null; - order: string | null; - search: string | null; - statuses: string | null; - members: string | null; - projects: string | null; - labels?: string | null; - priorities?: string | null; - filterBy?: string | null; - archived?: boolean; - paginate?: boolean; - count?: boolean; - parent_task?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/task-priorities-get-response.ts b/worklenz-frontend/src/app/interfaces/api-models/task-priorities-get-response.ts deleted file mode 100644 index e12033e8..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/task-priorities-get-response.ts +++ /dev/null @@ -1,5 +0,0 @@ -import {ITaskPriority} from '../task-priority'; - -export interface ITaskPrioritiesGetResponse extends ITaskPriority { - color_code?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/task-status-get-response.ts b/worklenz-frontend/src/app/interfaces/api-models/task-status-get-response.ts deleted file mode 100644 index a2c8cb1b..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/task-status-get-response.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {ITaskStatus} from '@interfaces/task-status'; - -export interface ITaskStatusViewModel extends ITaskStatus { - category_id?: string; - category_name?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/task-template-get-response.ts b/worklenz-frontend/src/app/interfaces/api-models/task-template-get-response.ts deleted file mode 100644 index 4cefb115..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/task-template-get-response.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; - -export interface ITaskTemplateGetResponse { - id?: string; - name?: string; - tasks?: IProjectTask[] -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/task-templates-get-response.ts b/worklenz-frontend/src/app/interfaces/api-models/task-templates-get-response.ts deleted file mode 100644 index d0c43c9b..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/task-templates-get-response.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface ITaskTemplatesGetResponse { - name?: string; - id?: string; - created_at?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/team-activate-response.ts b/worklenz-frontend/src/app/interfaces/api-models/team-activate-response.ts deleted file mode 100644 index 903062a4..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/team-activate-response.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface ITeamActivateResponse { - subdomain: string; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/team-get-response.ts b/worklenz-frontend/src/app/interfaces/api-models/team-get-response.ts deleted file mode 100644 index 8b71df1f..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/team-get-response.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {ITeam} from "@interfaces/team"; - -export interface ITeamGetResponse extends ITeam { - owner?: boolean; - owns_by?: string; - created_at?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/team-invitation-view-model.ts b/worklenz-frontend/src/app/interfaces/api-models/team-invitation-view-model.ts deleted file mode 100644 index 8ff30f96..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/team-invitation-view-model.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {ITeamInvites} from "@interfaces/team"; - -export interface ITeamInvitationViewModel extends ITeamInvites { - accepting?: boolean; - joining?: boolean; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/team-member-create-request.ts b/worklenz-frontend/src/app/interfaces/api-models/team-member-create-request.ts deleted file mode 100644 index a5b3f415..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/team-member-create-request.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {ITeamMember} from "../team-member"; - -export interface ITeamMemberCreateRequest extends ITeamMember { - job_title?: string | null; - emails?: string | string []; - is_admin?: boolean; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/team-members-get-response.ts b/worklenz-frontend/src/app/interfaces/api-models/team-members-get-response.ts deleted file mode 100644 index 569036d9..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/team-members-get-response.ts +++ /dev/null @@ -1,78 +0,0 @@ -import {ITeamMember} from '../team-member'; -import {ITask} from '@interfaces/task'; -import {IInsightTasks} from "@interfaces/api-models/project-insights"; -import {InlineMember} from "@interfaces/api-models/inline-member"; - -export interface ITeamMemberViewModel extends ITeamMember { - id?: string; - active?: boolean; - name?: string; - taskCount?: number; - job_title?: string; - email?: string; - task_count?: number; - projects_count?: number; - role_name?: string; - tasks?: ITask[]; - is_admin?: boolean; - show_handles?: boolean; - is_online?: boolean; - avatar_url?: string; - selected?: boolean; - color_code?: string; - usage?: number; - projects?: any; - total_logged_time?: string; - member_teams?: string[]; - is_pending?: boolean; -} - -export interface ITeamMemberOverviewGetResponse extends ITeamMember { - task_count?: number; - done_task_count?: number; - pending_task_count?: number; - overdue_task_count?: number; - progress?: number; - contribution?: number; - job_title?: string; - id: string; - name?: string; - tasks?: IInsightTasks[]; -} - -export interface ITeamMemberOverviewByProjectGetResponse { - name?: string; - assigned_task_count?: number; - progress?: number; - done_task_count?: number; - pending_task_count?: number; - id?: string; -} - -export interface ITeamMemberOverviewChartGetResponse { - pending_count?: number; - done_count?: number; -} - -export interface ITeamMemberFilterResponse { - text?: string; - value?: string; -} - -export interface ITeamMemberTreeMapResponse { - total: number; - data: ITeamMemberTreeMap[] -} - -export interface ITeamMemberTreeMap { - value?: number; - name?: string; - parent?: number; - id?: string; - color?: string; -} - -export interface ITasksByTeamMembers { - name?: string; - task_count?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/team-members-view-model.ts b/worklenz-frontend/src/app/interfaces/api-models/team-members-view-model.ts deleted file mode 100644 index 7afc972c..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/team-members-view-model.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {ITeamMemberViewModel} from "@interfaces/api-models/team-members-get-response"; - -export interface ITeamMembersViewModel { - data?: ITeamMemberViewModel[]; - total?: number; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/team-view-model.ts b/worklenz-frontend/src/app/interfaces/api-models/team-view-model.ts deleted file mode 100644 index f3223e47..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/team-view-model.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {ITeam} from "../team"; - -export interface ITeamViewModel extends ITeam { - all_tasks?: number; - completed_tasks?: number; - owns_by?: string; - loading?: boolean; - owner?: boolean; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/user-login-request.ts b/worklenz-frontend/src/app/interfaces/api-models/user-login-request.ts deleted file mode 100644 index 2ac53769..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/user-login-request.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {IUser} from "../user"; - -export interface IUserLoginRequest extends IUser { - team_id?: string; - project_id?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/user-login-response.ts b/worklenz-frontend/src/app/interfaces/api-models/user-login-response.ts deleted file mode 100644 index 636f6355..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/user-login-response.ts +++ /dev/null @@ -1,4 +0,0 @@ -import {IUser} from "../user"; - -export interface IUserLoginResponse extends IUser { -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/user-sign-up-response.ts b/worklenz-frontend/src/app/interfaces/api-models/user-sign-up-response.ts deleted file mode 100644 index 74b6d01c..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/user-sign-up-response.ts +++ /dev/null @@ -1,4 +0,0 @@ -import {IUser} from "../user"; - -export interface IUserSignUpResponse extends IUser { -} diff --git a/worklenz-frontend/src/app/interfaces/api-models/verify-reset-email.ts b/worklenz-frontend/src/app/interfaces/api-models/verify-reset-email.ts deleted file mode 100644 index bccc53cc..00000000 --- a/worklenz-frontend/src/app/interfaces/api-models/verify-reset-email.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface IUpdatePasswordRequest { - password?: string; - user?: string; - hash?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/client.ts b/worklenz-frontend/src/app/interfaces/client.ts deleted file mode 100644 index 57b195d6..00000000 --- a/worklenz-frontend/src/app/interfaces/client.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface IClient { - id?: string; - name?: string; - team_id?: string; - created_at?: string; - updated_at?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/gantt-chart.ts b/worklenz-frontend/src/app/interfaces/gantt-chart.ts deleted file mode 100644 index 2e9769b7..00000000 --- a/worklenz-frontend/src/app/interfaces/gantt-chart.ts +++ /dev/null @@ -1,69 +0,0 @@ -import {ITask} from '@interfaces/task'; - -export enum IGanttChartMargins { - LEFT = 200, - RIGHT = 100 -} - -export enum EGanttColumnWidth { - DAYS = 50, - WEEKS = 40, - MONTHS = 30 -} - -export enum EResourceGanttColumn { - WEEKS = 30, - MONTHS = 15, - QUARTERS = 10 -} - -export enum EGanttChartTypes { - RESOURCE, PROJECT -} - -export enum EGanttViewModes { - DAYS, WEEKS, MONTHS -} - -export enum EResourceGanttViewModes { - WEEKS, MONTHS, QUARTERS -} - -export interface IProjectMemberGantt { - id?: string; - team_member_id?: string; - project_access_level_id?: string; - job_title?: string; - name?: string; - access_level?: string; - tasks?: ITask[]; -} - -export interface IGanttDateRange { - isSunday?: boolean; - isToday?: boolean; - isWeekend?: boolean; - isLastDayOfWeek?: boolean; - isLastDayOfMonth?: boolean; - date?: Date; -} - -export interface IGanttWeekRange { - max?: number; - min?: number; - month_name?: string; - week_index?: number; - days_of_week?: IGanttDateRange[]; -} - -export interface IGanttMonthRange { - max?: number; - min?: number; - month_name?: string; - month_index?: number; - days_of_month?: IGanttDateRange[]; -} - -export interface IGanttChartTasks { - [id: string]: ITask -} diff --git a/worklenz-frontend/src/app/interfaces/invitation-response.ts b/worklenz-frontend/src/app/interfaces/invitation-response.ts deleted file mode 100644 index 517baf05..00000000 --- a/worklenz-frontend/src/app/interfaces/invitation-response.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface IInvitationResponse { - message: string; - team: string; // team name - team_id: string; -} diff --git a/worklenz-frontend/src/app/interfaces/job-title.ts b/worklenz-frontend/src/app/interfaces/job-title.ts deleted file mode 100644 index 3ec9b3ca..00000000 --- a/worklenz-frontend/src/app/interfaces/job-title.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface IJobTitle { - id?: string; - name?: string; - team_id?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/my-tasks.ts b/worklenz-frontend/src/app/interfaces/my-tasks.ts deleted file mode 100644 index 26728d45..00000000 --- a/worklenz-frontend/src/app/interfaces/my-tasks.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {ITaskStatusViewModel} from "@interfaces/api-models/task-status-get-response"; - -export interface IMyTask extends IProjectTask { - is_task: boolean; - done: boolean - project_color?: string; - project_name?: string; - team_id?: string; - status_color?: string; - project_statuses?: ITaskStatusViewModel[]; - parent_task_name?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/nav-item-type.ts b/worklenz-frontend/src/app/interfaces/nav-item-type.ts deleted file mode 100644 index 3af2df6b..00000000 --- a/worklenz-frontend/src/app/interfaces/nav-item-type.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum NavItemType { - Category, - MenuItem -} diff --git a/worklenz-frontend/src/app/interfaces/nav-item.ts b/worklenz-frontend/src/app/interfaces/nav-item.ts deleted file mode 100644 index 635117c4..00000000 --- a/worklenz-frontend/src/app/interfaces/nav-item.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {NavItemType} from "./nav-item-type"; - -export interface NavItem { - label?: string; - icon?: string; - url?: string; - type?: NavItemType; -} diff --git a/worklenz-frontend/src/app/interfaces/notification.ts b/worklenz-frontend/src/app/interfaces/notification.ts deleted file mode 100644 index 9dad76d5..00000000 --- a/worklenz-frontend/src/app/interfaces/notification.ts +++ /dev/null @@ -1,14 +0,0 @@ -export interface INotification { - id?: string; - message?: string; - user_id?: string; - team_id?: string; - read?: boolean; - team?: string; - project?: string; - url?: string; - color?: string; - team_color?: string; - created_at?: string; - updated_at?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/pagination-component.ts b/worklenz-frontend/src/app/interfaces/pagination-component.ts deleted file mode 100644 index d842ada8..00000000 --- a/worklenz-frontend/src/app/interfaces/pagination-component.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {NzTableQueryParams} from "ng-zorro-antd/table"; - -export interface IPaginationComponent { - total: number; - pageSize: number; - pageIndex: number; - paginationSizes: number[]; - loading: boolean; - sortField: string | null; - sortOrder: string | null; - - onQueryParamsChange(params: NzTableQueryParams): void; -} diff --git a/worklenz-frontend/src/app/interfaces/password-strength-check.ts b/worklenz-frontend/src/app/interfaces/password-strength-check.ts deleted file mode 100644 index 7f594735..00000000 --- a/worklenz-frontend/src/app/interfaces/password-strength-check.ts +++ /dev/null @@ -1,76 +0,0 @@ -export class PasswordStrengthChecker { - private static readonly defaultOptions = [ - { - value: 0, - text: "Too weak", - minDiversity: 0, - minLength: 0 - }, - { - value: 1, - text: "Weak", - minDiversity: 2, - minLength: 6 - }, - { - value: 2, - text: "Medium", - minDiversity: 4, - minLength: 8 - }, - { - value: 3, - text: "Strong", - minDiversity: 4, - minLength: 10 - } - ]; - private static readonly defaultAllowedSymbols = "!\"#\$%&'\(\)\*\+,-\./:;<=>\?@\[\\\\\\]\^_`\{|\}~"; - - public static validate(password: string, options = this.defaultOptions, allowedSymbols = this.defaultAllowedSymbols) { - const passwordCopy = password || ""; - - options[0].minDiversity = 0; - options[0].minLength = 0; - - const rules = [ - { - regex: "[a-z]", - message: "lowercase" - }, - { - regex: "[A-Z]", - message: "uppercase" - }, - { - regex: "[0-9]", - message: "number" - }, - ]; - - if (allowedSymbols) { - rules.push({ - regex: `[${allowedSymbols}]`, - message: "symbol" - }); - } - - const strength: any = {}; - - strength.contains = rules - .filter(rule => new RegExp(`${rule.regex}`).test(passwordCopy)) - .map(rule => rule.message); - - strength.length = passwordCopy.length; - - const fulfilledOptions = options - .filter(option => strength.contains.length >= option.minDiversity) - .filter(option => strength.length >= option.minLength) - .sort((o1, o2) => o2.value - o1.value) - .map(option => ({value: option.value, text: option.text})); - - Object.assign(strength, fulfilledOptions[0]); - - return strength; - } -} diff --git a/worklenz-frontend/src/app/interfaces/password-validity-result.ts b/worklenz-frontend/src/app/interfaces/password-validity-result.ts deleted file mode 100644 index 8598a011..00000000 --- a/worklenz-frontend/src/app/interfaces/password-validity-result.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface IPasswordValidityResult { - contains: ("lowercase" | "uppercase" | "number" | "symbol")[]; - length: number; - value: number; - text: "Too Weak" | "Weak" | "Strong" | "Excellent"; -} diff --git a/worklenz-frontend/src/app/interfaces/personal-overview.ts b/worklenz-frontend/src/app/interfaces/personal-overview.ts deleted file mode 100644 index 2918fe75..00000000 --- a/worklenz-frontend/src/app/interfaces/personal-overview.ts +++ /dev/null @@ -1,13 +0,0 @@ -export interface IActivityLog { - project_name?: string, - description?: string, - created_at?: string -} - -export interface ITasksOverview { - id?: string, - color_code?: string, - name?: string - min_date?: Date - max_date?: Date -} diff --git a/worklenz-frontend/src/app/interfaces/project-comments.ts b/worklenz-frontend/src/app/interfaces/project-comments.ts deleted file mode 100644 index 836e43cb..00000000 --- a/worklenz-frontend/src/app/interfaces/project-comments.ts +++ /dev/null @@ -1,11 +0,0 @@ - -export interface IMentionMember { - id?: string; /*user id*/ - name?: string -} - -export interface IMentionMemberViewModel extends IMentionMember{ - email?: string; - avatar_url?: string; - color_code?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/project-folder.ts b/worklenz-frontend/src/app/interfaces/project-folder.ts deleted file mode 100644 index d3c3d1a7..00000000 --- a/worklenz-frontend/src/app/interfaces/project-folder.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface IProjectFolder { - id: string; - name: string; - key: string; - color_code: string; - created_by: string; - parent_folder_id?: string; - team_id: string; - created_at: string; - updated_at: string; -} diff --git a/worklenz-frontend/src/app/interfaces/project-health.ts b/worklenz-frontend/src/app/interfaces/project-health.ts deleted file mode 100644 index 1987c361..00000000 --- a/worklenz-frontend/src/app/interfaces/project-health.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface IProjectHealth { - id: string; - name: string; - color_code?: string; - sort_order?: number; - is_default?: boolean; - selected?: boolean -} diff --git a/worklenz-frontend/src/app/interfaces/project-manager.ts b/worklenz-frontend/src/app/interfaces/project-manager.ts deleted file mode 100644 index 04adda64..00000000 --- a/worklenz-frontend/src/app/interfaces/project-manager.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface IProjectManager { - id: string; - name: string; - team_member_id: string; - selected?: boolean -} diff --git a/worklenz-frontend/src/app/interfaces/project-template.ts b/worklenz-frontend/src/app/interfaces/project-template.ts deleted file mode 100644 index 7490eb9e..00000000 --- a/worklenz-frontend/src/app/interfaces/project-template.ts +++ /dev/null @@ -1,17 +0,0 @@ -export interface ICustomProjectTemplateCreateRequest { - project_id: string, - templateName: string, - projectIncludes: { - statuses: boolean, - phases: boolean, - labels: boolean - }, - taskIncludes: { - status: boolean, - phase: boolean, - labels: boolean, - estimation: boolean, - description: boolean, - subtasks: boolean - } - } diff --git a/worklenz-frontend/src/app/interfaces/project-wise-resources-view-model.ts b/worklenz-frontend/src/app/interfaces/project-wise-resources-view-model.ts deleted file mode 100644 index 6ec632ea..00000000 --- a/worklenz-frontend/src/app/interfaces/project-wise-resources-view-model.ts +++ /dev/null @@ -1,56 +0,0 @@ -import {IGanttDateRange, IGanttMonthRange} from "@interfaces/gantt-chart"; -import {ITeamMember} from "@interfaces/team-member"; - -export interface IResourceTeamMemberViewModel extends ITeamMember { - id?: string; - name?: string; - taskCount?: number; - job_title?: string; - email?: string; - invitee_email?: string; - projects_count?: number; - role_name?: string; - tasks?: ISchedule[]; - is_admin?: boolean; - show_handles?: boolean; - is_online?: boolean; - avatar_url?: string; - selected?: boolean; - color_code?: string; -} - -export interface IScheduledTask { - project_name: string; - id?: string; - name?: string; - project_id?: string; -} - - -export interface ISchedule { - color_code?: string; - scheduled_tasks: IScheduledTask[]; - min?: number; - date_series?: string; - project_id?: string; - sum?: number; -} - -export interface IResource { - id?: string; - name?: string; - invitee_email?: string; - date_range?: number; - min?: string; - collapsed?: boolean; - project_members?: IResourceTeamMemberViewModel[]; - color_code?: string; - schedule?: ISchedule[]; - unassigned_tasks: ISchedule[]; -} - -export interface IProjectWiseResourcesViewModel { - dates: IGanttDateRange[], - months: IGanttMonthRange[], - projects: IResource[] -} diff --git a/worklenz-frontend/src/app/interfaces/project.ts b/worklenz-frontend/src/app/interfaces/project.ts deleted file mode 100644 index f638b4a8..00000000 --- a/worklenz-frontend/src/app/interfaces/project.ts +++ /dev/null @@ -1,44 +0,0 @@ -import {IProjectCategory} from "@interfaces/project-category"; -import {IProjectStatus} from "@interfaces/project-status"; -import {ITaskComment} from "@interfaces/task-comment"; - -export interface IProject { - id?: string; - name?: string; - color_code?: string; - notes?: string; - team_id?: string; - client_id?: string; - owner_id?: string; - created_at?: string; - updated_at?: string; - status_id?: string; - man_days?: number - hours_per_day?: number -} - -export interface IProjectUpdate { - name?: string; - category?: IProjectCategory; - status?: IProjectStatus, - notes?: string -} - -export interface IProjectUpdateComment { - id?: string; - content?: string; - user_id?: string; - project_id?: string; - created_at?: string; - updated_at?: string; -} - -export interface IProjectUpdateCommentViewModel extends IProjectUpdateComment { - created_by?: string; - avatar_url?: string; - color_code?: string; - mentions: [ - user_name?: string, - user_email?: string - ]; -} diff --git a/worklenz-frontend/src/app/interfaces/reporting-allocation-settings.ts b/worklenz-frontend/src/app/interfaces/reporting-allocation-settings.ts deleted file mode 100644 index e52e167e..00000000 --- a/worklenz-frontend/src/app/interfaces/reporting-allocation-settings.ts +++ /dev/null @@ -1,5 +0,0 @@ -import {ITeam} from "@interfaces/team"; - -export interface IReportingAllocationSettings { - teams?: ITeam[]; -} diff --git a/worklenz-frontend/src/app/interfaces/reporting.ts b/worklenz-frontend/src/app/interfaces/reporting.ts deleted file mode 100644 index 9f1e552e..00000000 --- a/worklenz-frontend/src/app/interfaces/reporting.ts +++ /dev/null @@ -1,219 +0,0 @@ -export interface IActualVsEstimateGetRequest { - projects: string[]; - estimated: string[]; - logged: string[]; - estimated_string: string[]; - logged_string: string[]; -} - -export interface IReportingOverview { - total_teams?: string, - total_projects?: string, - active_projects?: string, - overdue_projects?: string - total_members?: string; - overdue_task_members?: string; - unassigned_members?: string; -} - -export interface IReportingEstimatedVsLogged { - total_logged?: string; - total_estimated?: string; -} - -export interface IReportingTeam { - id: string; - name: string; - members: null; - is_completed: boolean; - completed_date: string; - projects_count: number; - names: any; - projects: IReportingProject[]; -} - -export interface IReportingProject { - name: string; - is_overdue: boolean; - status: string; - status_icon: string; - status_color: string; - due_date: string; - overdue: number; - total_allocation: string; - overlogged: string; - members: null; - project_member_names: any -} - -export interface IReportingProjectStats { - active_projects?: number; - all_tasks_count?: number; - completed_tasks_count?: number; - overdue_projects?: number; - total_estimated?: number; - total_logged?: number; - progress?: number; - overlogged_hours?: string; - logged_hours_string?: string; - total_estimated_hours_string?: string; - total_logged_hours_string?: string; -} - -export interface IReportingActiveProject { - id?: string, - name: string, - updated_at: string, - updated_at_string: string, - status: string, - end_date: string -} - -export interface IReportingOverdueProject { - id?: string, - name: string, - updated_at: string, - updated_at_string: string, - status: string, - end_date: string - overlogged_hours: string; -} - -export interface IReportingProjectsCustom { - all_tasks_count: number; - client_name: string; - color_code: string; - completed_percentage: number; - completed_tasks_count: number; - doing_percentage: number; - end_date: string; - id: string; - is_doing_tasks_count: number; - is_todo_tasks_count: number; - members_count: number; - name: string; - project_owner: string; - start_date: string; - status: string; - status_color: string; - status_icon: string; - team_name: string; - todo_percentage: number; - updated_at: string; - updated_at_string?: string; - names: any; -} - -export interface IReportingProjectData { - name?: string; - team_name?: string; -} - -export interface IReportingOverdueMembers { - id: string; - name: string; - overdue_tasks: number; - overdue_time: string; - projects: number; -} - -export interface IReportingUnassignedMembers { - id: string; - name: string; -} - -export interface IReportingMemberStats { - name?: string, - total_teams?: number, - project_members?: number, - total_estimated?: number, - total_logged?: number, - total_tasks?: number, - total_tasks_completed?: number, - overdue_tasks?: number, - progress?: string, - log_progress?: string, - total_estimated_hours_string?: string, - total_logged_hours_string?: string, - overlogged_hours?: string, -} - -export interface IReportingMemberRecentLogged { - id?: string; - name?: string; - project_name?: string; - project_id?: string; - team_name?: string; - status?: string; - status_color?: string; - end_date?: string; - logged_time?: string; - logged_timestamp?: string; -} - -export interface IReportingMemberCurrentlyDoing { - id?: string; - task?: string; - project?: string; - project_id?: string; - team?: string; - status?: string; - status_color?: string; - end_date?: string; - last_updated?: string; -} - -export interface IReportingMemberOverdueTask { - id?: string; - task?: string; - project?: string; - project_id?: string; - team?: string; - status?: string; - status_color?: string; - end_date?: string; - overdue?: string; -} - -export interface IReportingMemberProjectTask { - name?: string; - is_overdue?: boolean; - status?: string; - status_color?: string; - due_date?: string; - overdue?: number; - completed_date?: string; - toal_estimated?: string; - overlogged_time?: string; - total_logged?: string; -} - -export interface IReportingMemberProjects { - id: string; - name?: string; - team?: string; - contribution?: number; - total_task_count?: number; - assigned_task_count?: number; - completed_tasks?: number; - incompleted_tasks?: number; - overdue_tasks?: number; - tasks?: IReportingMemberProjectTask[] -} - -export interface IReportingMemberProjectTaskLate { - name?: string; - project?: string; - status?: string; - status_color?: string; - due_date?: string; - completed_at?: string; - assignees?: any; - overlogged_time?: string; -} - -export interface ITeamMemberInsightsProjects { - id?: string; - name?: string; - selected?: boolean; -} diff --git a/worklenz-frontend/src/app/interfaces/roadmap.ts b/worklenz-frontend/src/app/interfaces/roadmap.ts deleted file mode 100644 index 0a2d547a..00000000 --- a/worklenz-frontend/src/app/interfaces/roadmap.ts +++ /dev/null @@ -1,36 +0,0 @@ -import {Moment} from "moment/moment"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; - -export interface IRoadmapConfigV2 { - id: string; - parent_task?: string; - timezone: string; - group?: string; - isSubtasksInclude: boolean; - expandedGroups: string[]; -} - - -export interface ITaskDragResponse { - task_id: string; - task_width: number; - task_offset: number; - start_date: string; - end_date: string; - group_id: string; -} - -export interface ITaskResizeResponse { - id: string; - parent_task: string | null; - end_date: string; - start_date: string; - group_id: string; -} - -export interface IDateVerificationResponse { - task: IProjectTask, - taskStartDate: string | null, - taskEndDate: string | null, - chartStartDate: Moment -} diff --git a/worklenz-frontend/src/app/interfaces/role.ts b/worklenz-frontend/src/app/interfaces/role.ts deleted file mode 100644 index abf9855c..00000000 --- a/worklenz-frontend/src/app/interfaces/role.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface IRole { - id?: string; - name?: string; - team_id?: string; - default_role?: boolean; - admin_role?: boolean; -} diff --git a/worklenz-frontend/src/app/interfaces/schedular.ts b/worklenz-frontend/src/app/interfaces/schedular.ts deleted file mode 100644 index f0388303..00000000 --- a/worklenz-frontend/src/app/interfaces/schedular.ts +++ /dev/null @@ -1,92 +0,0 @@ -import {ITaskListGroup} from "../administrator/modules/task-list-v2/interfaces"; - -export interface IScheduleSingleDay { - day: number; - name: string; - isWeekend: boolean; - isToday: boolean; -} - -export interface IScheduleSingleMonth { - month: string; - days: IScheduleSingleDay[] -} - -export interface IScheduleDateCreateResponse { - width: number; - scroll_by: number; - date_data: IScheduleSingleMonth[]; - chart_start: string; - chart_end: string; -} - -export interface IScheduleProject { - id: string; - name: string; - color_code: string; - indicator_offset?: number; - indicator_width?: number; - members: IScheduleProjectMember[]; - is_expanded: boolean; -} - -export interface IScheduleProjectReload { - project_id: string; - expanded_members: string[]; - is_unassigned_expanded: boolean; -} - -export interface IMemberUpdateResponse { - project_allocation: IScheduleProject, - member_allocations: IMemberAllocation[] -} - -export interface IProjectUpdateResposne { - project_allocation: IScheduleProject -} - -export interface IMemberIndicatorContextMenuEvent { - event: MouseEvent, - projectId: string, - teamMemberId: string, - ids: string[] -} - -export interface IMemberAllocation { - ids: string[]; - indicator_offset: number; - indicator_width: number; - allocated_from: string; - allocated_to: string; -} - -export interface IScheduleProjectMember { - project_member_id: string; - is_project_member: boolean; - pending_invitation: boolean; - name: string; - avatar_url?: string; - color_code?: string; - team_member_id: string; - user_id: string; - allocations: IMemberAllocation[] -} - -export interface IScheduleMemberTask { - task_id: string; - task_name: string; -} - -export interface IScheduleTasksConfig { - id: string; - members: string | null; - archived?: boolean; - count?: boolean; - parent_task?: string; - group?: string; - isSubtasksInclude: boolean; -} - -export interface IMemberTaskListGroup extends ITaskListGroup { - isExpand: boolean -} diff --git a/worklenz-frontend/src/app/interfaces/selectable-category.ts b/worklenz-frontend/src/app/interfaces/selectable-category.ts deleted file mode 100644 index e734cb68..00000000 --- a/worklenz-frontend/src/app/interfaces/selectable-category.ts +++ /dev/null @@ -1,5 +0,0 @@ -import {IProjectCategory} from "@interfaces/project-category"; - -export interface ISelectableCategory extends IProjectCategory { - selected?: boolean; -} diff --git a/worklenz-frontend/src/app/interfaces/selectable-project.ts b/worklenz-frontend/src/app/interfaces/selectable-project.ts deleted file mode 100644 index 715e20fd..00000000 --- a/worklenz-frontend/src/app/interfaces/selectable-project.ts +++ /dev/null @@ -1,5 +0,0 @@ -import {IProject} from "@interfaces/project"; - -export interface ISelectableProject extends IProject { - selected?: boolean; -} diff --git a/worklenz-frontend/src/app/interfaces/selectable-team.ts b/worklenz-frontend/src/app/interfaces/selectable-team.ts deleted file mode 100644 index 68bd0353..00000000 --- a/worklenz-frontend/src/app/interfaces/selectable-team.ts +++ /dev/null @@ -1,5 +0,0 @@ -import {ITeam} from "@interfaces/team"; - -export interface ISelectableTeam extends ITeam { - selected?: boolean; -} diff --git a/worklenz-frontend/src/app/interfaces/settings-navigation-item.ts b/worklenz-frontend/src/app/interfaces/settings-navigation-item.ts deleted file mode 100644 index e5da12ca..00000000 --- a/worklenz-frontend/src/app/interfaces/settings-navigation-item.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface ISettingsNavigationItem { - label: string; - icon: string; - url: string; -} diff --git a/worklenz-frontend/src/app/interfaces/sub-task.ts b/worklenz-frontend/src/app/interfaces/sub-task.ts deleted file mode 100644 index a454f9f2..00000000 --- a/worklenz-frontend/src/app/interfaces/sub-task.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {InlineMember} from "@interfaces/api-models/inline-member"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; - -export interface ISubTask extends IProjectTask { - id?: string; - name?: string; - status_color?: string; - status?: string; - status_name?: string; - priority?: string; - priority_name?: string; - end_date?: string; - names?: InlineMember[]; - show_handles?: boolean; - min?: number; - max?: number; - color_code?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/task-assignee-update-response.ts b/worklenz-frontend/src/app/interfaces/task-assignee-update-response.ts deleted file mode 100644 index 2743654a..00000000 --- a/worklenz-frontend/src/app/interfaces/task-assignee-update-response.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {ITaskAssignee} from "@interfaces/api-models/project-tasks-view-model"; -import {InlineMember} from "@interfaces/api-models/inline-member"; -import {ITeamMemberViewModel} from "./api-models/team-members-get-response"; - -export interface ITaskAssigneesUpdateResponse { - id: string; - parent_task: string; - assignees: ITaskAssignee[]; - names: InlineMember[]; - members: ITeamMemberViewModel[]; - mode: any; - team_member_id: string; -} diff --git a/worklenz-frontend/src/app/interfaces/task-assignee.ts b/worklenz-frontend/src/app/interfaces/task-assignee.ts deleted file mode 100644 index f4b352e5..00000000 --- a/worklenz-frontend/src/app/interfaces/task-assignee.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface ITaskAssignee { - task_id?: string; - project_member_id?: string; - created_at?: string; - updated_at?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/task-comment.ts b/worklenz-frontend/src/app/interfaces/task-comment.ts deleted file mode 100644 index e9b6c137..00000000 --- a/worklenz-frontend/src/app/interfaces/task-comment.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface ITaskComment { - id?: string; - content?: string; - user_id?: string; - team_member_id?: string; - task_id?: string; - created_at?: string; - updated_at?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/task-form-view-model.ts b/worklenz-frontend/src/app/interfaces/task-form-view-model.ts deleted file mode 100644 index e330f736..00000000 --- a/worklenz-frontend/src/app/interfaces/task-form-view-model.ts +++ /dev/null @@ -1,57 +0,0 @@ -import {IProjectMember} from "@interfaces/project-member"; -import {ITask} from "@interfaces/task"; -import {ITaskPriority} from "@interfaces/task-priority"; -import {IProject} from "@interfaces/project"; -import {ITaskStatus} from "@interfaces/task-status"; -import {ITeamMember} from "@interfaces/team-member"; -import {InlineMember} from "./api-models/inline-member"; -import {ITaskLabel} from "@interfaces/task-label"; -import {ITaskPhase} from "@interfaces/api-models/task-phase"; - -export interface IProjectMemberViewModel extends IProjectMember { - name?: string; - team_member_id?: string; - job_title?: string; - email?: string; - avatar_url?: string; - color_code?: string; -} - -export interface ITaskViewModel extends ITask { - task_key?: string; - created_from_now?: string; - updated_from_now?: string; - reporter?: string; - start_date?: string; - end_date?: string; - sub_tasks_count?: number; - is_sub_task?: boolean; - status_color?: string; - attachments_count?: number; - complete_ratio?: number; - names?: InlineMember[]; - labels?: ITaskLabel[]; - timer_start_time?: number; - phase_id?: string; -} - -export interface ITaskTeamMember extends ITeamMember { - name?: string; - color_code?: string; - avatar_url?: string; - email?: string; -} - -export interface ITaskFormViewModel { - task?: ITaskViewModel; - priorities?: ITaskPriority[]; - projects?: IProject[]; - statuses?: ITaskStatus[]; - phases?: ITaskPhase[]; - team_members?: ITaskTeamMember[]; -} - -export interface IHomeTaskViewModel extends ITask { - task?: ITaskViewModel; - team_member_id?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/task-list-column.ts b/worklenz-frontend/src/app/interfaces/task-list-column.ts deleted file mode 100644 index e8f92c95..00000000 --- a/worklenz-frontend/src/app/interfaces/task-list-column.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface ITaskListColumn { - id?: string; - name?: string; - key?: string; - index?: number; - pinned?: boolean; - project_id?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/task-list-estimation-change-response.ts b/worklenz-frontend/src/app/interfaces/task-list-estimation-change-response.ts deleted file mode 100644 index 35e75638..00000000 --- a/worklenz-frontend/src/app/interfaces/task-list-estimation-change-response.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface ITaskListEstimationChangeResponse { - id: string; - total_minutes: number; - total_hours: number; - parent_task: string; - time_spent_string: string; - total_time_string: string; -} diff --git a/worklenz-frontend/src/app/interfaces/task-list-status-change-response.ts b/worklenz-frontend/src/app/interfaces/task-list-status-change-response.ts deleted file mode 100644 index 1f0cff1b..00000000 --- a/worklenz-frontend/src/app/interfaces/task-list-status-change-response.ts +++ /dev/null @@ -1,14 +0,0 @@ -export interface ITaskListStatusChangeResponse { - status_id: string | undefined; - id: string; - parent_task: string; - color_code: string; - complete_ratio: number; - completed_at?: string; - timer_start_time?: number; - statusCategory?: { - is_todo: boolean; - is_doing: boolean; - is_done: boolean; - }; -} diff --git a/worklenz-frontend/src/app/interfaces/task-priority.ts b/worklenz-frontend/src/app/interfaces/task-priority.ts deleted file mode 100644 index e548f954..00000000 --- a/worklenz-frontend/src/app/interfaces/task-priority.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface ITaskPriority { - id: string; - name: string; - value: string; - color_code?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/task-status.ts b/worklenz-frontend/src/app/interfaces/task-status.ts deleted file mode 100644 index 53a68f0b..00000000 --- a/worklenz-frontend/src/app/interfaces/task-status.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ITaskStatusCategory} from "@interfaces/task-status-category"; - -export interface ITaskStatus { - id?: string; - name?: string; - description?: string; - order_index?: number; - color_code?: string; - team_id?: string; - default_status?: boolean; - date_created?: string; - date_updated?: string; -} - -export interface IKanbanTaskStatus extends ITaskStatus { - category_id?: string; -} - -export interface ICategorizedStatus { - category_id: string; - category_color: string; - statuses: ITaskStatusCategory[] -} diff --git a/worklenz-frontend/src/app/interfaces/task.ts b/worklenz-frontend/src/app/interfaces/task.ts deleted file mode 100644 index 0e0b4f27..00000000 --- a/worklenz-frontend/src/app/interfaces/task.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {ISubTask} from "@interfaces/sub-task"; -import {IUser} from "@interfaces/user"; - -export interface ITaskAssignee { - team_member_id: any; - id: string; - project_member_id: string; - name: string -} - -export interface ITask { - assignees?: ITaskAssignee[] | string[]; - assignees_ids?: any[]; - description?: string; - done?: boolean; - end?: string | Date; - end_date?: string | Date; - id?: string; - name?: string; - resize_valid?: boolean; - start?: string | Date; - start_date?: string | Date; - _start?: Date; - _end?: Date; - color_code?: string; - priority?: string; - priority_id?: string; - status?: string; - status_id?: string; - project_id?: string; - reporter_id?: string; - created_at?: string; - updated_at?: string; - show_handles?: boolean; - min?: number; - max?: number; - total_hours?: number; - total_minutes?: number; - name_color?: string; - sub_tasks_count?: number; - is_sub_task?: boolean; - parent_task_name?: string; - parent_task_id?: string; - show_sub_tasks?: boolean; - sub_tasks?: ISubTask[]; - archived?: boolean; - subscribers?: IUser[]; -} diff --git a/worklenz-frontend/src/app/interfaces/team.ts b/worklenz-frontend/src/app/interfaces/team.ts deleted file mode 100644 index cad1e23c..00000000 --- a/worklenz-frontend/src/app/interfaces/team.ts +++ /dev/null @@ -1,21 +0,0 @@ -export interface ITeam { - id?: string; - name?: string; - key?: string; - user_id?: string; - created_at?: string; - updated_at?: string; -} - -export interface ITeamInvites { - id?: string; - team_id?: string; - team_member_id?: string; - team_name?: string; - team_owner?: string; -} - -export interface IAcceptTeamInvite { - team_member_id?: string; - show_alert?: boolean; -} diff --git a/worklenz-frontend/src/app/interfaces/timer-start-event-response.ts b/worklenz-frontend/src/app/interfaces/timer-start-event-response.ts deleted file mode 100644 index b28c1726..00000000 --- a/worklenz-frontend/src/app/interfaces/timer-start-event-response.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface ITimerStartEventResponse { - id?: string; - timer_start_time?: number; - parent_task?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/timer-stop-event-response.ts b/worklenz-frontend/src/app/interfaces/timer-stop-event-response.ts deleted file mode 100644 index 6a683530..00000000 --- a/worklenz-frontend/src/app/interfaces/timer-stop-event-response.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface ITimerStopEventResponse { - id?: string; - parent_task?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/todo-list-item.ts b/worklenz-frontend/src/app/interfaces/todo-list-item.ts deleted file mode 100644 index 6b397aa0..00000000 --- a/worklenz-frontend/src/app/interfaces/todo-list-item.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface ITodoListItem { - id?: string; - name?: string; - description?: string; - color_code?: string; - done?: any; - user_id?: string; - created_at?: string; - updated_at?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/user.ts b/worklenz-frontend/src/app/interfaces/user.ts deleted file mode 100644 index c8badffa..00000000 --- a/worklenz-frontend/src/app/interfaces/user.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface IUser { - id?: string; - name?: string; - password?: string; - email?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/worklenz-notification.ts b/worklenz-frontend/src/app/interfaces/worklenz-notification.ts deleted file mode 100644 index c027a0c3..00000000 --- a/worklenz-frontend/src/app/interfaces/worklenz-notification.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {Params} from "@angular/router"; - -export interface IWorklenzNotification { - id: string; - team: string; - team_id: string; - message: string; - project?: string; - color?: string; - url?: string; - task_id?: string; - params?: Params; - created_at?: string; -} diff --git a/worklenz-frontend/src/app/interfaces/workload.ts b/worklenz-frontend/src/app/interfaces/workload.ts deleted file mode 100644 index 88c19c89..00000000 --- a/worklenz-frontend/src/app/interfaces/workload.ts +++ /dev/null @@ -1,91 +0,0 @@ -import {ITaskListGroup} from "../administrator/modules/task-list-v2/interfaces"; - -export interface IWLTasksConfig { - id: string; - members: string | null; - archived?: boolean; - count?: boolean; - parent_task?: string; - group?: string; - isSubtasksInclude: boolean; - dateChecker: "" | "end_date_null" | "start_date_null" | "start_end_dates_null"; -} - -export interface IWLTaskListGroup extends ITaskListGroup { - isExpand: boolean -} - -export interface ISingleDay { - day: number; - name: string; - isWeekend: boolean; - isToday: boolean; -} - -export interface ISingleMonth { - month: string; - days: ISingleDay[] -} - -export interface IDateCreateResponse { - width: number; - scroll_by: number; - date_data: ISingleMonth[] | []; - chart_start: string; - chart_end: string; -} - -export interface IWLMemberTask { - task_id: string; - task_name: string; - start_date: string; - end_date: string; - width: number; - left: number; -} - -export interface IWLMember { - project_member_id: string; - is_project_member: boolean; - name: string; - avatar_url?: string; - color_code?: string; - team_member_id: string; - indicator_offset: number; - indicator_width: number; - tasks_start_date: string; - tasks_end_date: string; - tasks_stats: { - total: number; - null_start_dates: number; - null_end_dates: number; - null_start_end_dates: number; - available_start_end_dates: number; - null_start_dates_percentage: number; - null_end_dates_percentage: number; - null_start_end_dates_percentage: number; - available_start_end_dates_percentage: number; - }, - not_allocated: boolean; - // tasks: IWLMemberTask[] | []; - // isExpand: boolean; -} - -export interface IDragReturn { - finalLeft: number; - dragDifference: number; -} - -export interface IWLMemberOverviewResponse { - by_status: IWLMemberOverview[]; - by_priority: IWLMemberOverview[]; - by_phase: IWLMemberOverview[]; - by_dates: IWLMemberOverview[]; -} - -export interface IWLMemberOverview { - id: string; - label: string; - color_code: string; - tasks_count: number -} diff --git a/worklenz-frontend/src/app/pipes/bind-na.pipe.spec.ts b/worklenz-frontend/src/app/pipes/bind-na.pipe.spec.ts deleted file mode 100644 index 1f94b2a9..00000000 --- a/worklenz-frontend/src/app/pipes/bind-na.pipe.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {BindNaPipe} from './bind-na.pipe'; - -describe('BindNaPipe', () => { - it('create an instance', () => { - const pipe = new BindNaPipe(); - expect(pipe).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/pipes/bind-na.pipe.ts b/worklenz-frontend/src/app/pipes/bind-na.pipe.ts deleted file mode 100644 index eab4cb69..00000000 --- a/worklenz-frontend/src/app/pipes/bind-na.pipe.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; - -@Pipe({ - name: 'bindNa', - standalone: true -}) -export class BindNaPipe implements PipeTransform { - transform(value?: string): string { - return value?.trim() || "-"; - } -} diff --git a/worklenz-frontend/src/app/pipes/date-formatter.pipe.ts b/worklenz-frontend/src/app/pipes/date-formatter.pipe.ts deleted file mode 100644 index 77b06548..00000000 --- a/worklenz-frontend/src/app/pipes/date-formatter.pipe.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {Pipe, PipeTransform} from "@angular/core"; -import moment from "moment"; - -@Pipe({ - name: 'dateFormatter', - standalone: true -}) - -export class DateFormatterPipe implements PipeTransform { - - currentDate: moment.Moment = moment(); - currentYear = moment().year(); - - transform(value: any) { - - if (value) { - const date = this.currentDate; - const isSame = (input: moment.Moment, duration: any) => moment(value).isSame(input, duration); - - if (moment(value).year() == this.currentYear) { - if (moment(value).isSame(date.clone(), 'day')) { - return "Today"; - } else if (isSame(date.clone().subtract(1, 'day'), 'day')) { - return "Yesterday"; - } else if (isSame(date.clone().add(1, 'day'), 'day')) { - return "Tomorrow"; - } - return moment(value).format('MMM DD').toString(); - } - return moment(value).format('MMM DD, YYYY').toString(); - } - return null; - } - -} diff --git a/worklenz-frontend/src/app/pipes/ellipsis.pipe.spec.ts b/worklenz-frontend/src/app/pipes/ellipsis.pipe.spec.ts deleted file mode 100644 index f7443804..00000000 --- a/worklenz-frontend/src/app/pipes/ellipsis.pipe.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {EllipsisPipe} from './ellipsis.pipe'; - -describe('EllipsisPipe', () => { - it('create an instance', () => { - const pipe = new EllipsisPipe(); - expect(pipe).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/pipes/ellipsis.pipe.ts b/worklenz-frontend/src/app/pipes/ellipsis.pipe.ts deleted file mode 100644 index e631c190..00000000 --- a/worklenz-frontend/src/app/pipes/ellipsis.pipe.ts +++ /dev/null @@ -1,15 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; - -@Pipe({ - name: 'ellipsis', - standalone: true -}) -export class EllipsisPipe implements PipeTransform { - transform(value?: string, limit?: number): any { - if (!value) return null; - - if (limit && value.length > limit) - return value.substring(0, limit).concat('...'); - return value; - } -} diff --git a/worklenz-frontend/src/app/pipes/first-char-upper.pipe.spec.ts b/worklenz-frontend/src/app/pipes/first-char-upper.pipe.spec.ts deleted file mode 100644 index 43bc214d..00000000 --- a/worklenz-frontend/src/app/pipes/first-char-upper.pipe.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {FirstCharUpperPipe} from './first-char-upper.pipe'; - -describe('FirstCharUpperPipe', () => { - it('create an instance', () => { - const pipe = new FirstCharUpperPipe(); - expect(pipe).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/pipes/first-char-upper.pipe.ts b/worklenz-frontend/src/app/pipes/first-char-upper.pipe.ts deleted file mode 100644 index 86eeede3..00000000 --- a/worklenz-frontend/src/app/pipes/first-char-upper.pipe.ts +++ /dev/null @@ -1,12 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; - -@Pipe({ - name: 'firstCharUpper', - standalone: true -}) -export class FirstCharUpperPipe implements PipeTransform { - transform(value?: string, ...args: unknown[]): string { - if (!value) return ''; - return value.charAt(0).toUpperCase(); - } -} diff --git a/worklenz-frontend/src/app/pipes/from-now.pipe.spec.ts b/worklenz-frontend/src/app/pipes/from-now.pipe.spec.ts deleted file mode 100644 index 14bb7046..00000000 --- a/worklenz-frontend/src/app/pipes/from-now.pipe.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {FromNowPipe} from './from-now.pipe'; - -describe('FromNowPipe', () => { - it('create an instance', () => { - const pipe = new FromNowPipe(); - expect(pipe).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/pipes/from-now.pipe.ts b/worklenz-frontend/src/app/pipes/from-now.pipe.ts deleted file mode 100644 index 7004eac9..00000000 --- a/worklenz-frontend/src/app/pipes/from-now.pipe.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; -import moment from "moment/moment"; - -@Pipe({ - name: 'fromNow', - standalone: true -}) -export class FromNowPipe implements PipeTransform { - transform(value?: any): string | undefined { - if (!value) return value; - return moment(value).fromNow(); - } -} diff --git a/worklenz-frontend/src/app/pipes/min-date-validator.pipe.ts b/worklenz-frontend/src/app/pipes/min-date-validator.pipe.ts deleted file mode 100644 index d08e4318..00000000 --- a/worklenz-frontend/src/app/pipes/min-date-validator.pipe.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; -import {UtilsService} from "@services/utils.service"; - -@Pipe({ - name: 'minDateValidator', - standalone: true -}) -export class MinDateValidatorPipe implements PipeTransform { - constructor( - private readonly utils: UtilsService - ) { - } - - transform(value?: string, ...args: unknown[]) { - return this.utils.checkForMinDate(value); - } -} diff --git a/worklenz-frontend/src/app/pipes/safe-string.pipe.spec.ts b/worklenz-frontend/src/app/pipes/safe-string.pipe.spec.ts deleted file mode 100644 index 8e017f56..00000000 --- a/worklenz-frontend/src/app/pipes/safe-string.pipe.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {SafeStringPipe} from './safe-string.pipe'; - -describe('SafeStringPipe', () => { - it('create an instance', () => { - const pipe = new SafeStringPipe(); - expect(pipe).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/pipes/safe-string.pipe.ts b/worklenz-frontend/src/app/pipes/safe-string.pipe.ts deleted file mode 100644 index 61b7f687..00000000 --- a/worklenz-frontend/src/app/pipes/safe-string.pipe.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; - -@Pipe({ - name: 'safeString', - standalone: true -}) -export class SafeStringPipe implements PipeTransform { - transform(value?: any): string { - const stringValue = String(value); - if (stringValue === 'null' || stringValue === 'undefined' || stringValue === 'NaN') { - return ''; - } - - return stringValue; - } -} diff --git a/worklenz-frontend/src/app/pipes/search-by-name.pipe.spec.ts b/worklenz-frontend/src/app/pipes/search-by-name.pipe.spec.ts deleted file mode 100644 index 45868196..00000000 --- a/worklenz-frontend/src/app/pipes/search-by-name.pipe.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {SearchByNamePipe} from './search-by-name.pipe'; - -describe('SearchByNamePipe', () => { - it('create an instance', () => { - const pipe = new SearchByNamePipe(); - expect(pipe).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/pipes/search-by-name.pipe.ts b/worklenz-frontend/src/app/pipes/search-by-name.pipe.ts deleted file mode 100644 index 007fb3f1..00000000 --- a/worklenz-frontend/src/app/pipes/search-by-name.pipe.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; - -@Pipe({ - name: 'searchByName', - standalone: true -}) -export class SearchByNamePipe implements PipeTransform { - transform(items: T[], searchTerm: string | null): T[] { - if (!searchTerm) - return items; - - return items.filter((item: any) => { - return item.name?.toLowerCase().includes(searchTerm.toLowerCase()); - }); - } -} diff --git a/worklenz-frontend/src/app/pipes/task-comment-mention.pipe.spec.ts b/worklenz-frontend/src/app/pipes/task-comment-mention.pipe.spec.ts deleted file mode 100644 index 4ed207cb..00000000 --- a/worklenz-frontend/src/app/pipes/task-comment-mention.pipe.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { TaskCommentMentionPipe } from './task-comment-mention.pipe'; - -describe('TaskCommentMentionPipe', () => { - it('create an instance', () => { - const pipe = new TaskCommentMentionPipe(); - expect(pipe).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/pipes/task-comment-mention.pipe.ts b/worklenz-frontend/src/app/pipes/task-comment-mention.pipe.ts deleted file mode 100644 index 10e86267..00000000 --- a/worklenz-frontend/src/app/pipes/task-comment-mention.pipe.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core'; - -@Pipe({ - name: 'taskCommentMention', - standalone: true -}) -export class TaskCommentMentionPipe implements PipeTransform { - - transform(commentText: string, teamMembers: string[]): string { - - if (!commentText || !teamMembers.length) { - return commentText; - } - - const words = commentText.split(/|s+/); - - return words.map(word => { - if (word.startsWith('@')) { - const name = word.substring(1); - if (teamMembers.includes(name)) { - return `@${name}`; - } - } - return word; - }).join(' '); - - } - -} diff --git a/worklenz-frontend/src/app/pipes/to-now.pipe.spec.ts b/worklenz-frontend/src/app/pipes/to-now.pipe.spec.ts deleted file mode 100644 index 321badb7..00000000 --- a/worklenz-frontend/src/app/pipes/to-now.pipe.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {ToNowPipe} from './to-now.pipe'; - -describe('ToNowPipe', () => { - it('create an instance', () => { - const pipe = new ToNowPipe(); - expect(pipe).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/pipes/to-now.pipe.ts b/worklenz-frontend/src/app/pipes/to-now.pipe.ts deleted file mode 100644 index 25cab74b..00000000 --- a/worklenz-frontend/src/app/pipes/to-now.pipe.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; -import moment from "moment"; - -@Pipe({ - name: 'toNow', - standalone: true -}) -export class ToNowPipe implements PipeTransform { - transform(value?: any): string | undefined { - if (!value) return value; - return moment(value).toNow(); - } -} diff --git a/worklenz-frontend/src/app/pipes/with-alpha.pipe.spec.ts b/worklenz-frontend/src/app/pipes/with-alpha.pipe.spec.ts deleted file mode 100644 index cab8903b..00000000 --- a/worklenz-frontend/src/app/pipes/with-alpha.pipe.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {WithAlphaPipe} from './with-alpha.pipe'; - -describe('WithAlphaPipe', () => { - it('create an instance', () => { - const pipe = new WithAlphaPipe(); - expect(pipe).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/pipes/with-alpha.pipe.ts b/worklenz-frontend/src/app/pipes/with-alpha.pipe.ts deleted file mode 100644 index f409dd65..00000000 --- a/worklenz-frontend/src/app/pipes/with-alpha.pipe.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; -import {ALPHA_CHANNEL} from "@shared/constants"; - -@Pipe({ - name: 'withAlpha', - standalone: true -}) -export class WithAlphaPipe implements PipeTransform { - transform(value?: string, ...args: unknown[]): string { - if (!value) return ''; - return value + ALPHA_CHANNEL; - } -} diff --git a/worklenz-frontend/src/app/pipes/wl-safe-array.pipe.spec.ts b/worklenz-frontend/src/app/pipes/wl-safe-array.pipe.spec.ts deleted file mode 100644 index da494031..00000000 --- a/worklenz-frontend/src/app/pipes/wl-safe-array.pipe.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {WlSafeArrayPipe} from './wl-safe-array.pipe'; - -describe('WlSafeArrayPipe', () => { - it('create an instance', () => { - const pipe = new WlSafeArrayPipe(); - expect(pipe).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/pipes/wl-safe-array.pipe.ts b/worklenz-frontend/src/app/pipes/wl-safe-array.pipe.ts deleted file mode 100644 index 8b5fb46c..00000000 --- a/worklenz-frontend/src/app/pipes/wl-safe-array.pipe.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; - -@Pipe({ - name: 'wlSafeArray', - standalone: true -}) -export class WlSafeArrayPipe implements PipeTransform { - transform(value: any, ...args: unknown[]) { - return !Array.isArray(value) ? [] : value; - } -} diff --git a/worklenz-frontend/src/app/routes/account-setup-routes.tsx b/worklenz-frontend/src/app/routes/account-setup-routes.tsx new file mode 100644 index 00000000..b7f2fcaf --- /dev/null +++ b/worklenz-frontend/src/app/routes/account-setup-routes.tsx @@ -0,0 +1,9 @@ +import { RouteObject } from 'react-router-dom'; +import AccountSetup from '@/pages/account-setup/account-setup'; + +const accountSetupRoute: RouteObject = { + path: '/worklenz/setup', + element: , +}; + +export default accountSetupRoute; diff --git a/worklenz-frontend/src/app/routes/admin-center-routes.tsx b/worklenz-frontend/src/app/routes/admin-center-routes.tsx new file mode 100644 index 00000000..2e670a7f --- /dev/null +++ b/worklenz-frontend/src/app/routes/admin-center-routes.tsx @@ -0,0 +1,32 @@ +import { RouteObject } from 'react-router-dom'; +import AdminCenterLayout from '@/layouts/admin-center-layout'; +import { adminCenterItems } from '@/pages/admin-center/admin-center-constants'; +import { Navigate } from 'react-router-dom'; +import { useAuthService } from '@/hooks/useAuth'; + +const AdminCenterGuard = ({ children }: { children: React.ReactNode }) => { + const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin(); + + if (!isOwnerOrAdmin) { + return ; + } + + return <>{children}; +}; + +const adminCenterRoutes: RouteObject[] = [ + { + path: 'admin-center', + element: ( + + + + ), + children: adminCenterItems.map(item => ({ + path: item.endpoint, + element: item.element, + })), + }, +]; + +export default adminCenterRoutes; diff --git a/worklenz-frontend/src/app/routes/auth-routes.tsx b/worklenz-frontend/src/app/routes/auth-routes.tsx new file mode 100644 index 00000000..2eca96a9 --- /dev/null +++ b/worklenz-frontend/src/app/routes/auth-routes.tsx @@ -0,0 +1,51 @@ +import AuthLayout from '@/layouts/AuthLayout'; +import LoginPage from '@/pages/auth/login-page'; +import SignupPage from '@/pages/auth/signup-page'; +import ForgotPasswordPage from '@/pages/auth/forgot-password-page'; +import LoggingOutPage from '@/pages/auth/logging-out'; +import AuthenticatingPage from '@/pages/auth/authenticating'; +import { Navigate } from 'react-router-dom'; +import VerifyResetEmailPage from '@/pages/auth/verify-reset-email'; +import { Suspense } from 'react'; +import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallback'; + +const authRoutes = [ + { + path: '/auth', + element: ( + + ), + children: [ + { + path: '', + element: , + }, + { + path: 'login', + element: , + }, + { + path: 'signup', + element: , + }, + { + path: 'forgot-password', + element: , + }, + { + path: 'logging-out', + element: , + }, + { + path: 'authenticating', + element: , + }, + { + path: 'verify-reset-email/:user/:hash', + element: , + }, + ], + }, +]; + +export default authRoutes; diff --git a/worklenz-frontend/src/app/routes/index.tsx b/worklenz-frontend/src/app/routes/index.tsx new file mode 100644 index 00000000..7d6d6826 --- /dev/null +++ b/worklenz-frontend/src/app/routes/index.tsx @@ -0,0 +1,230 @@ +import { createBrowserRouter, Navigate, RouteObject, useLocation } from 'react-router-dom'; +import rootRoutes from './root-routes'; +import authRoutes from './auth-routes'; +import mainRoutes, { licenseExpiredRoute } from './main-routes'; +import notFoundRoute from './not-found-route'; +import accountSetupRoute from './account-setup-routes'; +import reportingRoutes from './reporting-routes'; +import { useAuthService } from '@/hooks/useAuth'; +import { AuthenticatedLayout } from '@/layouts/AuthenticatedLayout'; +import ErrorBoundary from '@/components/ErrorBoundary'; +import NotFoundPage from '@/pages/404-page/404-page'; +import { ISUBSCRIPTION_TYPE } from '@/shared/constants'; +import LicenseExpired from '@/pages/license-expired/license-expired'; + +interface GuardProps { + children: React.ReactNode; +} + +export const AuthGuard = ({ children }: GuardProps) => { + const isAuthenticated = useAuthService().isAuthenticated(); + const location = useLocation(); + + if (!isAuthenticated) { + return ; + } + + return <>{children}; +}; + +export const AdminGuard = ({ children }: GuardProps) => { + const isAuthenticated = useAuthService().isAuthenticated(); + const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin(); + const currentSession = useAuthService().getCurrentSession(); + const isFreePlan = currentSession?.subscription_type === ISUBSCRIPTION_TYPE.FREE; + const location = useLocation(); + + if (!isAuthenticated) { + return ; + } + + if (!isOwnerOrAdmin || isFreePlan) { + return ; + } + + return <>{children}; +}; + +export const LicenseExpiryGuard = ({ children }: GuardProps) => { + const isAuthenticated = useAuthService().isAuthenticated(); + const currentSession = useAuthService().getCurrentSession(); + const location = useLocation(); + const isAdminCenterRoute = location.pathname.includes('/worklenz/admin-center'); + const isLicenseExpiredRoute = location.pathname === '/worklenz/license-expired'; + + // Don't check or redirect if we're already on the license-expired page + if (isLicenseExpiredRoute) { + return <>{children}; + } + + // Check if trial is expired more than 7 days or if is_expired flag is set + const isLicenseExpiredMoreThan7Days = () => { + // Quick bail if no session data is available + if (!currentSession) { + return false; + } + + // Check is_expired flag first + if (currentSession.is_expired) { + // If no trial_expire_date exists but is_expired is true, defer to backend check + if (!currentSession.trial_expire_date) { + return true; + } + + // If there is a trial_expire_date, check if it's more than 7 days past + const today = new Date(); + const expiryDate = new Date(currentSession.trial_expire_date); + const diffTime = today.getTime() - expiryDate.getTime(); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + + // Redirect if more than 7 days past expiration + return diffDays > 7; + } + + // If not marked as expired but has trial_expire_date, do a date check + if (currentSession.subscription_type === ISUBSCRIPTION_TYPE.TRIAL && currentSession.trial_expire_date) { + const today = new Date(); + const expiryDate = new Date(currentSession.trial_expire_date); + + const diffTime = today.getTime() - expiryDate.getTime(); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + + // If expired more than 7 days, redirect + return diffDays > 7; + } + + // No expiration data found + return false; + }; + + // Add this explicit check and log the result + const shouldRedirect = isAuthenticated && isLicenseExpiredMoreThan7Days() && !isAdminCenterRoute; + if (shouldRedirect) { + return ; + } + + return <>{children}; +}; + +export const SetupGuard = ({ children }: GuardProps) => { + const isAuthenticated = useAuthService().isAuthenticated(); + const location = useLocation(); + + if (!isAuthenticated) { + return ; + } + + return <>{children}; +}; + +// Helper to wrap routes with guards +const wrapRoutes = ( + routes: RouteObject[], + Guard: React.ComponentType<{ children: React.ReactNode }> +): RouteObject[] => { + return routes.map(route => { + const wrappedRoute = { + ...route, + element: {route.element}, + }; + + if (route.children) { + wrappedRoute.children = wrapRoutes(route.children, Guard); + } + + if (route.index) { + delete wrappedRoute.children; + } + + return wrappedRoute; + }); +}; + +// Static license expired component that doesn't rely on translations or authentication +const StaticLicenseExpired = () => { + + return ( +
    +
    +

    + Your Worklenz trial has expired! +

    +

    + Please upgrade now to continue using Worklenz. +

    + +
    +
    + ); +}; + +const publicRoutes = [ + ...rootRoutes, + ...authRoutes, + notFoundRoute +]; +const protectedMainRoutes = wrapRoutes(mainRoutes, AuthGuard); +const adminRoutes = wrapRoutes(reportingRoutes, AdminGuard); +const setupRoutes = wrapRoutes([accountSetupRoute], SetupGuard); + +// Apply LicenseExpiryGuard to all protected routes +const withLicenseExpiryCheck = (routes: RouteObject[]): RouteObject[] => { + return routes.map(route => { + const wrappedRoute = { + ...route, + element: {route.element}, + }; + + if (route.children) { + wrappedRoute.children = withLicenseExpiryCheck(route.children); + } + + return wrappedRoute; + }); +}; + +const licenseCheckedMainRoutes = withLicenseExpiryCheck(protectedMainRoutes); + +const router = createBrowserRouter([ + { + element: , + errorElement: , + children: [ + ...licenseCheckedMainRoutes, + ...adminRoutes, + ...setupRoutes, + licenseExpiredRoute, + ], + }, + ...publicRoutes, +]); + +export default router; diff --git a/worklenz-frontend/src/app/routes/main-routes.tsx b/worklenz-frontend/src/app/routes/main-routes.tsx new file mode 100644 index 00000000..52826bf3 --- /dev/null +++ b/worklenz-frontend/src/app/routes/main-routes.tsx @@ -0,0 +1,64 @@ +import { RouteObject } from 'react-router-dom'; +import MainLayout from '@/layouts/MainLayout'; +import HomePage from '@/pages/home/home-page'; +import ProjectList from '@/pages/projects/project-list'; +import settingsRoutes from './settings-routes'; +import adminCenterRoutes from './admin-center-routes'; +import Schedule from '@/pages/schedule/schedule'; +import ProjectTemplateEditView from '@/pages/settings/project-templates/projectTemplateEditView/ProjectTemplateEditView'; +import LicenseExpired from '@/pages/license-expired/license-expired'; +import ProjectView from '@/pages/projects/projectView/project-view'; +import Unauthorized from '@/pages/unauthorized/unauthorized'; +import { useAuthService } from '@/hooks/useAuth'; +import { Navigate, useLocation } from 'react-router-dom'; + +// Define AdminGuard component first +const AdminGuard = ({ children }: { children: React.ReactNode }) => { + const isAuthenticated = useAuthService().isAuthenticated(); + const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin(); + const location = useLocation(); + + if (!isAuthenticated) { + return ; + } + + if (!isOwnerOrAdmin) { + return ; + } + + return <>{children}; +}; + +const mainRoutes: RouteObject[] = [ + { + path: '/worklenz', + element: , + children: [ + { path: 'home', element: }, + { path: 'projects', element: }, + { + path: 'schedule', + element: + }, + { path: `projects/:projectId`, element: }, + { + path: `settings/project-templates/edit/:templateId/:templateName`, + element: , + }, + { path: 'unauthorized', element: }, + ...settingsRoutes, + ...adminCenterRoutes, + ], + }, +]; + +// License expired route should be separate to avoid being wrapped in LicenseExpiryGuard +export const licenseExpiredRoute: RouteObject = { + path: '/worklenz', + element: , + children: [ + { path: 'license-expired', element: } + ] +}; + +export default mainRoutes; diff --git a/worklenz-frontend/src/app/routes/not-found-route.tsx b/worklenz-frontend/src/app/routes/not-found-route.tsx new file mode 100644 index 00000000..4059609f --- /dev/null +++ b/worklenz-frontend/src/app/routes/not-found-route.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { RouteObject } from 'react-router-dom'; +import NotFoundPage from '@/pages/404-page/404-page'; + +const notFoundRoute: RouteObject = { + path: '*', + element: , +}; + +export default notFoundRoute; diff --git a/worklenz-frontend/src/app/routes/protected-routes.tsx b/worklenz-frontend/src/app/routes/protected-routes.tsx new file mode 100644 index 00000000..3c5a3980 --- /dev/null +++ b/worklenz-frontend/src/app/routes/protected-routes.tsx @@ -0,0 +1,21 @@ +import { Navigate, useLocation } from 'react-router-dom'; + +interface ProtectedRouteProps { + children: React.ReactNode; + isAllowed: boolean; + redirectPath?: string; +} + +const ProtectedRoute = ({ + children, + isAllowed, + redirectPath = '/worklenz/login', +}: ProtectedRouteProps) => { + const location = useLocation(); + + if (!isAllowed) { + return ; + } + + return <>{children}; +}; diff --git a/worklenz-frontend/src/app/routes/reporting-routes.tsx b/worklenz-frontend/src/app/routes/reporting-routes.tsx new file mode 100644 index 00000000..01f83e9b --- /dev/null +++ b/worklenz-frontend/src/app/routes/reporting-routes.tsx @@ -0,0 +1,28 @@ +import { RouteObject } from 'react-router-dom'; +import ReportingLayout from '@/layouts/ReportingLayout'; +import { ReportingMenuItems, reportingsItems } from '@/lib/reporting/reporting-constants'; + +// function to flatten nested menu items +const flattenItems = (items: ReportingMenuItems[]): ReportingMenuItems[] => { + return items.reduce((acc, item) => { + if (item.children) { + return [...acc, ...flattenItems(item.children)]; + } + return [...acc, item]; + }, []); +}; + +const flattenedItems = flattenItems(reportingsItems); + +const reportingRoutes: RouteObject[] = [ + { + path: 'worklenz/reporting', + element: , + children: flattenedItems.map(item => ({ + path: item.endpoint, + element: item.element, + })), + }, +]; + +export default reportingRoutes; diff --git a/worklenz-frontend/src/app/routes/root-routes.tsx b/worklenz-frontend/src/app/routes/root-routes.tsx new file mode 100644 index 00000000..2b903787 --- /dev/null +++ b/worklenz-frontend/src/app/routes/root-routes.tsx @@ -0,0 +1,10 @@ +import { Navigate, RouteObject } from 'react-router-dom'; + +const rootRoutes: RouteObject[] = [ + { + path: '/', + element: , + }, +]; + +export default rootRoutes; diff --git a/worklenz-frontend/src/app/routes/settings-routes.tsx b/worklenz-frontend/src/app/routes/settings-routes.tsx new file mode 100644 index 00000000..39468efb --- /dev/null +++ b/worklenz-frontend/src/app/routes/settings-routes.tsx @@ -0,0 +1,32 @@ +import { RouteObject } from 'react-router-dom'; +import { Navigate } from 'react-router-dom'; +import SettingsLayout from '@/layouts/SettingsLayout'; +import { settingsItems } from '@/lib/settings/settings-constants'; +import { useAuthService } from '@/hooks/useAuth'; + +const SettingsGuard = ({ children, adminRequired }: { children: React.ReactNode; adminRequired: boolean }) => { + const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin(); + + if (adminRequired && !isOwnerOrAdmin) { + return ; + } + + return <>{children}; +}; + +const settingsRoutes: RouteObject[] = [ + { + path: 'settings', + element: , + children: settingsItems.map(item => ({ + path: item.endpoint, + element: ( + + {item.element} + + ), + })), + }, +]; + +export default settingsRoutes; diff --git a/worklenz-frontend/src/app/services/api/access-controls-api.service.ts b/worklenz-frontend/src/app/services/api/access-controls-api.service.ts deleted file mode 100644 index 0ef5a6c4..00000000 --- a/worklenz-frontend/src/app/services/api/access-controls-api.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; - -@Injectable({ - providedIn: 'root' -}) -export class AccessControlsApiService extends APIServiceBase { -} diff --git a/worklenz-frontend/src/app/services/api/account-center-api.service.ts b/worklenz-frontend/src/app/services/api/account-center-api.service.ts deleted file mode 100644 index 4fe66caf..00000000 --- a/worklenz-frontend/src/app/services/api/account-center-api.service.ts +++ /dev/null @@ -1,90 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IClient} from "@interfaces/client"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import { - IBillingAccountInfo, - IBillingAccountStorage, - IBillingChargesResponse, - IBillingConfiguration, - IBillingConfigurationCountry, - IBillingModifier, - IBillingTransaction, - IOrganization, - IOrganizationTeam, - IOrganizationUser, - IPricingPlans, - IStorageInfo, - IUpgradeSubscriptionPlanResponse -} from "@interfaces/account-center"; -import {toQueryString} from "@shared/utils"; -import {IOrganizationUsersGetRequest} from "@interfaces/api-models/organization-users-get-request"; -import {IOrganizationTeamGetRequest} from "@interfaces/api-models/organization-team-get-request"; - -@Injectable({ - providedIn: 'root' -}) -export class AccountCenterApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/admin-center`; - - constructor( - private http: HttpClient - ) { - super(); - } - - getOrganizationName(): Promise> { - return this._get(this.http, `${this.root}/organization`); - } - - getOrganizationAdmins(): Promise> { - return this._get(this.http, `${this.root}/organization/admins`); - } - - updateOrganizationName(body: IClient): Promise> { - return this._put(this.http, `${this.root}/organization`, body); - } - - updateOwnerContactNumber(body: { contact_number: string }): Promise> { - return this._put(this.http, `${this.root}/organization/owner/contact-number`, body); - } - - getOrganizationUsers(index: number, size: number, field: string | null, order: string | null, search: string | null): Promise> { - const s = encodeURIComponent(search || ''); - return this._get(this.http, `${this.root}/organization/users${toQueryString({ - index, - size, - field, - order, - search: s - })}`); - } - - getOrganizationTeams(index: number, size: number, field: string | null, order: string | null, search: string | null): Promise> { - const s = encodeURIComponent(search || ''); - return this._get(this.http, `${this.root}/organization/teams${toQueryString({ - index, - size, - field, - order, - search: s - })}`); - } - - getOrganizationTeam(team_id: string): Promise> { - return this._get(this.http, `${this.root}/organization/team/${team_id}`); - } - - updateTeam(team_id: string, team_members: IOrganizationUser[]): Promise> { - return this._put(this.http, `${this.root}/organization/team/${team_id}`, team_members); - } - - deleteTeam(id: string): Promise> { - return this._delete(this.http, `${this.root}/organization/team/${id}`); - } - - removeTeamMember(team_member_id: string, team_id: string): Promise> { - return this._put(this.http, `${this.root}/organization/team-member/${team_member_id}`, {teamId: team_id}) - } -} diff --git a/worklenz-frontend/src/app/services/api/activity-logs.service.spec.ts b/worklenz-frontend/src/app/services/api/activity-logs.service.spec.ts deleted file mode 100644 index ad349eaf..00000000 --- a/worklenz-frontend/src/app/services/api/activity-logs.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {TestBed} from '@angular/core/testing'; - -import {ActivityLogsService} from './activity-logs.service'; - -describe('ActivityLogsService', () => { - let service: ActivityLogsService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(ActivityLogsService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/services/api/activity-logs.service.ts b/worklenz-frontend/src/app/services/api/activity-logs.service.ts deleted file mode 100644 index 7343a06c..00000000 --- a/worklenz-frontend/src/app/services/api/activity-logs.service.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {Injectable} from '@angular/core'; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IActivityLogsResponse} from "@interfaces/api-models/activity-logs-get-response"; - -@Injectable({ - providedIn: 'root' -}) -export class ActivityLogsService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/activity-logs`; - - constructor( - private http: HttpClient - ) { - super(); - } - - getActivityLogs(task_id: string): Promise> { - return this._get(this.http, `${this.root}/${task_id}`); - } -} diff --git a/worklenz-frontend/src/app/services/api/api-service-base.ts b/worklenz-frontend/src/app/services/api/api-service-base.ts deleted file mode 100644 index 328a8ff1..00000000 --- a/worklenz-frontend/src/app/services/api/api-service-base.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {lastValueFrom} from "rxjs"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; - -export abstract class APIServiceBase { - protected readonly API_BASE_URL = "/api/v1"; - protected readonly AUTH_API_BASE_URL = "/secure"; - - protected _post(http: HttpClient, url: string, data?: any, options?: any): Promise> | any { - return lastValueFrom(http.post>(url, data || null, options)); - } - - protected _get(http: HttpClient, url: string): Promise> { - return lastValueFrom(http.get>(url)); - } - - protected _put(http: HttpClient, url: string, data?: any): Promise> { - return lastValueFrom(http.put>(url, data || null)); - } - - protected _delete(http: HttpClient, url: string): Promise> { - return lastValueFrom(http.delete>(url)); - } -} diff --git a/worklenz-frontend/src/app/services/api/attachments-api.service.ts b/worklenz-frontend/src/app/services/api/attachments-api.service.ts deleted file mode 100644 index 56fe9271..00000000 --- a/worklenz-frontend/src/app/services/api/attachments-api.service.ts +++ /dev/null @@ -1,47 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {lastValueFrom} from "rxjs"; -import {ITaskAttachment} from "@interfaces/api-models/task-attachment"; -import { - IProjectAttachmentsViewModel, - ITaskAttachmentViewModel -} from "@interfaces/api-models/task-attachment-view-model"; -import {IAvatarAttachment} from "@interfaces/avatar-attachment"; -import {toQueryString} from "@shared/utils"; - -@Injectable({ - providedIn: 'root' -}) -export class AttachmentsApiService extends APIServiceBase { - constructor( - private http: HttpClient - ) { - super(); - } - - createTaskAttachment(body: ITaskAttachment): Promise> { - return this._post(this.http, `${this.API_BASE_URL}/attachments/tasks`, body); - } - - createAvatarAttachment(body: IAvatarAttachment): Promise> { - return this._post(this.http, `${this.API_BASE_URL}/attachments/avatar`, body); - } - - getTaskAttachment(taskId: string): Promise> { - return this._get(this.http, `${this.API_BASE_URL}/attachments/tasks/${taskId}`); - } - - getProjectAttachment(projectId: string, index: number, size: number ): Promise> { - return this._get(this.http, `${this.API_BASE_URL}/attachments/project/${projectId}${toQueryString({index, size})}`); - } - - deleteTaskAttachment(id: string): Promise> { - return lastValueFrom(this.http.delete>(`${this.API_BASE_URL}/attachments/tasks/${id}`)); - } - - download(id: string, filename: string): Promise> { - return this._get(this.http, `${this.API_BASE_URL}/attachments/download?id=${id}&file=${filename}`); - } -} diff --git a/worklenz-frontend/src/app/services/api/auth-api.service.ts b/worklenz-frontend/src/app/services/api/auth-api.service.ts deleted file mode 100644 index a519cf6c..00000000 --- a/worklenz-frontend/src/app/services/api/auth-api.service.ts +++ /dev/null @@ -1,57 +0,0 @@ -import {Injectable} from '@angular/core'; -import {lastValueFrom} from 'rxjs'; -import {HttpClient} from '@angular/common/http'; - -import {APIServiceBase} from './api-service-base'; -import {IUserLoginRequest} from '@interfaces/api-models/user-login-request'; -import {IServerResponse} from '@interfaces/api-models/server-response'; -import {IUserLoginResponse} from '@interfaces/api-models/user-login-response'; -import {IUserSignUpRequest} from '@interfaces/api-models/user-sign-up-request'; -import {IAuthorizeResponse} from '@interfaces/api-models/authorize-response'; -import {IResetPasswordRequest} from '@interfaces/api-models/reset-password-request'; -import {IUpdatePasswordRequest} from "@interfaces/api-models/verify-reset-email"; -import {IPasswordValidityResult} from "@interfaces/password-validity-result"; - -@Injectable({ - providedIn: 'root' -}) -export class AuthApiService extends APIServiceBase { - constructor( - private http: HttpClient - ) { - super(); - } - - public login(body: IUserLoginRequest): Promise> { - return this._post(this.http, `${this.AUTH_API_BASE_URL}/login`, body); - } - - public signup(body: IUserSignUpRequest): Promise { - body.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; - return lastValueFrom(this.http.post(`${this.AUTH_API_BASE_URL}/signup`, body)); - } - - public signupCheck(body: IUserSignUpRequest): Promise> { - return this._post(this.http, `${this.AUTH_API_BASE_URL}/signup/check`, body); - } - - public logout(): Promise> { - return this._get(this.http, `${this.AUTH_API_BASE_URL}/logout`); - } - - public checkPasswordStrength(password: string): Promise> { - return this._get(this.http, `${this.AUTH_API_BASE_URL}/check-password?password=${password}`); - } - - public authorize(): Promise { - return lastValueFrom(this.http.get(`${this.AUTH_API_BASE_URL}/verify`)); - } - - public resetPassword(body: IResetPasswordRequest): Promise> { - return this._post(this.http, `${this.AUTH_API_BASE_URL}/reset-password`, body); - } - - public updateNewPassword(body: IUpdatePasswordRequest): Promise> { - return this._post(this.http, `${this.AUTH_API_BASE_URL}/update-password`, body); - } -} diff --git a/worklenz-frontend/src/app/services/api/clients-api.service.ts b/worklenz-frontend/src/app/services/api/clients-api.service.ts deleted file mode 100644 index fe68b319..00000000 --- a/worklenz-frontend/src/app/services/api/clients-api.service.ts +++ /dev/null @@ -1,43 +0,0 @@ -import {Injectable} from '@angular/core'; -import {HttpClient} from "@angular/common/http"; -import {lastValueFrom} from "rxjs"; - -import {APIServiceBase} from "./api-service-base"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {IClient} from "@interfaces/client"; -import {IClientsViewModel} from "@interfaces/api-models/clients-view-model"; -import {toQueryString} from "@shared/utils"; - -@Injectable({ - providedIn: 'root' -}) -export class ClientsApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/clients`; - - constructor( - private http: HttpClient - ) { - super(); - } - - create(body: IClient): Promise> { - return this._post(this.http, this.root, body); - } - - get(index: number, size: number, field: string | null, order: string | null, search: string | null): Promise> { - const s = encodeURIComponent(search || ''); - return this._get(this.http, `${this.root}${toQueryString({index, size, field, order, search: s})}`); - } - - getById(id: string): Promise> { - return this._get(this.http, `${this.root}/${id}`); - } - - update(id: string, body: IClient): Promise> { - return this._put(this.http, `${this.root}/${id}`, body); - } - - delete(id: string): Promise> { - return lastValueFrom(this.http.delete>(`${this.root}/${id}`)); - } -} diff --git a/worklenz-frontend/src/app/services/api/gantt-api.service.spec.ts b/worklenz-frontend/src/app/services/api/gantt-api.service.spec.ts deleted file mode 100644 index cf8ce568..00000000 --- a/worklenz-frontend/src/app/services/api/gantt-api.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {TestBed} from '@angular/core/testing'; - -import {GanttApiService} from './gantt-api.service'; - -describe('GanttApiService', () => { - let service: GanttApiService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(GanttApiService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/services/api/gantt-api.service.ts b/worklenz-frontend/src/app/services/api/gantt-api.service.ts deleted file mode 100644 index aa260f61..00000000 --- a/worklenz-frontend/src/app/services/api/gantt-api.service.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {Injectable} from '@angular/core'; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IEventMarker, IGanttRoadMapTask, IProjectPhaseLabel} from "@interfaces/api-models/gantt"; - -@Injectable({ - providedIn: 'root' -}) -export class GanttApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/gantt`; - - constructor(private http: HttpClient) { - super(); - } - - getGanttTasksByProject(project_id: string): Promise> { - return this._get(this.http, `${this.root}/project-roadmap?project_id=${project_id}`); - } - - getProjectPhaseLabel(project_id: string): Promise> { - return this._get(this.http, `${this.root}/project-phase-label?project_id=${project_id}`); - } - - getProjectPhases(project_id: string): Promise> { - return this._get(this.http, `${this.root}/project-phases/${project_id}`); - } - - getWorkloadData(project_id: string): Promise> { - return this._get(this.http, `${this.root}/project-workload?project_id=${project_id}`); - } -} diff --git a/worklenz-frontend/src/app/services/api/home-page-api.service.ts b/worklenz-frontend/src/app/services/api/home-page-api.service.ts deleted file mode 100644 index 75c1f973..00000000 --- a/worklenz-frontend/src/app/services/api/home-page-api.service.ts +++ /dev/null @@ -1,55 +0,0 @@ -import {HttpClient} from '@angular/common/http'; -import {Injectable} from '@angular/core'; -import {IServerResponse} from '@interfaces/api-models/server-response'; -import {toQueryString} from '@shared/utils'; -import {APIServiceBase} from './api-service-base'; -import {IMyTask} from "@interfaces/my-tasks"; -import {IHomeTasksConfig, IHomeTasksModel, IPersonalTask} from "../../administrator/my-dashboard/intefaces"; -import {IProject} from "@interfaces/project"; -import {IProjectViewModel} from "@interfaces/api-models/project-view-model"; - -@Injectable({ - providedIn: 'root' -}) -export class HomePageApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/home`; - - constructor(private http: HttpClient) { - super(); - } - - createPersonalTask(body: IPersonalTask): Promise> { - return this._post(this.http, `${this.root}/personal-task`, body); - } - - getMyTasks(config: IHomeTasksConfig): Promise> { - const group_by = config.tasks_group_by; - const current_tab = config.current_tab; - const is_calendar_view = config.is_calendar_view; - const selected_date = config.selected_date?.toISOString().split('T')[0]; - const time_zone = config.time_zone; - - const url = `${this.root}/tasks${toQueryString({group_by, current_tab, is_calendar_view, selected_date, time_zone})}`; - return this._get(this.http, url); - } - - getPersonalTasks(): Promise> { - const url = `${this.root}/personal-tasks`; - return this._get(this.http, url); - } - - getProjects(view: number): Promise> { - const url = `${this.root}/projects${toQueryString({view})}`; - return this._get(this.http, url); - } - - getProjectsByTeam(): Promise> { - const url = `${this.root}/team-projects`; - return this._get(this.http, url); - } - - taskMarkAsDone(id: string): Promise> { - return this._put(this.http, `${this.root}/update-personal-task`, {id}); - } - -} diff --git a/worklenz-frontend/src/app/services/api/job-titles-api.service.ts b/worklenz-frontend/src/app/services/api/job-titles-api.service.ts deleted file mode 100644 index 47841a9d..00000000 --- a/worklenz-frontend/src/app/services/api/job-titles-api.service.ts +++ /dev/null @@ -1,44 +0,0 @@ -import {Injectable} from '@angular/core'; -import {lastValueFrom} from "rxjs"; -import {HttpClient} from "@angular/common/http"; - -import {APIServiceBase} from "./api-service-base"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {IJobTitle} from "@interfaces/job-title"; -import {IJobTitlesViewModel} from "@interfaces/api-models/job-titles-view-model"; -import {toQueryString} from "@shared/utils"; - -@Injectable({ - providedIn: 'root' -}) -export class JobTitlesApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/job-titles`; - - constructor( - private http: HttpClient - ) { - super(); - } - - create(body: IJobTitle): Promise> { - return this._post(this.http, this.root, body); - } - - get(index: number, size: number, field: string | null, order: string | null, search: string | null): Promise> { - const s = encodeURIComponent(search || ''); - const url = `${this.root}${toQueryString({index, size, field, order, search: s})}`; - return this._get(this.http, url); - } - - getById(id: string): Promise> { - return this._get(this.http, `${this.root}/${id}`); - } - - update(id: string, body: IJobTitle): Promise> { - return this._put(this.http, `${this.root}/${id}`, body); - } - - delete(id: string): Promise> { - return lastValueFrom(this.http.delete>(`${this.root}/${id}`)); - } -} diff --git a/worklenz-frontend/src/app/services/api/logs-api.service.ts b/worklenz-frontend/src/app/services/api/logs-api.service.ts deleted file mode 100644 index cee2da73..00000000 --- a/worklenz-frontend/src/app/services/api/logs-api.service.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {IActivityLog} from "@interfaces/personal-overview"; - -@Injectable({ - providedIn: 'root' -}) -export class LogsApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/logs`; - - constructor( - private http: HttpClient - ) { - super(); - } - - getActivityLog(): Promise> { - return this._get(this.http, `${this.root}/my-dashboard`); - } -} diff --git a/worklenz-frontend/src/app/services/api/notifications-api.service.ts b/worklenz-frontend/src/app/services/api/notifications-api.service.ts deleted file mode 100644 index 79b823f9..00000000 --- a/worklenz-frontend/src/app/services/api/notifications-api.service.ts +++ /dev/null @@ -1,37 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {IWorklenzNotification} from "@interfaces/worklenz-notification"; -import {toQueryString} from "@shared/utils"; - -@Injectable({ - providedIn: 'root' -}) -export class NotificationsApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/notifications`; - - constructor( - private http: HttpClient - ) { - super(); - } - - update(id: string): Promise> { - return this._put(this.http, `${this.root}/${id}`, null); - } - - readAll(): Promise> { - return this._put(this.http, `${this.root}/read-all`, null); - } - - get(filter: string): Promise> { - const q = toQueryString({filter}); - return this._get(this.http, `${this.root}${q}`); - } - - getUnreadCount(): Promise> { - return this._get(this.http, `${this.root}/unread-count`); - } - -} diff --git a/worklenz-frontend/src/app/services/api/personal-overview.service.ts b/worklenz-frontend/src/app/services/api/personal-overview.service.ts deleted file mode 100644 index 81d4fb53..00000000 --- a/worklenz-frontend/src/app/services/api/personal-overview.service.ts +++ /dev/null @@ -1,36 +0,0 @@ -import {Injectable} from '@angular/core'; -import {IServerResponse} from '@interfaces/api-models/server-response'; -import {HttpClient} from '@angular/common/http'; - -import {APIServiceBase} from './api-service-base'; -import {IActivityLogGetRequest} from '@interfaces/api-models/activity-log'; -import {ITasksOverview} from '@interfaces/personal-overview'; - -@Injectable({ - providedIn: 'root' -}) -export class PersonalOverviewService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/personal-overview`; - - constructor( - private http: HttpClient - ) { - super(); - } - - getActivityLog(): Promise> { - return this._get(this.http, this.root); - } - - getTasksDueToday(): Promise> { - return this._get(this.http, `${this.root}/tasks-due-today`); - } - - getRemainingTasks(): Promise> { - return this._get(this.http, `${this.root}/tasks-remaining`); - } - - getTasksOverview(start_date: string, end_date: string): Promise> { - return this._get(this.http, `${this.root}/tasks-overview?start_date=${start_date}&end_date=${end_date}`); - } -} diff --git a/worklenz-frontend/src/app/services/api/profile-settings-api.service.ts b/worklenz-frontend/src/app/services/api/profile-settings-api.service.ts deleted file mode 100644 index 7ec331f2..00000000 --- a/worklenz-frontend/src/app/services/api/profile-settings-api.service.ts +++ /dev/null @@ -1,49 +0,0 @@ -import {Injectable} from '@angular/core'; -import {HttpClient} from "@angular/common/http"; - -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {IProfileSettings} from "@interfaces/profile-settings"; - -import {APIServiceBase} from "@api/api-service-base"; -import {ITeam} from "@interfaces/team"; -import { - IAccountSetupRequest, - IAccountSetupResponse -} from "../../administrator/account-setup/account-setup/account-setup.component"; -import {INotificationSettings} from "@interfaces/notification-settings"; - -@Injectable({ - providedIn: 'root' -}) -export class ProfileSettingsApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/settings`; - - constructor(private http: HttpClient) { - super(); - } - - get(): Promise> { - return this._get(this.http, `${this.root}/profile`); - } - - update(body: IProfileSettings): Promise> { - return this._put(this.http, `${this.root}/profile`, body); - } - - getNotificationSettings(): Promise> { - return this._get(this.http, `${this.root}/notifications`); - } - - updateNotificationSettings(body: INotificationSettings): Promise> { - return this._put(this.http, `${this.root}/notifications`, body); - } - - public setupAccount(body: IAccountSetupRequest): Promise> { - return this._post(this.http, `${this.root}/setup`, body); - } - - updateTeamName(id: string, body: ITeam): Promise> { - return this._put(this.http, `${this.root}/team-name/${id}`, body); - } - -} diff --git a/worklenz-frontend/src/app/services/api/project-categories-api.service.ts b/worklenz-frontend/src/app/services/api/project-categories-api.service.ts deleted file mode 100644 index fc72ba73..00000000 --- a/worklenz-frontend/src/app/services/api/project-categories-api.service.ts +++ /dev/null @@ -1,44 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {IProjectCategory, IProjectCategoryViewModel} from "@interfaces/project-category"; -import {ITaskLabel} from "@interfaces/task-label"; - -@Injectable({ - providedIn: 'root' -}) -export class ProjectCategoriesApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/project-categories`; - - constructor( - private http: HttpClient - ) { - super(); - } - - create(body: IProjectCategory): Promise> { - return this._post(this.http, this.root, body); - } - - get(): Promise> { - return this._get(this.http, `${this.root}`); - } - - getByTeamId(id: string): Promise> { - return this._get(this.http, `${this.root}/${id}`); - } - - getByOrg(): Promise> { - return this._get(this.http, `${this.root}/org-categories`); - } - - updateColor(id: string, color: string): Promise> { - return this._put(this.http, `${this.root}/${id}`, {color}); - } - - deleteById(id: string): Promise> { - return this._delete(this.http, `${this.root}/${id}`); - } -} - diff --git a/worklenz-frontend/src/app/services/api/project-categories.service.spec.ts b/worklenz-frontend/src/app/services/api/project-categories.service.spec.ts deleted file mode 100644 index be511ca5..00000000 --- a/worklenz-frontend/src/app/services/api/project-categories.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {TestBed} from '@angular/core/testing'; - -import {ProjectCategoriesApiService} from './project-categories-api.service'; - -describe('ProjectCategoriesService', () => { - let service: ProjectCategoriesApiService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(ProjectCategoriesApiService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/services/api/project-comments-api.service.ts b/worklenz-frontend/src/app/services/api/project-comments-api.service.ts deleted file mode 100644 index 817eb9a5..00000000 --- a/worklenz-frontend/src/app/services/api/project-comments-api.service.ts +++ /dev/null @@ -1,50 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {lastValueFrom} from "rxjs"; -import {IProjectCommentsCreateRequest} from "@interfaces/api-models/project-comment-create-request"; -import {IProjectMemberViewModel} from "@interfaces/task-form-view-model"; -import {toQueryString} from "@shared/utils"; -import {IMentionMemberViewModel} from "@interfaces/project-comments"; -import {IProjectUpdateCommentViewModel} from "@interfaces/project"; - -@Injectable({ - providedIn: 'root' -}) -export class ProjectCommentsApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/project-comments`; - - constructor( - private http: HttpClient - ) { - super(); - } - - create(body: IProjectCommentsCreateRequest): Promise> { - return this._post(this.http, `${this.root}`, body); - } - - getMembers(projectId: string, index: number, size: number, field: string | null, order: string | null, search: string | null): Promise> { - const s = encodeURIComponent(search || ''); - const url = `${this.root}/project-members/${projectId}${toQueryString({index, size, field, order, search: s})}`; - return this._get(this.http, url); - } - - getCountByProjectId(projectId: string): Promise> { - const url = `${this.root}/comments-count/${projectId}`; - return this._get(this.http, url); - } - - getByProjectId(projectId: string, isLimit: boolean): Promise> { - const url = `${this.root}/project-comments/${projectId}${toQueryString({latest: isLimit})}`; - return this._get(this.http, url); - } - - deleteById(commentId: string): Promise> { - const url = `${this.root}/delete/${commentId}`; - return this._delete(this.http, url); - } - -} - diff --git a/worklenz-frontend/src/app/services/api/project-folders-api.service.spec.ts b/worklenz-frontend/src/app/services/api/project-folders-api.service.spec.ts deleted file mode 100644 index e47f6f05..00000000 --- a/worklenz-frontend/src/app/services/api/project-folders-api.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {TestBed} from '@angular/core/testing'; - -import {ProjectFoldersApiService} from './project-folders-api.service'; - -describe('ProjectFoldersApiService', () => { - let service: ProjectFoldersApiService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(ProjectFoldersApiService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/services/api/project-folders-api.service.ts b/worklenz-frontend/src/app/services/api/project-folders-api.service.ts deleted file mode 100644 index 1e94c3b6..00000000 --- a/worklenz-frontend/src/app/services/api/project-folders-api.service.ts +++ /dev/null @@ -1,39 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {lastValueFrom} from "rxjs"; -import {IProjectFolder} from "@interfaces/project-folder"; - -@Injectable({ - providedIn: 'root' -}) -export class ProjectFoldersApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/projects-folders`; - - constructor( - private readonly http: HttpClient - ) { - super(); - } - - create(body: { name: string; color_code?: string; }): Promise> { - return this._post(this.http, this.root, body); - } - - get(): Promise> { - return this._get(this.http, `${this.root}`); - } - - getById(id: string): Promise> { - return this._get(this.http, `${this.root}/${id}`); - } - - update(id: string, body: IProjectFolder): Promise> { - return this._put(this.http, `${this.root}/${id}`, body); - } - - delete(id: string): Promise> { - return lastValueFrom(this.http.delete>(`${this.root}/${id}`)); - } -} diff --git a/worklenz-frontend/src/app/services/api/project-healths-api.service.ts b/worklenz-frontend/src/app/services/api/project-healths-api.service.ts deleted file mode 100644 index 9d386beb..00000000 --- a/worklenz-frontend/src/app/services/api/project-healths-api.service.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Injectable } from '@angular/core'; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {IProjectStatus} from "@interfaces/project-status"; -import {APIServiceBase} from "@api/api-service-base"; -import {IProjectHealth} from "@interfaces/project-health"; - -@Injectable({ - providedIn: 'root' -}) -export class ProjectHealthsApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/project-healths`; - - constructor( - private http: HttpClient - ) { - super(); - } - - get(): Promise> { - return this._get(this.http, this.root); - } -} diff --git a/worklenz-frontend/src/app/services/api/project-insights.service.ts b/worklenz-frontend/src/app/services/api/project-insights.service.ts deleted file mode 100644 index f04f6f98..00000000 --- a/worklenz-frontend/src/app/services/api/project-insights.service.ts +++ /dev/null @@ -1,72 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import { - IDeadlineTaskStats, - IInsightTasks, - IProjectInsightsGetRequest, - IProjectLogs, - IProjectMemberStats -} from "@interfaces/api-models/project-insights"; - -@Injectable({ - providedIn: 'root' -}) -export class ProjectInsightsService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/project-insights`; - - constructor( - private http: HttpClient - ) { - super(); - } - - getProjectOverviewData(id: string, include_archived: boolean): Promise> { - return this._get(this.http, `${this.root}/${id}?archived=${include_archived}`); - } - - getLastUpdatedTasks(id: string, include_archived: boolean): Promise> { - return this._get(this.http, `${this.root}/last-updated/${id}?archived=${include_archived}`); - } - - getProjectLogs(id: string): Promise> { - return this._get(this.http, `${this.root}/logs/${id}`); - } - - getTaskStatusCounts(id: string, include_archived: boolean): Promise> { - return this._get(this.http, `${this.root}/status-overview/${id}?archived=${include_archived}`); - } - - getPriorityOverview(id: string, include_archived: boolean): Promise> { - return this._get(this.http, `${this.root}/priority-overview/${id}?archived=${include_archived}`); - } - - getOverdueTasks(id: string, include_archived: boolean): Promise> { - return this._get(this.http, `${this.root}/overdue-tasks/${id}?archived=${include_archived}`); - } - - getTasksCompletedEarly(id: string, include_archived: boolean): Promise> { - return this._get(this.http, `${this.root}/early-tasks/${id}?archived=${include_archived}`); - } - - getTasksCompletedLate(id: string, include_archived: boolean): Promise> { - return this._get(this.http, `${this.root}/late-tasks/${id}?archived=${include_archived}`); - } - - getMemberInsightAStats(id: string, include_archived: boolean): Promise> { - return this._get(this.http, `${this.root}/members/stats/${id}?archived=${include_archived}`); - } - - getMemberTasks(body: any): Promise> { - return this._post(this.http, `${this.root}/members/tasks`, body); - } - - getProjectDeadlineStats(id: string, include_archived: boolean): Promise> { - return this._get(this.http, `${this.root}/deadline/${id}?archived=${include_archived}`); - } - - getOverloggedTasks(id: string, include_archived: boolean): Promise> { - return this._get(this.http, `${this.root}/overlogged-tasks/${id}?archived=${include_archived}`); - } -} diff --git a/worklenz-frontend/src/app/services/api/project-managers-api.service.ts b/worklenz-frontend/src/app/services/api/project-managers-api.service.ts deleted file mode 100644 index c0de0770..00000000 --- a/worklenz-frontend/src/app/services/api/project-managers-api.service.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Injectable } from '@angular/core'; -import {HttpClient} from "@angular/common/http"; -import {APIServiceBase} from "@api/api-service-base"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {IProjectCategory} from "@interfaces/project-category"; -import {IProjectManager} from "@interfaces/project-manager"; - -@Injectable({ - providedIn: 'root' -}) -export class ProjectManagersApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/project-managers`; - - constructor( - private http: HttpClient - ) { - super(); - } - - get(): Promise> { - return this._get(this.http, `${this.root}`); - } - -} diff --git a/worklenz-frontend/src/app/services/api/project-members-api.service.ts b/worklenz-frontend/src/app/services/api/project-members-api.service.ts deleted file mode 100644 index 7dd27e11..00000000 --- a/worklenz-frontend/src/app/services/api/project-members-api.service.ts +++ /dev/null @@ -1,38 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {lastValueFrom} from "rxjs"; -import {IProjectMemberViewModel} from "@interfaces/task-form-view-model"; -import {toQueryString} from "@shared/utils"; - -@Injectable({ - providedIn: 'root' -}) -export class ProjectMembersApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/project-members`; - - constructor( - private http: HttpClient - ) { - super(); - } - - create(body: IProjectMemberViewModel): Promise> { - const q = toQueryString({current_project_id: body.project_id}); - return this._post(this.http, `${this.root}${q}`, body); - } - - createByEmail(body: { project_id: string; email: string; }): Promise> { - return this._post(this.http, `${this.root}/invite`, body); - } - - getByProjectId(id: string): Promise> { - return this._get(this.http, `${this.root}/${id}`); - } - - deleteById(id: string, currentProjectId: string): Promise> { - const q = toQueryString({current_project_id: currentProjectId}) - return lastValueFrom(this.http.delete>(`${this.root}/${id}${q}`)); - } -} diff --git a/worklenz-frontend/src/app/services/api/project-roadmap-api.service.ts b/worklenz-frontend/src/app/services/api/project-roadmap-api.service.ts deleted file mode 100644 index 90ee7abf..00000000 --- a/worklenz-frontend/src/app/services/api/project-roadmap-api.service.ts +++ /dev/null @@ -1,31 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {ITaskListConfigV2, ITaskListGroup} from "../../administrator/modules/task-list-v2/interfaces"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {toQueryString} from "@shared/utils"; -import {IDateCreateResponse} from "@interfaces/workload"; -import {IRoadmapConfigV2} from "@interfaces/roadmap"; - -@Injectable({ - providedIn: 'root' -}) -export class ProjectRoadmapApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/roadmap-gannt`; - - constructor(private http: HttpClient) { - super(); - } - - getGanntDates(id: string, timeZone: string): Promise> { - const q = toQueryString({timeZone: timeZone}) - return this._get(this.http, `${this.root}/chart-dates/${id}${q}`); - } - - getTaskGroups(config: IRoadmapConfigV2): Promise> { - const q = toQueryString(config); - return this._get(this.http, `${this.root}/task-groups/${config.id}${q}`); - } - -} diff --git a/worklenz-frontend/src/app/services/api/project-statuses-api.service.ts b/worklenz-frontend/src/app/services/api/project-statuses-api.service.ts deleted file mode 100644 index 0459fba2..00000000 --- a/worklenz-frontend/src/app/services/api/project-statuses-api.service.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {IProjectStatus} from "@interfaces/project-status"; - -@Injectable({ - providedIn: 'root' -}) -export class ProjectStatusesApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/project-statuses`; - - constructor( - private http: HttpClient - ) { - super(); - } - - get(): Promise> { - return this._get(this.http, this.root); - } -} diff --git a/worklenz-frontend/src/app/services/api/project-template-api.service.ts b/worklenz-frontend/src/app/services/api/project-template-api.service.ts deleted file mode 100644 index e1644cd9..00000000 --- a/worklenz-frontend/src/app/services/api/project-template-api.service.ts +++ /dev/null @@ -1,61 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {ICustomProjectTemplateCreateRequest} from "@interfaces/project-template"; -import {lastValueFrom} from "rxjs"; -import {IProjectTemplate, IWorklenzTemplate} from "@interfaces/api-models/project-template"; -import { - IAccountSetupRequest, - IAccountSetupResponse -} from "../../administrator/account-setup/account-setup/account-setup.component"; - -@Injectable({ - providedIn: 'root' -}) -export class ProjectTemplateApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/project-templates`; - - constructor( - private http: HttpClient - ) { - super(); - } - - createCustomTemplate(body: ICustomProjectTemplateCreateRequest): Promise> { - return this._post(this.http, `${this.root}/custom-template`, body); - } - - createFromTemplate(body: { template_id: string }): Promise> { - return this._post(this.http, `${this.root}/import-template`, body); - } - - createFromCustomTemplate(body: { template_id: string }): Promise> { - return this._post(this.http, `${this.root}/import-custom-template`, body); - } - - createTemplates(): Promise> { - return this._get(this.http, `${this.root}/create`); - } - - getWorklenzTemplates(): Promise> { - return this._get(this.http, `${this.root}/worklenz-templates`); - } - - getWorklenzTemplateById(templateId: string): Promise> { - return this._get(this.http, `${this.root}/worklenz-templates/${templateId}`); - } - - getWorklenzCustomTemplates(): Promise> { - return this._get(this.http, `${this.root}/custom-templates`); - } - - delete(id: string): Promise> { - return lastValueFrom(this.http.delete>(`${this.root}/${id}`)); - } - - setupAccount(body: IAccountSetupRequest): Promise> { - return this._post(this.http, `${this.root}/setup`, body); - } - -} diff --git a/worklenz-frontend/src/app/services/api/project-workload-api.service.ts b/worklenz-frontend/src/app/services/api/project-workload-api.service.ts deleted file mode 100644 index 77ceaf76..00000000 --- a/worklenz-frontend/src/app/services/api/project-workload-api.service.ts +++ /dev/null @@ -1,46 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import { - IDateCreateResponse, - IWLMember, - IWLMemberOverview, IWLMemberOverviewResponse, - IWLTaskListGroup, - IWLTasksConfig -} from "@interfaces/workload"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; -import {toQueryString} from "@shared/utils"; - -@Injectable({ - providedIn: 'root' -}) -export class ProjectWorkloadApiService extends APIServiceBase { - - private readonly root = `${this.API_BASE_URL}/workload-gannt`; - - constructor(private http: HttpClient) { - super(); - } - - getGanntDates(id: string, timeZone: string): Promise> { - const q = toQueryString({timeZone: timeZone}) - return this._get(this.http, `${this.root}/chart-dates/${id}${q}`); - } - - getWorkloadMembers(project_id: string, timeZone: string): Promise> { - const q = toQueryString({timeZone: timeZone}) - return this._get(this.http, `${this.root}/workload-members/${project_id}${q}`); - } - - getTasksByMember(config: IWLTasksConfig): Promise> { - const q = toQueryString(config); - return this._get(this.http, `${this.root}/workload-tasks-by-member/${config.id}${q}`); - } - - getMemberOverview(project_id: string, team_member_id: string): Promise> { - const q = toQueryString({team_member_id}) - return this._get(this.http, `${this.root}/workload-overview-by-member/${project_id}${q}`); - } - -} diff --git a/worklenz-frontend/src/app/services/api/projects-api.service.ts b/worklenz-frontend/src/app/services/api/projects-api.service.ts deleted file mode 100644 index b4b437d5..00000000 --- a/worklenz-frontend/src/app/services/api/projects-api.service.ts +++ /dev/null @@ -1,126 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from './api-service-base'; -import {HttpClient} from '@angular/common/http'; -import {lastValueFrom} from 'rxjs'; - -import {IServerResponse} from '@interfaces/api-models/server-response'; -import {IProjectCreateRequest} from '@interfaces/api-models/project-create-request'; -import {IProject} from '@interfaces/project'; -import {ITeamMemberOverviewGetResponse} from '@interfaces/api-models/team-members-get-response'; -import {toQueryString} from "@shared/utils"; -import {IProjectsViewModel} from "@interfaces/api-models/projects-view-model"; -import {IProjectMembersViewModel} from "@interfaces/api-models/project-members-view-model"; -import {IMyDashboardAllTasksViewModel} from "@interfaces/api-models/my-dashboard-all-tasks-view-model"; -import {NzTableFilterList} from 'ng-zorro-antd/table'; -import {IProjectViewModel} from "@interfaces/api-models/project-view-model"; -import {IProjectFilterConfig} from "@interfaces/projects-filter-config"; - -@Injectable({ - providedIn: 'root' -}) -export class ProjectsApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/projects`; - - constructor( - private http: HttpClient - ) { - super(); - } - - create(body: IProjectCreateRequest): Promise> { - return this._post(this.http, this.root, body); - } - - get(index: number, size: number, field: string | null, order: string | null, - search: string | null, filter: string | null = null): Promise> { - const s = encodeURIComponent(search || ''); - const url = `${this.root}${toQueryString({index, size, field, order, search: s, filter})}`; - return this._get(this.http, url); - } - - getByConfig(config: IProjectFilterConfig): Promise> { - config.search = encodeURIComponent(config.search || ''); - const url = `${this.root}${toQueryString(config)}`; - return this._get(this.http, url); - } - - updateDefaultView(projectId: string, tabIndex: number) { - let defaultview = 'TASK_LIST' - if(tabIndex === 1) { - defaultview = 'BOARD'; - }; - const body = { - project_id: projectId, - default_view: defaultview - } - return this._put(this.http, `${this.root}/update-pinned-view`, body); - } - - getMyProjectsToTasks(): Promise> { - const url = `${this.root}/my-task-projects`; - return this._get(this.http, url); - } - - getMyProjects(index: number, size: number, field: string | null, order: string | null, - search: string | null, filter: string | null = null): Promise> { - const s = encodeURIComponent(search || ''); - const url = `${this.root}/my-projects${toQueryString({index, size, field, order, search: s, filter})}`; - return this._get(this.http, url); - } - - getAllTasks(index: number, size: number, field: string | null, order: string | null, search: string | null, - filter: number | null = null): Promise> { - const s = encodeURIComponent(search || ''); - const url = `${this.root}/tasks${toQueryString({index, size, field, order, search: s, filter})}`; - return this._get(this.http, url); - } - - getMembers(id: string, index: number, size: number, field: string | null, order: string | null, - search: string | null): Promise> { - const s = encodeURIComponent(search || ''); - const url = `${this.root}/members/${id}${toQueryString({index, size, field, order, search: s})}`; - return this._get(this.http, url); - } - - getById(id: string): Promise> { - return this._get(this.http, `${this.root}/${id}`); - } - - getProjectManager(id: string): Promise> { - return this._get(this.http, `${this.root}/project-manager/${id}`); - } - - update(id: string, body: IProjectCreateRequest): Promise> { - const q = toQueryString({current_project_id: id}) - return this._put(this.http, `${this.root}/${id}${q}`, body); - } - - delete(id: string): Promise> { - return lastValueFrom(this.http.delete>(`${this.root}/${id}`)); - } - - getOverViewById(id: string): Promise> { - return this._get(this.http, `${this.root}/overview/${id}`); - } - - getOverViewMembersById(id: string, include_archived: boolean): Promise> { - return this._get(this.http, `${this.root}/overview-members/${id}?archived=${include_archived}`); - } - - public getAll(): Promise> { - return this._get(this.http, `${this.root}/all`); - } - - public toggleFavorite(id: string): Promise> { - return this._get(this.http, `${this.root}/favorite/${id}`); - } - - public toggleArchive(id: string): Promise> { - return this._get(this.http, `${this.root}/archive/${id}`); - } - - public toggleArchiveAll(id: string): Promise> { - return this._get(this.http, `${this.root}/archive-all/${id}`); - } - -} diff --git a/worklenz-frontend/src/app/services/api/pt-labels-api.service.ts b/worklenz-frontend/src/app/services/api/pt-labels-api.service.ts deleted file mode 100644 index fcb89e71..00000000 --- a/worklenz-frontend/src/app/services/api/pt-labels-api.service.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {toQueryString} from "@shared/utils"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {ITaskLabel} from "@interfaces/task-label"; - -@Injectable({ - providedIn: 'root' -}) -export class PtLabelsApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/labels`; - - constructor( - private http: HttpClient - ) { - super(); - } - - get(projectId: string | null = null): Promise> { - const q = toQueryString({project: projectId}); - return this._get(this.http, `${this.root}${q}`); - } - -} diff --git a/worklenz-frontend/src/app/services/api/pt-priorities-api.service.ts b/worklenz-frontend/src/app/services/api/pt-priorities-api.service.ts deleted file mode 100644 index 333bc282..00000000 --- a/worklenz-frontend/src/app/services/api/pt-priorities-api.service.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Injectable } from '@angular/core'; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {ITaskPrioritiesGetResponse} from "@interfaces/api-models/task-priorities-get-response"; -import {APIServiceBase} from "@api/api-service-base"; - -@Injectable({ - providedIn: 'root' -}) -export class PtPrioritiesApiService extends APIServiceBase{ - private readonly root = `${this.API_BASE_URL}/task-priorities`; - - constructor( - private http: HttpClient - ) { - super(); - } - - get(): Promise> { - return this._get(this.http, this.root); - } - - getById(id: string): Promise> { - return this._get(this.http, `${this.root}/${id}`); - } -} diff --git a/worklenz-frontend/src/app/services/api/pt-statuses-api.service.ts b/worklenz-frontend/src/app/services/api/pt-statuses-api.service.ts deleted file mode 100644 index 465e0a8b..00000000 --- a/worklenz-frontend/src/app/services/api/pt-statuses-api.service.ts +++ /dev/null @@ -1,49 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {ITaskStatusCreateRequest} from "@interfaces/api-models/task-status-create-request"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {ITaskStatus} from "@interfaces/task-status"; -import {ITaskStatusCategory} from "@interfaces/task-status-category"; -import {ITaskStatusUpdateModel} from "@interfaces/api-models/task-status-update-model"; - -@Injectable({ - providedIn: 'root' -}) -export class PtStatusesApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/pt-statuses`; - - constructor( - private http: HttpClient - ) { - super(); - } - - create(body: ITaskStatusCreateRequest): Promise> { - return this._post(this.http, this.root, body); - } - - get(id: string): Promise> { - return this._get(this.http, `${this.root}?template_id=${id}`); - } - - getById(id: string): Promise> { - return this._get(this.http, `${this.root}/${id}`); - } - - getCategories(): Promise> { - return this._get(this.http, `${this.root}/categories`); - } - - update(id: string, body: ITaskStatusUpdateModel): Promise> { - return this._put(this.http, `${this.root}/${id}`, body); - } - - updateName(id: string, body: ITaskStatusUpdateModel): Promise> { - return this._put(this.http, `${this.root}/name/${id}`, body); - } - - delete(id: string, projectId: string, replacingStatusId?: string): Promise> { - return this._delete(this.http, `${this.root}/${id}?project=${projectId}&replace=${replacingStatusId || null}`); - } -} diff --git a/worklenz-frontend/src/app/services/api/pt-task-phases-api.service.ts b/worklenz-frontend/src/app/services/api/pt-task-phases-api.service.ts deleted file mode 100644 index f0809ba4..00000000 --- a/worklenz-frontend/src/app/services/api/pt-task-phases-api.service.ts +++ /dev/null @@ -1,55 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {ITaskPhase} from "@interfaces/api-models/task-phase"; -import {toQueryString} from "@shared/utils"; - -@Injectable({ - providedIn: 'root' -}) -export class PtTaskPhasesApiService extends APIServiceBase { - - private readonly root = `${this.API_BASE_URL}/pt-task-phases`; - - constructor( - private http: HttpClient - ) { - super(); - } - - create(projectId: string): Promise> { - const q = toQueryString({id: projectId}) - return this._post(this.http, `${this.root}${q}`, {}); - } - - get(projectId: string): Promise> { - const q = toQueryString({id: projectId}) - return this._get(this.http, `${this.root}${q}`); - } - - getById(id: string, projectId: string): Promise> { - const q = toQueryString({id: projectId}) - return this._get(this.http, `${this.root}/${id}${q}`); - } - - updateLabel(projectId: string, name: string): Promise> { - return this._put(this.http, `${this.root}/label/${projectId}`, {name}); - } - - update(projectId: string, body: ITaskPhase): Promise> { - const q = toQueryString({id: projectId}) - return this._put(this.http, `${this.root}/${body.id}${q}`, body); - } - - updateColor(templateId: string, body: ITaskPhase) : Promise> { - const q = toQueryString({id: templateId, current_project_id: templateId}) - return this._put(this.http, `${this.root}/change-color/${body.id}${q}`, body); - } - - delete(id: string, projectId: string): Promise> { - const q = toQueryString({id: projectId}) - return this._delete(this.http, `${this.root}/${id}${q}`); - } - -} diff --git a/worklenz-frontend/src/app/services/api/pt-tasks-api.service.ts b/worklenz-frontend/src/app/services/api/pt-tasks-api.service.ts deleted file mode 100644 index 68154538..00000000 --- a/worklenz-frontend/src/app/services/api/pt-tasks-api.service.ts +++ /dev/null @@ -1,41 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import { - IPTTask, - IPTTaskListColumn, - IPTTaskListConfig, - IPTTaskListGroup -} from "../../administrator/settings/project-template-edit-view/interfaces"; -import {toQueryString} from "@shared/utils"; -import {ITaskStatusCreateRequest} from "@interfaces/api-models/task-status-create-request"; -import {ITaskCreateRequest} from "@interfaces/api-models/task-create-request"; -import {ITaskFormViewModel} from "@interfaces/task-form-view-model"; -import {ITaskGetRequest} from "@interfaces/api-models/task-get-response"; -import {lastValueFrom} from "rxjs"; -import {IBulkTasksStatusChangeRequest} from "@interfaces/api-models/bulk-tasks-status-change-request"; -import {IBulkTasksDeleteRequest} from "@interfaces/api-models/bulk-tasks-delete-request"; -import {IBulkTasksDeleteResponse} from "@interfaces/api-models/bulk-tasks-delete-response"; - -@Injectable({ - providedIn: 'root' -}) -export class PtTasksApiService extends APIServiceBase { - - private readonly root = `${this.API_BASE_URL}/pt-tasks`; - - constructor(private http: HttpClient) { - super(); - } - - getTaskList(config: IPTTaskListConfig): Promise> { - const q = toQueryString(config); - return this._get(this.http, `${this.root}/list/${config.id}${q}`); - } - - bulkDelete(body: IBulkTasksDeleteRequest, templateId: string): Promise> { - return this._put(this.http, `${this.root}/bulk/delete`, body); - } - -} diff --git a/worklenz-frontend/src/app/services/api/reporting-api-v0.service.ts b/worklenz-frontend/src/app/services/api/reporting-api-v0.service.ts deleted file mode 100644 index 1a8ff74f..00000000 --- a/worklenz-frontend/src/app/services/api/reporting-api-v0.service.ts +++ /dev/null @@ -1,187 +0,0 @@ -import {Injectable} from '@angular/core'; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {HttpClient} from "@angular/common/http"; -import {APIServiceBase} from "@api/api-service-base"; -import { - IActualVsEstimateGetRequest, - IReportingActiveProject, - IReportingEstimatedVsLogged, - IReportingMemberProjects, - IReportingMemberProjectTask, - IReportingMemberStats, - IReportingOverdueMembers, - IReportingOverdueProject, - IReportingOverview, - IReportingProject, - IReportingProjectStats, - IReportingTeam, - IReportingUnassignedMembers -} from "@interfaces/reporting"; -import {ISelectableTeam} from "@interfaces/selectable-team"; -import {ISelectableProject} from "@interfaces/selectable-project"; -import {IAllocationViewModel} from "@interfaces/allocation-view-model"; -import {toQueryString} from "@shared/utils"; -import { - IReportingMemberOverviewGetRequest, - IReportingMemberProjectResponse, - IReportingProjectsCustomGetResponse -} from "@interfaces/api-models/reporting"; -import {ITeamMemberTreeMapResponse} from "@interfaces/api-models/team-members-get-response"; - -@Injectable({ - providedIn: 'root' -}) -export class ReportingApiV0Service extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/reporting`; - - constructor( - private http: HttpClient - ) { - super(); - } - - getReportingOverview(includeArchived: boolean = false): Promise> { - return this._get(this.http, `${this.root}/overview?includeArchived=${includeArchived}`); - } - - getEstimatedVsLogged(includeArchived: boolean = false): Promise> { - return this._get(this.http, `${this.root}/estimated-vs-logged?includeArchived=${includeArchived}`); - } - - getTeamsList(includeArchived: boolean = false): Promise> { - return this._get(this.http, `${this.root}/teams?includeArchived=${includeArchived}`); - } - - getProjectsByTeam(teamId: string, includeArchived: boolean = false): Promise> { - return this._get(this.http, `${this.root}/projects-by-team?id=${teamId}&includeArchived=${includeArchived}`); - } - - getEstimatedVsActual(obj: {}): Promise> { - return this._post(this.http, `${this.root}/actual-vs-estimate`, obj); - } - - getAllocation(body = {}, archived = false): Promise> { - const q = toQueryString({archived}); - return this._post(this.http, `${this.root}/allocation${q}`, body); - } - - getAllocationTeams(): Promise> { - return this._get(this.http, `${this.root}/allocation/teams`); - } - - getAllocationProjects(selectedTeams: string[]): Promise> { - return this._post(this.http, `${this.root}/allocation/projects`, selectedTeams); - } - - exportOverviewExcel(includeArchived = false) { - window.location.href = `${this.root}/overview/export?includeArchived=${includeArchived}`; - } - - exportAllocationExcel(teams: string[], projects: string[], duration: string | null, date_range: any, includeArchived: boolean) { - const teamsString = teams?.join(","); - const projectsString = projects?.join(","); - window.location.href = `${this.root}/allocation/export${toQueryString({ - teams: teamsString, - projects: projectsString, - duration, - date_range, - includeArchived - })}`; - } - - exportMembers(team: string, search: string | null, all = false, projects: string[] | null, status: string[] | null, duration: string | null, dateRange: any) { - const projectsString = projects?.join(","); - const statusString = status?.join(","); - const range: string = dateRange?.join(","); - window.location.href = `${this.root}/members/export${toQueryString({ - team, - search, - all, - dateRange: range, - projects: projectsString, - status: statusString, - duration - })}`; - } - - // Projects Reporting APIs - getReportingProjectStats(includeArchived: boolean = false): Promise> { - return this._get(this.http, `${this.root}/projects/stats?includeArchived=${includeArchived}`); - } - - getReportingActiveProjects(includeArchived: boolean = false): Promise> { - return this._get(this.http, `${this.root}/projects/active?includeArchived=${includeArchived}`); - } - - getReportingOverdueProjects(includeArchived: boolean = false): Promise> { - return this._get(this.http, `${this.root}/projects/overdue?includeArchived=${includeArchived}`); - } - - getProjectsCustom(body = {}, includeArchived = false, index: number, size: number, search: string): Promise> { - const q = toQueryString({includeArchived, index, size, search}); - return this._post(this.http, `${this.root}/projects/custom${q}`, body); - } - - getDetails(projectId: string): Promise> { - return this._get(this.http, `${this.root}/project/${projectId}`); - } - - exportProjectsExcel(teams: string[], status: string[], duration: string | null, date_range: any, includeArchived: boolean, search: string) { - const s = encodeURIComponent(search || ''); - const teamsString = teams?.join(","); - const statusString = status?.join(","); - window.location.href = `${this.root}/projects/export${toQueryString({ - teams: teamsString, - status: statusString, - duration, - date_range, - includeArchived, - search: s - })}`; - } - - // Members - getTeamMemberList(body = {}): Promise> { - return this._post(this.http, `${this.root}/members/all`, body); - } - - getUnassignedMembers(): Promise> { - return this._get(this.http, `${this.root}/members/unassigned`); - } - - getOverdueMembers(teamId: string): Promise> { - return this._get(this.http, `${this.root}/members/overdue/${teamId}`); - } - - getReportingMemberStats(memberId: string): Promise> { - return this._get(this.http, `${this.root}/member/stats/${memberId}`); - } - - getReportingMemberOverview(memberId: string): Promise> { - return this._get(this.http, `${this.root}/member/overview/${memberId}`); - } - - getReportingMemberProjects(memberId: string, teamId: string): Promise> { - const params = toQueryString({memberId, teamId}); - return this._get(this.http, `${this.root}/member/projects${params}`); - } - - getTasksByProject(memberId: string, projectId: string): Promise> { - const params = toQueryString({memberId, projectId}); - return this._get(this.http, `${this.root}/member/project${params}`); - } - - getReportingMemberTasks(memberId: string, teamId: string): Promise> { - const params = toQueryString({memberId, teamId}); - return this._get(this.http, `${this.root}/member/tasks${params}`); - } - - getAllProjects(teamId: string): Promise> { - return this._get(this.http, `${this.root}/projects/all/${teamId}`); - } - - async getProjectsByTeamMember(body: any): Promise> { - const url = `${this.root}/projects-by-member`; - return this._post(this.http, url, body); - } -} diff --git a/worklenz-frontend/src/app/services/api/reporting-export-api.service.ts b/worklenz-frontend/src/app/services/api/reporting-export-api.service.ts deleted file mode 100644 index 8516df40..00000000 --- a/worklenz-frontend/src/app/services/api/reporting-export-api.service.ts +++ /dev/null @@ -1,154 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {ITimeLogBreakdownReq} from "../../administrator/reporting/interfaces"; -import {toQueryString} from "@shared/utils"; - -@Injectable({ - providedIn: 'root' -}) -export class ReportingExportApiService extends APIServiceBase { - - private readonly root = `${this.API_BASE_URL}/reporting-export`; - - constructor( - private http: HttpClient - ) { - super(); - } - - exportOverviewProjectsByTeam(teamId: string, teamName: string) { - const params = toQueryString({ - team_id: teamId, - team_name: teamName - }); - window.location.href = `${this.root}/overview/projects${params}`; - } - - exportOverviewMembersByTeam(teamId: string, teamName: string) { - const params = toQueryString({ - team_id: teamId, - team_name: teamName - }); - window.location.href = `${this.root}/overview/members${params}`; - } - - - exportAllocation(archived: boolean, teams: string[], projects: string[], duration: string | undefined, date_range: string[]) { - const teamsString = teams?.join(","); - const projectsString = projects.join(","); - window.location.href = `${this.root}/allocation/export${toQueryString({ - teams: teamsString, - projects: projectsString, - duration: duration, - date_range: date_range, - include_archived: archived - })}` - } - - exportProjects(teamName: string | undefined) { - const params = toQueryString({ - team_name: teamName - }); - window.location.href = `${this.root}/projects/export${params}`; - } - - exportMembers(teamName: string | undefined, duration: string | null | undefined, date_range: string[] | null, archived: boolean) { - const params = toQueryString({ - team_name: teamName, - duration: duration, - date_range: date_range, - archived: archived - }); - window.location.href = `${this.root}/members/export${params}`; - } - - exportProjectMembers(projectId: string, projectName: string, teamName: string | null | undefined) { - const params = toQueryString({ - project_id: projectId, - project_name: projectName, - team_name: teamName ? teamName : null - }); - window.location.href = `${this.root}/project-members/export${params}` - } - - exportProjectTasks(projectId: string, projectName: string, teamName: string | null | undefined) { - const params = toQueryString({ - project_id: projectId, - project_name: projectName, - team_name: teamName ? teamName : null - }); - window.location.href = `${this.root}/project-tasks/export${params}` - } - - exportMemberProjects(memberId: string, teamId: string | null, memberName: string, teamName: string | null | undefined, archived: boolean) { - const params = toQueryString({ - team_member_id: memberId, - team_id: teamId, - team_member_name: memberName, - team_name: teamName ? teamName : null, - archived: archived - }); - window.location.href = `${this.root}/member-projects/export${params}` - } - - exportMemberTasks(memberId: string, memberName: string, teamName: string | null | undefined, body: any | null) { - const params = toQueryString({ - team_member_id: memberId, - team_member_name: memberName, - team_name: teamName ? teamName : null, - duration: body.duration, - date_range: body.date_range, - only_single_member: body.only_single_member ? body.only_single_member : false, - archived: body.archived ? body.archived : false - }); - window.location.href = `${this.root}/member-tasks/export${params}` - } - - exportFlatTasks(memberId: string, memberName: string, projectId: string | null, projectName: string | null) { - const params = toQueryString({ - team_member_id: memberId, - team_member_name: memberName, - project_id: projectId, - project_name: projectName - }); - window.location.href = `${this.root}/flat-tasks/export${params}` - } - - exportProjectTimeLogs(body: ITimeLogBreakdownReq, projectName: string) { - const params = toQueryString({ - id: body.id, - duration: body.duration, - date_range: body.date_range, - project_name: projectName - }) - window.location.href = `${this.root}/projects-time-log-breakdown/export${params}`; - } - - exportMemberTimeLogs(body: any | null) { - const params = toQueryString({ - team_member_id: body.team_member_id, - team_id: body.team_id, - duration: body.duration, - date_range: body.date_range, - member_name: body.member_name, - team_name: body.team_name, - archived: body.archived ? body.archived : false - }); - window.location.href = `${this.root}/member-time-log-breakdown/export${params}` - } - - exportMemberActivityLogs(body: any | null) { - const params = toQueryString({ - team_member_id: body.team_member_id, - team_id: body.team_id, - duration: body.duration, - date_range: body.date_range, - member_name: body.member_name, - team_name: body.team_name, - archived: body.archived ? body.archived : false - }); - window.location.href = `${this.root}/member-activity-log-breakdown/export${params}` - } - -} diff --git a/worklenz-frontend/src/app/services/api/resource-allocation.service.ts b/worklenz-frontend/src/app/services/api/resource-allocation.service.ts deleted file mode 100644 index 949e52f8..00000000 --- a/worklenz-frontend/src/app/services/api/resource-allocation.service.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {Injectable} from '@angular/core'; -import {HttpClient} from "@angular/common/http"; - -import {APIServiceBase} from "@api/api-service-base"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {IProjectWiseResourcesViewModel} from "@interfaces/project-wise-resources-view-model"; -import {format} from "date-fns"; - - -@Injectable({ - providedIn: 'root' -}) -export class ResourceAllocationService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/resource-allocation`; - - constructor(private http: HttpClient) { - super(); - } - - getProjectWiseResources(week: { start: Date, end: Date }): Promise> { - return this._get(this.http, `${this.root}/project?start=${format(week.start, 'yyyy-MM-dd')}&end=${format(week.end, 'yyyy-MM-dd')}`); - } - - getUserWiseResources(week: { start: Date, end: Date }): Promise> { - return this._get(this.http, `${this.root}/team?start=${format(week.start, 'yyyy-MM-dd')}&end=${format(week.end, 'yyyy-MM-dd')}`); - } -} diff --git a/worklenz-frontend/src/app/services/api/schedule-api.service.ts b/worklenz-frontend/src/app/services/api/schedule-api.service.ts deleted file mode 100644 index 67e1d19e..00000000 --- a/worklenz-frontend/src/app/services/api/schedule-api.service.ts +++ /dev/null @@ -1,54 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {toQueryString} from "@shared/utils"; -import { - IMemberTaskListGroup, - IMemberUpdateResponse, IProjectUpdateResposne, - IScheduleDateCreateResponse, - IScheduleProject, - IScheduleTasksConfig -} from "@interfaces/schedular"; -import {IProjectTask} from "@interfaces/api-models/project-tasks-view-model"; - -@Injectable({ - providedIn: 'root' -}) -export class ScheduleApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/schedule-gannt`; - - constructor(private http: HttpClient) { - super(); - } - - getGanttDates(id: string, timeZone: string): Promise> { - const q = toQueryString({timeZone: timeZone}) - return this._get(this.http, `${this.root}/chart-dates/${id}${q}`); - } - - getProjects(id: string, timeZone: string): Promise> { - const q = toQueryString({timeZone: timeZone}) - return this._get(this.http, `${this.root}/projects/${id}${q}`); - } - - getMemberAllocation(projectId: string, teamMemberId: string, timeZone: string, isProjectRefresh: boolean): Promise> { - const q = toQueryString({team_member_id: teamMemberId, timeZone: timeZone, isProjectRefresh: isProjectRefresh}) - return this._get(this.http, `${this.root}/project-member/${projectId}${q}`); - } - - getMemberProjectAllocation(projectId: string, teamMemberId: string, timeZone: string, isProjectRefresh: boolean): Promise> { - const q = toQueryString({team_member_id: teamMemberId, timeZone: timeZone, isProjectRefresh: isProjectRefresh}) - return this._get(this.http, `${this.root}/refresh/project-indicator/${projectId}${q}`); - } - - bulkDeleteMemberAllocations(ids: string[]) { - return this._put(this.http, `${this.root}/bulk/delete-member-allocations`, ids); - } - - getTasksByMember(config: IScheduleTasksConfig): Promise> { - const q = toQueryString(config); - return this._get(this.http, `${this.root}/tasks-by-member/${config.id}${q}`); - } - -} diff --git a/worklenz-frontend/src/app/services/api/shared-projects-api.service.ts b/worklenz-frontend/src/app/services/api/shared-projects-api.service.ts deleted file mode 100644 index 981bddca..00000000 --- a/worklenz-frontend/src/app/services/api/shared-projects-api.service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; - -export interface ISharedProjectInfo { - url?: string; - created_by?: string; - created_at?: string; -} - -@Injectable({ - providedIn: 'root' -}) -export class SharedProjectsApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/shared/projects`; - - constructor( - private http: HttpClient - ) { - super(); - } - - create(body: { project_id?: string; }): Promise> { - return this._post(this.http, this.root, body); - } - - get(projectId: string): Promise> { - return this._get(this.http, `${this.root}/${projectId}`); - } - - delete(projectId: string): Promise> { - return this._delete(this.http, `${this.root}/${projectId}`); - } -} diff --git a/worklenz-frontend/src/app/services/api/sub-tasks-api.service.ts b/worklenz-frontend/src/app/services/api/sub-tasks-api.service.ts deleted file mode 100644 index 2bafe988..00000000 --- a/worklenz-frontend/src/app/services/api/sub-tasks-api.service.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {ISubTask} from "@interfaces/sub-task"; -import {IGanttDateRange} from "@interfaces/gantt-chart"; -import {ITaskCreateRequest} from "@interfaces/api-models/task-create-request"; - -@Injectable({ - providedIn: 'root' -}) -export class SubTasksApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/sub-tasks`; - - constructor(private http: HttpClient) { - super(); - } - - get(parentTaskId: string): Promise> { - return this._get(this.http, `${this.root}/${parentTaskId}`); - } - - getSubTasksForRoadMap(parentTaskId: string, body: IGanttDateRange[]): Promise> { - return this._post(this.http, `${this.root}/roadmap/${parentTaskId}`, body); - } - - getNames(parentTaskId: string): Promise> { - return this._get(this.http, `${this.root}/names/${parentTaskId}`); - } - - update(body: ITaskCreateRequest, id: string) { - return this._put(this.http, `${this.root}/${id}`, body); - } -} diff --git a/worklenz-frontend/src/app/services/api/task-comments-api.service.ts b/worklenz-frontend/src/app/services/api/task-comments-api.service.ts deleted file mode 100644 index bc00e298..00000000 --- a/worklenz-frontend/src/app/services/api/task-comments-api.service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {lastValueFrom} from "rxjs"; -import {ITaskCommentsCreateRequest} from "@interfaces/api-models/task-comments-create-request.ts"; -import {ITaskCommentViewModel} from "@interfaces/api-models/task-comment-view-model"; -import {ITaskComment} from "@interfaces/task-comment"; - -@Injectable({ - providedIn: 'root' -}) -export class TaskCommentsApiService extends APIServiceBase { - constructor( - private http: HttpClient - ) { - super(); - } - - create(body: ITaskCommentsCreateRequest): Promise> { - return this._post(this.http, `${this.API_BASE_URL}/task-comments`, body); - } - - getByTaskId(id: string): Promise> { - return this._get(this.http, `${this.API_BASE_URL}/task-comments/${id}`); - } - - update(id: string, body: ITaskComment): Promise> { - return this._put(this.http, `${this.API_BASE_URL}/task-comments/${id}`, body); - } - - delete(id: string, taskId: string): Promise> { - return lastValueFrom(this.http.delete>(`${this.API_BASE_URL}/task-comments/${id}/${taskId}`)); - } -} diff --git a/worklenz-frontend/src/app/services/api/task-labels-api.service.ts b/worklenz-frontend/src/app/services/api/task-labels-api.service.ts deleted file mode 100644 index 364a1339..00000000 --- a/worklenz-frontend/src/app/services/api/task-labels-api.service.ts +++ /dev/null @@ -1,41 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {ITaskLabel} from "@interfaces/task-label"; -import {toQueryString} from "@shared/utils"; - -@Injectable({ - providedIn: 'root' -}) -export class TaskLabelsApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/labels`; - - constructor( - private http: HttpClient - ) { - super(); - } - - get(projectId: string | null = null): Promise> { - const q = toQueryString({project: projectId}); - return this._get(this.http, `${this.root}${q}`); - } - - getByTask(id: string): Promise> { - return this._get(this.http, `${this.root}/tasks/${id}`); - } - - /** Returns labels that have only been assigned to at least one task. */ - getByProject(projectId: string): Promise> { - return this._get(this.http, `${this.root}/project/${projectId}`); - } - - updateColor(id: string, color: string): Promise> { - return this._put(this.http, `${this.root}/tasks/${id}`, {color}); - } - - deleteById(id: string): Promise> { - return this._delete(this.http, `${this.root}/team/${id}`); - } -} diff --git a/worklenz-frontend/src/app/services/api/task-phases-api.service.ts b/worklenz-frontend/src/app/services/api/task-phases-api.service.ts deleted file mode 100644 index 7333b3a1..00000000 --- a/worklenz-frontend/src/app/services/api/task-phases-api.service.ts +++ /dev/null @@ -1,60 +0,0 @@ -import {Injectable} from '@angular/core'; -import {IServerResponse} from '@interfaces/api-models/server-response'; -import {HttpClient} from "@angular/common/http"; - -import {APIServiceBase} from "./api-service-base"; -import {toQueryString} from "@shared/utils"; -import {ITaskPhase} from "@interfaces/api-models/task-phase"; - -@Injectable({ - providedIn: 'root' -}) -export class TaskPhasesApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/task-phases`; - - constructor( - private http: HttpClient - ) { - super(); - } - - create(projectId: string, isProjectManager = false): Promise> { - const q = toQueryString({id: projectId, current_project_id: projectId}) - return this._post(this.http, `${this.root}${q}`, {}); - } - - get(projectId: string): Promise> { - const q = toQueryString({id: projectId}) - return this._get(this.http, `${this.root}${q}`); - } - - getById(id: string, projectId: string): Promise> { - const q = toQueryString({id: projectId}) - return this._get(this.http, `${this.root}/${id}${q}`); - } - - updateLabel(projectId: string, name: string, isProjectManager = false): Promise> { - const q = toQueryString({current_project_id: projectId}) - return this._put(this.http, `${this.root}/label/${projectId}${q}`, {name}); - } - - update(projectId: string, body: ITaskPhase, isProjectManager = false): Promise> { - const q = toQueryString({id: projectId, current_project_id: projectId}) - return this._put(this.http, `${this.root}/${body.id}${q}`, body); - } - - updateColor(projectId: string, body: ITaskPhase) : Promise> { - const q = toQueryString({id: projectId, current_project_id: projectId}) - return this._put(this.http, `${this.root}/change-color/${body.id}${q}`, body); - } - - updateSortOrder(body: {}, project_id: string) { - const q = toQueryString({current_project_id: project_id}) - return this._put(this.http, `${this.root}/update-sort-order${q}`, body); - } - - delete(id: string, projectId: string, isProjectManager = false): Promise> { - const q = toQueryString({id: projectId, current_project_id: projectId}) - return this._delete(this.http, `${this.root}/${id}${q}`); - } -} diff --git a/worklenz-frontend/src/app/services/api/task-priorities.service.ts b/worklenz-frontend/src/app/services/api/task-priorities.service.ts deleted file mode 100644 index dbd219d5..00000000 --- a/worklenz-frontend/src/app/services/api/task-priorities.service.ts +++ /dev/null @@ -1,28 +0,0 @@ -import {Injectable} from '@angular/core'; -import {IServerResponse} from '@interfaces/api-models/server-response'; -import {ITaskPrioritiesGetResponse} from '@interfaces/api-models/task-priorities-get-response'; -import {HttpClient} from "@angular/common/http"; - -import {APIServiceBase} from "./api-service-base"; - -@Injectable({ - providedIn: 'root' -}) -export class TaskPrioritiesService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/task-priorities`; - - constructor( - private http: HttpClient - ) { - super(); - } - - get(): Promise> { - return this._get(this.http, this.root); - } - - getById(id: string): Promise> { - return this._get(this.http, `${this.root}/${id}`); - } - -} diff --git a/worklenz-frontend/src/app/services/api/task-statuses-api.service.ts b/worklenz-frontend/src/app/services/api/task-statuses-api.service.ts deleted file mode 100644 index 6aba9e76..00000000 --- a/worklenz-frontend/src/app/services/api/task-statuses-api.service.ts +++ /dev/null @@ -1,62 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {ITaskStatusCreateRequest} from "@interfaces/api-models/task-status-create-request"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {ITaskStatus} from "@interfaces/task-status"; -import {ITaskStatusCategory} from "@interfaces/task-status-category"; -import {ITaskStatusUpdateModel} from "@interfaces/api-models/task-status-update-model"; -import {toQueryString} from "@shared/utils"; - -@Injectable({ - providedIn: 'root' -}) -export class TaskStatusesApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/statuses`; - - constructor( - private http: HttpClient - ) { - super(); - } - - create(body: ITaskStatusCreateRequest, currentProjectId: string): Promise> { - const q = toQueryString({current_project_id: currentProjectId}) - return this._post(this.http, `${this.root}${q}`, body); - } - - /** - * Get statuses by project id - * @param id Project Id - */ - get(id: string): Promise> { - return this._get(this.http, `${this.root}?project=${id}`); - } - - getById(id: string): Promise> { - return this._get(this.http, `${this.root}/${id}`); - } - - getCategories(): Promise> { - return this._get(this.http, `${this.root}/categories`); - } - - update(id: string, body: ITaskStatusUpdateModel, currentProjectId: string): Promise> { - const q = toQueryString({current_project_id: currentProjectId}) - return this._put(this.http, `${this.root}/${id}${q}`, body); - } - - updateName(id: string, body: ITaskStatusUpdateModel, currentProjectId: string): Promise> { - const q = toQueryString({current_project_id: currentProjectId}) - return this._put(this.http, `${this.root}/name/${id}${q}`, body); - } - - updateStatus(id: string, body: ITaskStatusCreateRequest, currentProjectId: string): Promise> { - const q = toQueryString({current_project_id: currentProjectId}) - return this._put(this.http, `${this.root}/order${q}`, body); - } - - delete(id: string, projectId: string, replacingStatusId?: string): Promise> { - return this._delete(this.http, `${this.root}/${id}?project=${projectId}¤t_project_id=${projectId}&replace=${replacingStatusId || null}`); - } -} diff --git a/worklenz-frontend/src/app/services/api/task-templates.service.ts b/worklenz-frontend/src/app/services/api/task-templates.service.ts deleted file mode 100644 index 11a5967e..00000000 --- a/worklenz-frontend/src/app/services/api/task-templates.service.ts +++ /dev/null @@ -1,56 +0,0 @@ -import {Injectable} from '@angular/core'; -import {HttpClient} from '@angular/common/http'; - -import {IServerResponse} from '@interfaces/api-models/server-response'; -import {ITask} from '@interfaces/task'; -import {IProjectTask} from '@interfaces/api-models/project-tasks-view-model'; - -import {APIServiceBase} from './api-service-base'; -import {ITaskTemplatesGetResponse} from "@interfaces/api-models/task-templates-get-response"; -import {ITaskTemplateGetResponse} from "@interfaces/api-models/task-template-get-response"; -import {Subject} from "rxjs"; - -@Injectable({ - providedIn: 'root' -}) -export class TaskTemplatesService extends APIServiceBase { - private readonly importSbj$ = new Subject(); - - private readonly root = `${this.API_BASE_URL}/task-templates`; - - public get onTemplateImport() { - return this.importSbj$.asObservable(); - } - - constructor(private http: HttpClient) { - super(); - } - - get(): Promise> { - return this._get(this.http, this.root); - } - - getById(id: string): Promise> { - return this._get(this.http, `${this.root}/${id}`); - } - - createTemplate(body: { name: string, tasks: IProjectTask[] }): Promise> { - return this._post(this.http, `${this.root}`, body); - } - - updateTemplate(id: string, body: { name: string, tasks: IProjectTask[] }): Promise> { - return this._put(this.http, `${this.root}/${id}`, body); - } - - delete(id: string): Promise> { - return this._delete(this.http, `${this.root}/${id}`); - } - - import(id: string, body: IProjectTask[]): Promise> { - return this._post(this.http, `${this.root}/import/${id}`, body); - } - - public emitOnImport() { - this.importSbj$.next(); - } -} diff --git a/worklenz-frontend/src/app/services/api/tasks-api.service.ts b/worklenz-frontend/src/app/services/api/tasks-api.service.ts deleted file mode 100644 index 441b8611..00000000 --- a/worklenz-frontend/src/app/services/api/tasks-api.service.ts +++ /dev/null @@ -1,190 +0,0 @@ -import {HttpClient} from '@angular/common/http'; -import {Injectable} from '@angular/core'; -import {IBulkAssignMembersRequest, IBulkAssignRequest} from "@interfaces/api-models/bulk-assign-request"; -import {IBulkTasksArchiveRequest} from "@interfaces/api-models/bulk-tasks-archive-request"; -import {IBulkTasksDeleteRequest} from "@interfaces/api-models/bulk-tasks-delete-request"; -import {IBulkTasksDeleteResponse} from "@interfaces/api-models/bulk-tasks-delete-response"; -import {IBulkTasksLabelsRequest} from "@interfaces/api-models/bulk-tasks-labels-request"; -import {IBulkTasksStatusChangeRequest} from "@interfaces/api-models/bulk-tasks-status-change-request"; -import {IMyDashboardAllTasksViewModel} from '@interfaces/api-models/my-dashboard-all-tasks-view-model'; -import {IProjectTask, IProjectTasksViewModel} from '@interfaces/api-models/project-tasks-view-model'; - -import {IServerResponse} from '@interfaces/api-models/server-response'; -import {IHomeTaskCreateRequest, ITaskCreateRequest} from '@interfaces/api-models/task-create-request'; -import { - IProjectRoadmapGetRequest, - ITaskByRangeGetRequest, - ITaskGetRequest -} from '@interfaces/api-models/task-get-response'; -import {ITaskListConfig} from "@interfaces/api-models/task-list-config"; -import {ITaskStatusCreateRequest} from '@interfaces/api-models/task-status-create-request'; -import {ITeamMemberViewModel} from "@interfaces/api-models/team-members-get-response"; -import {ITask} from '@interfaces/task'; -import {ITaskAssigneesUpdateResponse} from "@interfaces/task-assignee-update-response"; -import {IHomeTaskViewModel, ITaskFormViewModel} from '@interfaces/task-form-view-model'; -import {ITaskListColumn} from "@interfaces/task-list-column"; -import {toQueryString} from '@shared/utils'; -import {lastValueFrom} from 'rxjs'; -import {ITaskListConfigV2, ITaskListGroup} from "../../administrator/modules/task-list-v2/interfaces"; - -import {APIServiceBase} from './api-service-base'; -import {InlineMember} from "@interfaces/api-models/inline-member"; -import { - IPTTask, IPTTaskListColumn, - IPTTaskListConfig, - IPTTaskListGroup -} from "../../administrator/settings/project-template-edit-view/interfaces"; -import {IBulkTasksPriorityChangeRequest} from "@interfaces/api-models/bulk-tasks-priority-change-request"; -import {IBulkTasksPhaseChangeRequest} from "@interfaces/api-models/bulk-tasks-phase-change-request"; - -@Injectable({ - providedIn: 'root' -}) -export class TasksApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/tasks`; - - constructor(private http: HttpClient) { - super(); - } - - getTasksByTeam(): Promise> { - return this._get(this.http, `${this.root}/team`); - } - - getTasksByProject(id: string): Promise> { - return this._get(this.http, `${this.root}/project/${id}`); - } - - getListCols(projectId: string): Promise> { - return this._get(this.http, `${this.root}/list/columns/${projectId}`); - } - - getListColsTest(projectId: string): Promise> { - return this._get(this.http, `${this.root}/list/columns/${projectId}`); - } - - toggleListCols(projectId: string, body: ITaskListColumn): Promise> { - return this._put(this.http, `${this.root}/list/columns/${projectId}`, body); - } - - getTaskList(config: ITaskListConfig | ITaskListConfigV2): Promise> { - const q = toQueryString(config); - return this._get(this.http, `${this.root}/list/${config.id}${q}`); - } - - getTaskListV2(config: ITaskListConfigV2): Promise> { - const q = toQueryString(config); - return this._get(this.http, `${this.root}/list/v2/${config.id}${q}`); - } - - getTaskListV2Test(config: IPTTaskListConfig): Promise> { - const q = toQueryString(config); - return this._get(this.http, `${this.root}/list/v2-test/${config.id}${q}`); - } - - getTasksAssignees(projectId: string): Promise> { - return this._get(this.http, `${this.root}/assignees/${projectId}`); - } - - getGanttTasksByProject(project_id: string): Promise> { - return this._get(this.http, `${this.root}/roadmap?project_id=${project_id}`); - } - - getTasksBetweenRange(project_id: string, start_date: string, end_date: string): Promise> { - return this._get(this.http, `${this.root}/range?project_id=${project_id}&start_date=${start_date}&end_date=${end_date}`); - } - - getTasksByStatus(id: string, status: string): Promise> { - return this._get(this.http, `${this.root}/kanban/${id}?status=${encodeURIComponent(status)}`); - } - - updateTaskStatus(task_id: string, status_id: string, body: ITaskStatusCreateRequest): Promise> { - return this._put(this.http, `${this.root}/status/${status_id}/${task_id}`, body); - } - - create(body: ITaskCreateRequest): Promise> { - return this._post(this.http, this.root, body); - } - - createHomeTask(body: IHomeTaskCreateRequest): Promise> { - return this._post(this.http, `${this.root}/home-task`, body); - } - - update(body: ITaskCreateRequest, id: string, inline = false): Promise> { - const q = inline ? "?inline=true" : ""; - return this._put(this.http, `${this.root}/${id}${q}`, body); - } - - delete(id: string): Promise> { - return lastValueFrom(this.http.delete>(`${this.root}/${id}`)); - } - - getFormViewModel(taskId: string | null, projectId: string | null): Promise> { - const params = []; - if (taskId) params.push(`task_id=${taskId}`); - if (projectId) params.push(`project_id=${projectId}`); - const q = params.length ? `?${params.join('&')}` : ''; - return this._get(this.http, `${this.root}/info${q}`); - } - - updateDuration(body: ITaskCreateRequest, id: string): Promise> { - return this._put(this.http, `${this.root}/duration/${id}`, body); - } - - createQuickTask(body: ITaskCreateRequest): Promise> { - return this._post(this.http, `${this.root}/quick-task`, body); - } - - bulkChangeStatus(body: IBulkTasksStatusChangeRequest, projectId: string): Promise> { - return this._put(this.http, `${this.root}/bulk/status?project=${projectId}`, body); - } - - bulkChangePriority(body: IBulkTasksPriorityChangeRequest, projectId: string): Promise> { - return this._put(this.http, `${this.root}/bulk/priority?project=${projectId}`, body); - } - - bulkChangePhase(body: IBulkTasksPhaseChangeRequest, projectId: string): Promise> { - return this._put(this.http, `${this.root}/bulk/phase?project=${projectId}`, body); - } - - bulkDelete(body: IBulkTasksDeleteRequest, projectId: string): Promise> { - return this._put(this.http, `${this.root}/bulk/delete?project=${projectId}`, body); - } - - bulkArchive(body: IBulkTasksArchiveRequest, unarchive = false): Promise> { - return this._put(this.http, `${this.root}/bulk/archive?type=${unarchive ? 'unarchive' : 'archive'}&project=${body.project_id}`, body); - } - - bulkAssignMe(body: IBulkAssignRequest): Promise> { - return this._put(this.http, `${this.root}/bulk/assign-me?project=${body.project_id}`, body); - } - - bulkAssignLabel(body: IBulkTasksLabelsRequest, projectId: string): Promise> { - return this._put(this.http, `${this.root}/bulk/label?project=${projectId}`, body); - } - - bulkAssignMembers(body: IBulkAssignMembersRequest): Promise> { - return this._put(this.http, `${this.root}/bulk/members?project=${body.project_id}`, body); - } - convertToTask(taskId: string, projectId: string): Promise> { - return this._post(this.http, `${this.root}/convert`, {id: taskId, project_id: projectId}); - } - - convertToSubTask(taskId: string, projectId: string, parentTaskId: string, groupBy: string, toGroupId: string): Promise> { - return this._post(this.http, `${this.root}/convert-to-subtask`, { - id: taskId, - project_id: projectId, - parent_task_id: parentTaskId, - group_by: groupBy, - to_group_id: toGroupId - }); - } - - getNewKanbanTask(taskId: string): Promise> { - return this._get(this.http, `${this.root}/get-new-kanban-task/${taskId}`); - } - - getTaskSubscribers(id: string): Promise> { - return this._get(this.http, `${this.root}/subscribers/${id}`); - } -} diff --git a/worklenz-frontend/src/app/services/api/tasks-log-time.service.ts b/worklenz-frontend/src/app/services/api/tasks-log-time.service.ts deleted file mode 100644 index 4529e3d3..00000000 --- a/worklenz-frontend/src/app/services/api/tasks-log-time.service.ts +++ /dev/null @@ -1,43 +0,0 @@ -import {Injectable} from '@angular/core'; -import {ITaskLogViewModel} from '@interfaces/api-models/task-log-create-request'; -import {IServerResponse} from '@interfaces/api-models/server-response'; -import {HttpClient} from '@angular/common/http'; -import {APIServiceBase} from '@api/api-service-base'; -import {toQueryString} from "@shared/utils"; -import {time} from "html2canvas/dist/types/css/types/time"; - -@Injectable({ - providedIn: 'root' -}) -export class TasksLogTimeService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/task-time-log`; - - constructor(private http: HttpClient) { - super(); - } - - create(body: ITaskLogViewModel, requestBody: {}): Promise> { - return this._post(this.http, this.root, requestBody); - } - - getByTask(id: string, timeZone: string): Promise> { - return this._get(this.http, `${this.root}/task/${id}`); - } - - exportExcel(id: string) { - // const options = { - // responseType: "blob", - // observe: 'response' - // }; - // return this._post(this.http, `${this.root}/export/${id}`, {}, options as any); - window.location.href = `${this.root}/export/${id}`; - } - - update(id: string, model: ITaskLogViewModel, requestBody: {}): Promise> { - return this._put(this.http, `${this.root}/${id}`, requestBody); - } - - delete(id: string, taskId: string): Promise> { - return this._delete(this.http, `${this.root}/${id}?task=${taskId}`); - } -} diff --git a/worklenz-frontend/src/app/services/api/team-members-api.service.ts b/worklenz-frontend/src/app/services/api/team-members-api.service.ts deleted file mode 100644 index aba8a916..00000000 --- a/worklenz-frontend/src/app/services/api/team-members-api.service.ts +++ /dev/null @@ -1,76 +0,0 @@ -import {Injectable} from '@angular/core'; -import {lastValueFrom} from "rxjs"; -import {HttpClient} from "@angular/common/http"; - -import {APIServiceBase} from "./api-service-base"; -import {ITeamMemberCreateRequest} from "@interfaces/api-models/team-member-create-request"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {ITeamMember} from "@interfaces/team-member"; -import { - ITasksByTeamMembers, - ITeamMemberOverviewByProjectGetResponse, - ITeamMemberOverviewChartGetResponse, - ITeamMemberTreeMap, - ITeamMemberTreeMapResponse, - ITeamMemberViewModel -} from "@interfaces/api-models/team-members-get-response"; -import {toQueryString} from "@shared/utils"; -import {ITeamMembersViewModel} from "@interfaces/api-models/team-members-view-model"; - -@Injectable({ - providedIn: 'root' -}) -export class TeamMembersApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/team-members`; - - constructor( - private http: HttpClient - ) { - super(); - } - - public create(body: ITeamMemberCreateRequest): Promise> { - return this._post(this.http, this.root, body); - } - - public get(index: number, size: number, field: string | null, order: string | null, search: string | null, all = false): Promise> { - const s = encodeURIComponent(search || ''); - const url = `${this.root}${toQueryString({index, size, field, order, search: s, all})}`; - return this._get(this.http, url); - } - - public getById(id: string): Promise> { - return this._get(this.http, `${this.root}/${id}`); - } - - public getAll(projectId: string | null = null): Promise> { - const q = toQueryString({project: projectId}); - return this._get(this.http, `${this.root}/all${q}`); - } - - public update(id: string, body: ITeamMemberCreateRequest): Promise> { - return this._put(this.http, `${this.root}/${id}`, body); - } - - public delete(id: string, email: string): Promise> { - const q = toQueryString({email: email}) - return lastValueFrom(this.http.delete>(`${this.root}/${id}${q}`)); - } - - public getTeamMembersByProjectId(projectId: string): Promise> { - return this._get(this.http, `${this.root}/project/${projectId}`); - } - - public resendInvitation(id: string): Promise> { - return this._put(this.http, `${this.root}/resend-invitation`, {id}); - } - - public toggleMemberActiveStatus(id: string, active: boolean, email: string): Promise> { - const q = toQueryString({active: active, email: email}) - return this._get(this.http, `${this.root}/deactivate/${id}${q}`); - } - - public addTeamMember(id: string, body: ITeamMemberCreateRequest): Promise> { - return this._put(this.http, `${this.root}/add-member/${id}`, body); - } -} diff --git a/worklenz-frontend/src/app/services/api/teams-api.service.ts b/worklenz-frontend/src/app/services/api/teams-api.service.ts deleted file mode 100644 index 42a3c50e..00000000 --- a/worklenz-frontend/src/app/services/api/teams-api.service.ts +++ /dev/null @@ -1,57 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from './api-service-base'; -import {HttpClient} from '@angular/common/http'; -import {lastValueFrom} from 'rxjs'; - -import {IProjectCreateRequest} from '@interfaces/api-models/project-create-request'; -import {IServerResponse} from '@interfaces/api-models/server-response'; -import {IAcceptTeamInvite, ITeam, ITeamInvites} from '@interfaces/team'; -import {ITeamActivateResponse} from '@interfaces/api-models/team-activate-response'; -import {ITeamGetResponse} from "@interfaces/api-models/team-get-response"; - -@Injectable({ - providedIn: 'root', -}) -export class TeamsApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/teams`; - - constructor(private http: HttpClient) { - super(); - } - - create(body: IProjectCreateRequest): Promise> { - return this._post(this.http, this.root, body); - } - - get(): Promise> { - return this._get(this.http, this.root); - } - - getInvites(): Promise> { - return this._get(this.http, `${this.root}/invites`); - } - - getById(id: string): Promise> { - return this._get(this.http, `${this.root}/${id}`); - } - - update(id: string, body: ITeam): Promise> { - return this._put(this.http, `${this.root}/${id}`, body); - } - - setName(name: string): Promise> { - return this._put(this.http, `${this.root}/pik-name`, {name}); - } - - activate(id: string): Promise> { - return this._put(this.http, `${this.root}/activate`, {id}); - } - - accept(body: IAcceptTeamInvite): Promise> { - return this._put(this.http, this.root, body); - } - - delete(id: string): Promise> { - return lastValueFrom(this.http.delete>(`${this.root}/${id}`)); - } -} diff --git a/worklenz-frontend/src/app/services/api/test-gannt-api.service.ts b/worklenz-frontend/src/app/services/api/test-gannt-api.service.ts deleted file mode 100644 index 1e03728b..00000000 --- a/worklenz-frontend/src/app/services/api/test-gannt-api.service.ts +++ /dev/null @@ -1,31 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {IGanttRoadMapTask} from "@interfaces/api-models/gantt"; -import {IDateCreateResponse, ISingleMonth, IWLMember} from "@interfaces/workload"; - -@Injectable({ - providedIn: 'root' -}) -export class TestGanntApiService extends APIServiceBase { - - private readonly root = `${this.API_BASE_URL}/workload-gannt`; - - constructor(private http: HttpClient) { - super(); - } - - getGanntDates(id: string): Promise> { - return this._get(this.http, `${this.root}/chart-dates/${id}`); - } - - getWorkloadTasks(project_id: string, expanded_members: string[], orderByOn: boolean): Promise> { - const body = { - expanded_members: expanded_members, - order_by_on: orderByOn - } - return this._post(this.http, `${this.root}/workload-tasks/${project_id}`, body); - } - -} diff --git a/worklenz-frontend/src/app/services/api/timezones-api.service.ts b/worklenz-frontend/src/app/services/api/timezones-api.service.ts deleted file mode 100644 index fd52efc1..00000000 --- a/worklenz-frontend/src/app/services/api/timezones-api.service.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {ITimezone} from "@interfaces/timezone"; - -@Injectable({ - providedIn: 'root' -}) -export class TimezonesApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/timezones`; - - constructor( - private http: HttpClient - ) { - super(); - } - - update(body: any): Promise> { - return this._put(this.http, this.root, body); - } - - get(): Promise> { - return this._get(this.http, this.root); - } - -} diff --git a/worklenz-frontend/src/app/services/api/todo-list-api.service.ts b/worklenz-frontend/src/app/services/api/todo-list-api.service.ts deleted file mode 100644 index 960bfbef..00000000 --- a/worklenz-frontend/src/app/services/api/todo-list-api.service.ts +++ /dev/null @@ -1,46 +0,0 @@ -import {Injectable} from '@angular/core'; -import {APIServiceBase} from "@api/api-service-base"; -import {HttpClient} from "@angular/common/http"; -import {IServerResponse} from "@interfaces/api-models/server-response"; -import {lastValueFrom} from "rxjs"; -import {ITodoListItem} from "@interfaces/todo-list-item"; -import {toQueryString} from "@shared/utils"; - -@Injectable({ - providedIn: 'root' -}) -export class TodoListApiService extends APIServiceBase { - private readonly root = `${this.API_BASE_URL}/todo-list`; - - constructor( - private http: HttpClient - ) { - super(); - } - - create(body: ITodoListItem): Promise> { - return this._post(this.http, this.root, body); - } - - get(search: string | null, showCompleted: boolean | null = null): Promise> { - const s = encodeURIComponent(search || ''); - const url = `${this.root}${toQueryString({showCompleted, search: s})}`; - return this._get(this.http, url); - } - - update(id: string, body: ITodoListItem): Promise> { - return this._put(this.http, `${this.root}/${id}`, body); - } - - updateStatus(id: string, body: ITodoListItem): Promise> { - return this._put(this.http, `${this.root}/status/${id}`, body); - } - - updateIndex(from: number, to: number): Promise> { - return this._put(this.http, `${this.root}/index`, {from, to}); - } - - delete(id: string): Promise> { - return lastValueFrom(this.http.delete>(`${this.root}/${id}`)); - } -} diff --git a/worklenz-frontend/src/app/services/api/users.service.ts b/worklenz-frontend/src/app/services/api/users.service.ts deleted file mode 100644 index 743ace61..00000000 --- a/worklenz-frontend/src/app/services/api/users.service.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {Injectable} from '@angular/core'; -import {IServerResponse} from '@interfaces/api-models/server-response'; -import {ITeam} from '@interfaces/team'; -import {HttpClient} from '@angular/common/http'; -import {APIServiceBase} from '@api/api-service-base'; - -@Injectable({ - providedIn: 'root' -}) -export class UsersService extends APIServiceBase { - constructor( - private http: HttpClient - ) { - super(); - } - - public changePassword(body: { - password: any; - new_password: any; - confirm_password: any - }): Promise> { - return this._post(this.http, `${this.API_BASE_URL}/change-password`, body); - } -} diff --git a/worklenz-frontend/src/app/services/app.service.spec.ts b/worklenz-frontend/src/app/services/app.service.spec.ts deleted file mode 100644 index a043d400..00000000 --- a/worklenz-frontend/src/app/services/app.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {TestBed} from "@angular/core/testing"; - -import {AppService} from "./app.service"; - -describe("AppService", () => { - let service: AppService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(AppService); - }); - - it("should be created", () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/services/app.service.ts b/worklenz-frontend/src/app/services/app.service.ts deleted file mode 100644 index 5739dd26..00000000 --- a/worklenz-frontend/src/app/services/app.service.ts +++ /dev/null @@ -1,63 +0,0 @@ -import {Injectable} from "@angular/core"; -import {Title} from "@angular/platform-browser"; -import {NzNotificationService} from 'ng-zorro-antd/notification'; -import {FormGroup} from "@angular/forms"; -import {UtilsService} from "@services/utils.service"; - -@Injectable({ - providedIn: "root" -}) -export class AppService { - - // To show activity Indicator while loading a lazy loaded Module - private loadingPath: string | null = null; - - private messagesMap = new Map(); - - constructor( - private readonly title: Title, - private readonly notification: NzNotificationService, - private readonly utils: UtilsService - ) { - } - - public setTitle(title: string): void { - this.title.setTitle(`Worklenz | ${title}`); - } - - public setLoadingPath(path: string | null) { - if (!path) return; - this.loadingPath = path; - } - - public displayErrorsOf(form: FormGroup) { - if (!form) return; - Object.values(form.controls).forEach(control => { - if (control.invalid) { - control.markAsDirty(); - control.updateValueAndValidity({onlySelf: true}); - } - }); - } - - notify(title: string, message: string, done: boolean) { - if (this.messagesMap.has(message)) return; - - this.messagesMap.set(message, true); - - const safeTitle = this.utils.sanitizeHtml(title); - const safeMessage = this.utils.sanitizeHtml(message); - this.notification - .blank(safeTitle || '', safeMessage || '', { - nzCloseIcon: "", - nzPlacement: "topRight", - nzPauseOnHover: true, - nzStyle: { - "border-radius": "4px" - } - }) - .onClose.subscribe(next => { - this.messagesMap.delete(message); - }); - } -} diff --git a/worklenz-frontend/src/app/services/auth.service.spec.ts b/worklenz-frontend/src/app/services/auth.service.spec.ts deleted file mode 100644 index ee124a68..00000000 --- a/worklenz-frontend/src/app/services/auth.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {TestBed} from '@angular/core/testing'; - -import {AuthService} from './auth.service'; - -describe('AuthService', () => { - let service: AuthService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(AuthService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/services/auth.service.ts b/worklenz-frontend/src/app/services/auth.service.ts deleted file mode 100644 index dada4696..00000000 --- a/worklenz-frontend/src/app/services/auth.service.ts +++ /dev/null @@ -1,85 +0,0 @@ -import {Injectable} from '@angular/core'; -import {Router} from "@angular/router"; -import {IAuthorizeResponse} from "@interfaces/api-models/authorize-response"; -import {AuthApiService} from "@api/auth-api.service"; -import {ILocalSession} from "@interfaces/api-models/local-session"; -import {log_error} from "@shared/utils"; -import {deleteSession, getSession, hasSession, setSession} from "@shared/session-helper"; -import {NzModalService} from "ng-zorro-antd/modal"; - -@Injectable({ - providedIn: 'root' -}) -export class AuthService { - public get role() { - const user = this.getCurrentSession(); - if (!user) return 'Unknown'; - if (user.owner) return 'Owner'; - if (user.is_admin) return 'Admin'; - return 'Member'; - } - - constructor( - private readonly router: Router, - private readonly api: AuthApiService, - private readonly modal: NzModalService - ) { - } - - public isAuthenticated(): boolean { - return !!this.getCurrentSession(); - } - - public setCurrentSession(user: ILocalSession): void { - setSession(user); - } - - public getCurrentSession(): ILocalSession | null { - return getSession(); - } - - public isOwnerOrAdmin() { - return !!(this.getCurrentSession()?.owner || this.getCurrentSession()?.is_admin); - } - - public async signOut() { - try { - if (hasSession()) { - deleteSession(); - await this.api.logout(); - } - } catch (e) { - // ignored - } - } - - private onSignOutConfirm() { - void this.signOut(); - window.location.href = "/secure/logout"; - } - - public signOutWithConfirm() { - this.modal.confirm({ - nzTitle: 'Sign out from the Worklenz?', - nzOnOk: () => { - this.onSignOutConfirm(); - } - }); - } - - public async authorize() { - try { - const res: IAuthorizeResponse = await this.api.authorize(); - if (res.authenticated) { - this.setCurrentSession(res.user); - return true; - } - await this.signOut(); - } catch (e) { - log_error(e); - await this.signOut(); - } - return false; - } -} - diff --git a/worklenz-frontend/src/app/services/menu-service.spec.ts b/worklenz-frontend/src/app/services/menu-service.spec.ts deleted file mode 100644 index ffe6d349..00000000 --- a/worklenz-frontend/src/app/services/menu-service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {TestBed} from '@angular/core/testing'; - -import {MenuService} from './menu.service'; - -describe('MenuService', () => { - let service: MenuService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(MenuService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/services/menu.service.ts b/worklenz-frontend/src/app/services/menu.service.ts deleted file mode 100644 index c18ddcb6..00000000 --- a/worklenz-frontend/src/app/services/menu.service.ts +++ /dev/null @@ -1,36 +0,0 @@ -import {Injectable} from '@angular/core'; -import {dispatchMenuChange} from "@shared/events"; - -@Injectable({ - providedIn: 'root' -}) -export class MenuService { - public readonly CLIENTS_MENU = "clients"; - public readonly JOB_TITLES_MENU = "job-titles"; - public readonly TEAMS_MENU = "teams"; - public readonly LABELS_MENU = "labels"; - public readonly TASK_STATUSES_MENU = "task-statuses"; - private readonly prefix = "worklenz.pinned-tab"; - public readonly TASK_TEMPLATES_MENU = "task-templates"; - - isPinned(key: string) { - return !!localStorage.getItem(`${this.prefix}.${key}`); - } - - public toggle(key: string) { - if (this.isPinned(key)) { - this.unpin(key); - } else { - this.pin(key); - } - dispatchMenuChange(); - } - - private pin(key: string) { - localStorage.setItem(`${this.prefix}.${key}`, "1"); - } - - private unpin(key: string) { - localStorage.removeItem(`${this.prefix}.${key}`); - } -} diff --git a/worklenz-frontend/src/app/services/notification-settings.service.spec.ts b/worklenz-frontend/src/app/services/notification-settings.service.spec.ts deleted file mode 100644 index 94e11c2a..00000000 --- a/worklenz-frontend/src/app/services/notification-settings.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {TestBed} from '@angular/core/testing'; - -import {NotificationSettingsService} from './notification-settings.service'; - -describe('NotificationSettingsService', () => { - let service: NotificationSettingsService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(NotificationSettingsService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/services/notification-settings.service.ts b/worklenz-frontend/src/app/services/notification-settings.service.ts deleted file mode 100644 index 9ac3cfa9..00000000 --- a/worklenz-frontend/src/app/services/notification-settings.service.ts +++ /dev/null @@ -1,30 +0,0 @@ -import {Injectable} from '@angular/core'; -import {INotificationSettings} from "@interfaces/notification-settings"; -import {BehaviorSubject, ReplaySubject} from "rxjs"; - -@Injectable({ - providedIn: 'root' -}) -export class NotificationSettingsService { - private readonly _countsUpdateSbj$ = new BehaviorSubject(0); - private readonly _clickSbj$ = new ReplaySubject<{ project: string; task: string; }>(); - - settings: INotificationSettings = {}; - count = 0; - - public get onCountsUpdate$() { - return this._countsUpdateSbj$.asObservable(); - } - - public get onNotificationClick() { - return this._clickSbj$.asObservable(); - } - - public emitCountsUpdate() { - this._countsUpdateSbj$.next(0); - } - - public emitNotificationClick(data: { project: string; task: string; }) { - this._clickSbj$.next(data); - } -} diff --git a/worklenz-frontend/src/app/services/project-form-service.service.ts b/worklenz-frontend/src/app/services/project-form-service.service.ts deleted file mode 100644 index c4645573..00000000 --- a/worklenz-frontend/src/app/services/project-form-service.service.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {Injectable} from '@angular/core'; -import {Subject} from "rxjs"; - -@Injectable({ - providedIn: 'root' -}) -export class ProjectFormService { - private readonly _updateSbj$ = new Subject(); - private readonly _onProjectMemberChangeSbj$ = new Subject(); - - public get onProjectUpdate() { - return this._updateSbj$.asObservable(); - } - - public get onMemberAssignOrRemoveReProject() { - return this._onProjectMemberChangeSbj$.asObservable(); - } - - public emitProjectUpdate() { - this._updateSbj$.next(); - } - - public emitMemberAssignOrRemoveReProject() { - this._onProjectMemberChangeSbj$.next(); - } - -} diff --git a/worklenz-frontend/src/app/services/project-phases.service.ts b/worklenz-frontend/src/app/services/project-phases.service.ts deleted file mode 100644 index e2deab23..00000000 --- a/worklenz-frontend/src/app/services/project-phases.service.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {Injectable} from '@angular/core'; -import {TaskListV2Service} from "../administrator/modules/task-list-v2/task-list-v2.service"; -import {Subject} from "rxjs"; - -@Injectable({ - providedIn: 'root' -}) -export class ProjectPhasesService { - private readonly _labelChangeSbj$ = new Subject(); - private readonly _phaseOptionsChangeSbj$ = new Subject(); - private readonly DEFAULT_LABEL = 'Phase'; - - private _label: string | null = null; - - public get label() { - return this._label || this.DEFAULT_LABEL; - } - - private set label(value) { - this._label = value || this.DEFAULT_LABEL; - } - - public get onLabelChange() { - return this._labelChangeSbj$.asObservable(); - } - - public get onPhaseOptionsChange() { - return this._phaseOptionsChangeSbj$.asObservable(); - } - - constructor( - private readonly list: TaskListV2Service - ) { - } - - public updateLabel(label: string) { - const phase = this.list.GROUP_BY_OPTIONS.find(p => p.value === this.list.GROUP_BY_PHASE_VALUE); - if (phase) { - this.label = label; - phase.label = this.label; - this._labelChangeSbj$.next(this.label); - } - } - - public emitOptionsChange() { - this._phaseOptionsChangeSbj$.next(); - } -} diff --git a/worklenz-frontend/src/app/services/project-template.service.ts b/worklenz-frontend/src/app/services/project-template.service.ts deleted file mode 100644 index 8b0dbb1f..00000000 --- a/worklenz-frontend/src/app/services/project-template.service.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {Injectable} from '@angular/core'; -import {Subject} from "rxjs"; -import {PtTaskListService} from "../administrator/settings/project-template-edit-view/services/pt-task-list.service"; - -@Injectable({ - providedIn: 'root' -}) -export class ProjectTemplateService { - private readonly _labelChangeSbj$ = new Subject(); - private readonly _phaseOptionsChangeSbj$ = new Subject(); - private readonly DEFAULT_LABEL = 'Phase'; - - private _label: string | null = null; - - public get label() { - return this._label || this.DEFAULT_LABEL; - } - - private set label(value) { - this._label = value || this.DEFAULT_LABEL; - } - - public get onLabelChange() { - return this._labelChangeSbj$.asObservable(); - } - - public get onPhaseOptionsChange() { - return this._phaseOptionsChangeSbj$.asObservable(); - } - - constructor( - private readonly list: PtTaskListService - ) { - } - - public updateLabel(label: string) { - const phase = this.list.GROUP_BY_OPTIONS.find(p => p.value === this.list.GROUP_BY_PHASE_VALUE); - if (phase) { - this.label = label; - phase.label = this.label; - this._labelChangeSbj$.next(this.label); - } - } - - public emitOptionsChange() { - this._phaseOptionsChangeSbj$.next(); - } -} diff --git a/worklenz-frontend/src/app/services/project-updates.service.ts b/worklenz-frontend/src/app/services/project-updates.service.ts deleted file mode 100644 index 92652811..00000000 --- a/worklenz-frontend/src/app/services/project-updates.service.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Injectable } from '@angular/core'; -import {Subject} from "rxjs"; - -@Injectable({ - providedIn: 'root' -}) -export class ProjectUpdatesService { - private readonly _emitRefreshSbj$ = new Subject(); - private readonly _disableBadgeSbj$ = new Subject(); - private readonly _getLastUpdateSbj$ = new Subject(); - - public get onRefresh() { - return this._emitRefreshSbj$.asObservable(); - } - - public get onBadgeDisable() { - return this._disableBadgeSbj$.asObservable(); - } - - public get onGetLatestUpdate() { - return this._getLastUpdateSbj$.asObservable(); - } - - public emitRefresh() { - this._emitRefreshSbj$.next(); - } - - public emitBadgeDisable() { - this._disableBadgeSbj$.next(); - } - - public emitGetLastUpdate() { - this._getLastUpdateSbj$.next(); - } - -} diff --git a/worklenz-frontend/src/app/services/socket.service.ts b/worklenz-frontend/src/app/services/socket.service.ts deleted file mode 100644 index fbf5f0aa..00000000 --- a/worklenz-frontend/src/app/services/socket.service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {Injectable} from '@angular/core'; -import {ReplaySubject, Subject} from "rxjs"; - -@Injectable({ - providedIn: 'root' -}) -export class SocketService { - private _socketConnectSbj$ = new Subject(); - private _socketDisconnectSbj$ = new Subject(); - private _socketLoginSbj$ = new ReplaySubject(); - - public get onSocketLoginSuccess$() { - return this._socketLoginSbj$.asObservable(); - } - - public get onSocketConnect$() { - return this._socketConnectSbj$.asObservable(); - } - - public get onSocketDisconnect$() { - return this._socketDisconnectSbj$.asObservable(); - } - - public emitSocketLoginSuccess() { - this._socketLoginSbj$.next(); - } - - public emitSocketConnect() { - this._socketConnectSbj$.next(); - } - - public emitSocketDisconnect() { - return this._socketDisconnectSbj$.next(); - } -} diff --git a/worklenz-frontend/src/app/services/utils.service.spec.ts b/worklenz-frontend/src/app/services/utils.service.spec.ts deleted file mode 100644 index b54a40bc..00000000 --- a/worklenz-frontend/src/app/services/utils.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {TestBed} from '@angular/core/testing'; - -import {UtilsService} from './utils.service'; - -describe('UtilsService', () => { - let service: UtilsService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(UtilsService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/services/utils.service.ts b/worklenz-frontend/src/app/services/utils.service.ts deleted file mode 100644 index baa27756..00000000 --- a/worklenz-frontend/src/app/services/utils.service.ts +++ /dev/null @@ -1,78 +0,0 @@ -import {Injectable} from '@angular/core'; -import {AvatarNamesMap} from "@shared/constants"; -import moment from "moment/moment"; - -@Injectable({ - providedIn: 'root' -}) -export class UtilsService { - - getColor(name?: string) { - return AvatarNamesMap[name?.charAt(0).toUpperCase() || 'A']; - } - - checkForMinDate(start?: any) { - return (end: Date) => { - if (!start) return false; - return !moment(start).isSame(end, 'day') && moment(start).isAfter(end); - }; - } - - checkForMaxDate(end?: any) { - return (start: Date) => { - if (!end) return false; - return !moment(end).isSame(start, 'day') && moment(start).isAfter(end); - }; - } - - isTestServer() { - const testServers = ["localhost", "uat.worklenz.com", "dev.worklenz.com"]; - const host = window.location.hostname; - return testServers.includes(host); - } - - toRound(value: string | number) { - return /\d+/.test(value as string) ? Math.ceil(+value) : 0; - } - - buildTimeString(hours: number, minutes: number, seconds: number) { - const h = hours > 0 ? `${hours}h` : ''; - const m = `${minutes}m`; - const s = `${seconds}s`; - return `${h} ${m} ${s}`.trim(); - } - - handleLastIndex(total: number, length: number, index: number, callback: (index: number) => void) { - if (total > 0 && length === 0) { - const i = index - 1; - index = i < 0 ? 0 : i; - if (index > 0) - callback(index); - } - } - - sortBySelection(data: Array<{ selected?: boolean; }>) { - data.sort((a, b) => { - if (a.selected && b.selected) return 0; - if (a.selected) return -1; - return 1; - }); - }; - - sortByPending(data: Array<{ is_pending?: boolean; }>) { - data.sort((a, b) => { - if (!a.is_pending && !b.is_pending) return 0; - if (!a.is_pending) return -1; - return 1; - }); - }; - - sanitizeHtml(value: string) { - return value - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); - } -} diff --git a/worklenz-frontend/src/app/services/worklenz.analytics.ts b/worklenz-frontend/src/app/services/worklenz.analytics.ts deleted file mode 100644 index 68ecc671..00000000 --- a/worklenz-frontend/src/app/services/worklenz.analytics.ts +++ /dev/null @@ -1,66 +0,0 @@ -// import mixpanel, {Dict} from 'mixpanel-browser'; -// import {getSession} from "@shared/session-helper"; -// import {ILocalSession} from "@interfaces/api-models/local-session"; -// import {environment} from "../../environments/environment"; -// -// export class WorklenzAnalytics { -// private static readonly LOCAL_SERVERS = "a280d9b1efe06ac76b553e73e229cb47"; -// private static readonly TEST_SERVERS = "a04c24bcf1cf06cc5ceceeb5179c0e90"; -// private static readonly PRODUCTION = "bb330b6bd25db4a6c988da89046f4b80"; -// -// private static get TOKEN() { -// const host = window.location.host; -// if (host === "uat.worklenz.com" || host === "dev.worklenz.com") -// return this.TEST_SERVERS; -// if (host === "app.worklenz.com") -// return this.PRODUCTION; -// return this.LOCAL_SERVERS; -// } -// -// private static session: ILocalSession | null = null; -// -// public static init() { -// mixpanel.init(this.TOKEN, {debug: !environment.production}); -// } -// -// public static setIdentity(user: ILocalSession) { -// if (user.id) { -// mixpanel.identify(user.id); -// mixpanel.people.set({ -// $user_id: user.id, -// $name: user.name, -// $email: user.email, -// $avatar: user.avatar_url -// }); -// } -// } -// -// public static reset() { -// mixpanel.reset(); -// } -// -// public static track(event: string, properties?: Dict) { -// try { -// let props: any = {}; -// -// this.updateSession(); -// -// if (this.session) props.id = this.session.user_no; -// -// if (properties) { -// props = {...properties, ...props}; -// } -// -// mixpanel.track(event, props); -// } catch (e) { -// // ignore -// } -// } -// -// private static updateSession() { -// if (this.session) return; -// const session = getSession(); -// if (session && session.user_no) -// this.session = session; -// } -// } diff --git a/worklenz-frontend/src/app/session-expired/session-expired.component.html b/worklenz-frontend/src/app/session-expired/session-expired.component.html deleted file mode 100644 index 1ccf3112..00000000 --- a/worklenz-frontend/src/app/session-expired/session-expired.component.html +++ /dev/null @@ -1,22 +0,0 @@ -
    -
    -
    -
    -
    -
    -
    -
    - Worklenz -
    -
    Please sign in again
    -

    - You were signed out of your account. Please press 'Reload' to sign in to the Worklenz again. -

    - -
    -
    -
    -
    -
    -
    -
    diff --git a/worklenz-frontend/src/app/session-expired/session-expired.component.scss b/worklenz-frontend/src/app/session-expired/session-expired.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/app/session-expired/session-expired.component.spec.ts b/worklenz-frontend/src/app/session-expired/session-expired.component.spec.ts deleted file mode 100644 index 618380d6..00000000 --- a/worklenz-frontend/src/app/session-expired/session-expired.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; - -import {SessionExpiredComponent} from './session-expired.component'; - -describe('SessionExpiredComponent', () => { - let component: SessionExpiredComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [SessionExpiredComponent] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(SessionExpiredComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/worklenz-frontend/src/app/session-expired/session-expired.component.ts b/worklenz-frontend/src/app/session-expired/session-expired.component.ts deleted file mode 100644 index 23a52c1d..00000000 --- a/worklenz-frontend/src/app/session-expired/session-expired.component.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {Component} from '@angular/core'; -import {Router} from "@angular/router"; -import {AppService} from "@services/app.service"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzTypographyModule} from "ng-zorro-antd/typography"; - -@Component({ - selector: 'worklenz-session-expired', - templateUrl: './session-expired.component.html', - styleUrls: ['./session-expired.component.scss'], - imports: [ - NzButtonModule, - NzTypographyModule - ], - standalone: true -}) -export class SessionExpiredComponent { - loading = false; - - constructor( - private router: Router, - private app: AppService - ) { - this.app.setTitle("Please sign in again"); - } - - reload() { - this.loading = true; - setTimeout(() => { - void this.router.navigate(['/auth/login']); - }, 1000); - } -} diff --git a/worklenz-frontend/src/app/shared/constants.ts b/worklenz-frontend/src/app/shared/constants.ts deleted file mode 100644 index 13a42eba..00000000 --- a/worklenz-frontend/src/app/shared/constants.ts +++ /dev/null @@ -1,120 +0,0 @@ -export const ProjectsDefaultColorCodes = [ - "#154c9b", - "#3b7ad4", - "#70a6f3", - "#7781ca", - "#9877ca", - "#c178c9", - "#ee87c5", - "#ca7881", - "#75c9c0", - "#75c997", - "#80ca79", - "#aacb78", - "#cbbc78", - "#cb9878", - "#bb774c", - "#905b39", - "#903737", - "#bf4949", - "#f37070", - "#ff9c3c", - "#fbc84c", - "#cbc8a1", - "#a9a9a9", - "#767676" -]; - -export const AvatarNamesMap: { [x: string]: string } = { - "A": "#154c9b", - "B": "#3b7ad4", - "C": "#70a6f3", - "D": "#7781ca", - "E": "#9877ca", - "F": "#c178c9", - "G": "#ee87c5", - "H": "#ca7881", - "I": "#75c9c0", - "J": "#75c997", - "K": "#80ca79", - "L": "#aacb78", - "M": "#cbbc78", - "N": "#cb9878", - "O": "#bb774c", - "P": "#905b39", - "Q": "#903737", - "R": "#bf4949", - "S": "#f37070", - "T": "#ff9c3c", - "U": "#fbc84c", - "V": "#cbc8a1", - "W": "#a9a9a9", - "X": "#767676", - "Y": "#cb9878", - "Z": "#903737", - "+": "#9e9e9e" -}; - -export const PhaseColorCodes = [ - "#154c9b", - "#3b7ad4", - "#70a6f3", - "#7781ca", - "#9877ca", - "#c178c9", - "#ee87c5", - "#ca7881", - "#75c9c0", - "#75c997", - "#80ca79", - "#aacb78", - "#cbbc78", - "#cb9878", - "#bb774c", - "#905b39", - "#903737", - "#bf4949", - "#f37070", - "#ff9c3c", - "#fbc84c", - "#cbc8a1", - "#a9a9a9", - "#767676", - "#cb9878", - "#903737", - "#9e9e9e" -]; - -export const PriorityColorCodes: { [x: number]: string; } = { - 0: "#75c997", - 1: "#fbc84c", - 2: "#f37070" -} - -export const DEFAULT_PAGE_SIZE = 20; -export const WORKLENZ_REDIRECT_PROJ_KEY = "worklenz.redirect_proj"; - -export const ALPHA_CHANNEL = '69'; - -export const EMAIL_REGEXP = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; -export const WHITESPACE_REGEXP = /^(\s+\S+\s*)*(?!\s).*$/; -export const DEFAULT_TASK_NAME = "Untitled Task"; - -export const YESTERDAY = "YESTERDAY"; -export const LAST_WEEK = "LAST_WEEK"; -export const LAST_MONTH = "LAST_MONTH"; -export const LAST_QUARTER = "LAST_QUARTER"; -export const PREV_WEEK = "PREV_WEEK"; -export const PREV_MONTH = "PREV_MONTH"; - -export const ALL_TIME = "ALL_TIME"; - -// *Sync with the server -export const PASSWORD_POLICY = "Minimum of 8 characters, with upper and lowercase and a number and a symbol."; - -export const HTML_TAG_REGEXP = /<\/?[^>]+>/gi; -export const UNMAPPED = "Unmapped"; - -export const DRAWER_ANIMATION_INTERVAL = 200; - -export const GANNT_COLUMN_WIDTH = 35; diff --git a/worklenz-frontend/src/app/shared/events.ts b/worklenz-frontend/src/app/shared/events.ts deleted file mode 100644 index 47260c1e..00000000 --- a/worklenz-frontend/src/app/shared/events.ts +++ /dev/null @@ -1,40 +0,0 @@ -export const EventProfileUpdate = "worklenz.events.profile_update"; -export const EventTaskCreatedOrUpdate = "worklenz.tasks.update"; -export const EventProjectCreatedOrUpdated = "worklenz.tasks.update"; -export const EventMenuChanged = "worklenz.menu.update"; -export const EventStatusChanged = "worklenz.status.update"; -export const EventTaskTimeLogChange = "worklenz.tasks.timelog.update"; -export const EventProfilePictureChange = "worklenz.profile.avatar_change"; -export const EventTaskCommentCreate = "worklenz.task_comments.create"; - -export function dispatchProfileUpdate() { - document.dispatchEvent(new Event(EventProfileUpdate)); -} - -export function dispatchTasksChange() { - document.dispatchEvent(new Event(EventTaskCreatedOrUpdate)); -} - -export function dispatchTasksTimeLogChange() { - document.dispatchEvent(new Event(EventTaskTimeLogChange)); -} - -export function dispatchProjectChange() { - document.dispatchEvent(new Event(EventProjectCreatedOrUpdated)); -} - -export function dispatchMenuChange() { - document.dispatchEvent(new Event(EventMenuChanged)); -} - -export function dispatchStatusChange() { - document.dispatchEvent(new Event(EventStatusChanged)); -} - -export function dispatchProfilePictureChange() { - document.dispatchEvent(new Event(EventProfilePictureChange)); -} - -export function dispatchTaskCommentCreate() { - document.dispatchEvent(new Event(EventTaskCommentCreate)); -} diff --git a/worklenz-frontend/src/app/shared/session-helper.ts b/worklenz-frontend/src/app/shared/session-helper.ts deleted file mode 100644 index 825b418a..00000000 --- a/worklenz-frontend/src/app/shared/session-helper.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {ILocalSession} from "@interfaces/api-models/local-session"; - -export const WORKLENZ_SESSION_ID = "worklenz.sid"; -const storage: Storage = localStorage; - -export function setSession(user: ILocalSession): void { - storage.setItem(WORKLENZ_SESSION_ID, btoa(unescape(encodeURIComponent(JSON.stringify(user))))); - // storage.setItem(WORKLENZ_SESSION_ID, btoa(JSON.stringify(user))); -} - -export function getSession(): ILocalSession | null { - try { - return JSON.parse(atob(storage.getItem(WORKLENZ_SESSION_ID))); - } catch (e) { - return null; - } -} - -export function hasSession() { - return !!storage.getItem(WORKLENZ_SESSION_ID); -} - -export function deleteSession() { - storage.removeItem(WORKLENZ_SESSION_ID); -} diff --git a/worklenz-frontend/src/app/shared/utils.ts b/worklenz-frontend/src/app/shared/utils.ts deleted file mode 100644 index a0933b9e..00000000 --- a/worklenz-frontend/src/app/shared/utils.ts +++ /dev/null @@ -1,120 +0,0 @@ -import {EMAIL_REGEXP} from "@shared/constants"; -import moment from "moment/moment"; - -export const getLetters = (str: { split: (arg0: string) => any[][]; }) => { - return str - .split(' ') - .map((word: any[]) => word[0]) - .join(''); -}; - -export function toQueryString(obj: any) { - const query = []; - for (const key in obj) { - if (typeof obj[key] !== undefined && obj[key] !== null) { - query.push(`${key}=${obj[key]}`); - } - } - return "?" + query.join("&"); -} - -const IconsMap: { [x: string]: string; } = { - ai: "ai.png", - avi: "avi.png", - css: "css.png", - csv: "csv.png", - doc: "doc.png", - docx: "doc.png", - exe: "exe.png", - html: "html.png", - js: "js.png", - jpg: "jpg.png", - jpeg: "jpg.png", - json: "json.png", - mp3: "mp3.png", - mp4: "mp4.png", - pdf: "pdf.png", - png: "png.png", - ppt: "ppt.png", - psd: "psd.png", - search: "search.png", - svg: "svg.png", - txt: "txt.png", - xls: "xls.png", - xml: "xml.png", - zip: "zip.png", -} - -export function getFileIcon(type?: string) { - return IconsMap[type as string] || IconsMap["search"]; -} - -export const getBase64 = (file: File): Promise => - new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = () => resolve(reader.result); - reader.onerror = error => reject(error); - }); - -export function log_error(error: any) { - console.error("Worklenz Error: ", error); -} - -export function isValidateEmail(email: string) { - return EMAIL_REGEXP.test(String(email).toLowerCase()); -} - -export function smallId(length: number) { - // Generate a random number - const l = Array.from(Array(length).keys()).fill(0).join(''); - const size = +`1${l}`; - let number = ~~(Math.random() * size); - let string = ""; - // Convert the number to base-26 representation - while (number > 0) { - const digit = number % 26; - string = String.fromCharCode(65 + digit) + string; - number = ~~(number / 26); - } - // Add a check digit to the end - const sum = string.split("").map(char => char.charCodeAt(0) - 65).reduce((a, b) => a + b); - const checkDigit = sum % 26; - return string + String.fromCharCode(65 + checkDigit); -} - -export function deepClone(obj: T): T { - if (typeof window.structuredClone !== "undefined") - return structuredClone(obj); - - try { - return JSON.parse(JSON.stringify(obj)); - } catch (e) { - return obj; - } -} - -export function calculateTaskCompleteRatio(totalCompleted: number, totalTasks: number) { - if (totalCompleted === 0 && totalTasks === 0) return 0; - const ratio = ((totalCompleted / totalTasks) * 100); - return ratio == Infinity ? 100 : +ratio.toFixed(); -} - -/** - * @param seconds default 200ms - */ -export function waitForSeconds(seconds = 200): Promise { - return new Promise((resolve) => { - setTimeout(() => { - return resolve(); - }, seconds); - }); -} - -// export const formatGanttDate = (date: moment.MomentInput) => date ? moment(date).format("YYYY-MM-DDT00:00:00Z") : null; -export const formatGanttDate = (date: any) => date ? date : null; - -export function formatGanttEndDate(date: moment.MomentInput) { - const timeZoneOffset = new Date().getTimezoneOffset(); - return date ? moment(date).add(timeZoneOffset, "minutes").format("YYYY-MM-DDT00:00:00Z") : null; -} diff --git a/worklenz-frontend/src/app/shared/worklenz-analytics-events.ts b/worklenz-frontend/src/app/shared/worklenz-analytics-events.ts deleted file mode 100644 index 522b23ba..00000000 --- a/worklenz-frontend/src/app/shared/worklenz-analytics-events.ts +++ /dev/null @@ -1,157 +0,0 @@ -// // _ -// -// // Login page -// export const evt_login_page_visit = "login_page_visit"; -// export const evt_login_with_email_click = "login_with_email_click"; -// export const evt_login_with_google_click = "login_with_google_click"; -// export const evt_login_remember_me_click = "login_remember_me_click"; -// -// // Sign up page -// export const evt_signup_page_visit = "signup_page_visit"; -// export const evt_signup_with_email_click = "signup_with_email_click"; -// export const evt_signup_with_google_click = "signup_with_google_click"; -// -// // Account setup -// export const evt_account_setup_visit = "account_setup_visit"; -// export const evt_account_setup_complete = "account_setup_complete"; -// export const evt_account_setup_skip_invite = "account_setup_skip_invite"; -// -// // Reset password page -// export const evt_reset_password_click = "reset_password_click"; -// -// // Projects list -// export const evt_projects_page_visit = "projects_page_visit"; -// export const evt_projects_create_click = "projects_create_click"; -// export const evt_projects_create = "projects_create"; -// export const evt_projects_refresh_click = "projects_refresh_click"; -// export const evt_projects_search = "projects_search"; -// export const evt_projects_archive = "projects_archive"; -// export const evt_projects_unarchive = "projects_unarchive"; -// export const evt_projects_settings_click = "projects_settings_click"; -// -// export const evt_projects_archive_all = "projects_archive_all"; -// export const evt_projects_unarchive_all = "projects_unarchive_all"; -// -// // Project view -// export const evt_project_board_visit = "project_board_visit"; -// export const evt_project_workload_visit = "project_workload_visit"; -// export const evt_project_roadmap_visit = "project_roadmap_visit"; -// export const evt_project_insights_overview_visit = "project_insights_overview_visit"; -// export const evt_project_insights_members_visit = "project_insights_members_visit"; -// export const evt_project_insights_tasks_visit = "project_insights_tasks_visit"; -// export const evt_project_files_visit = "project_files_visit"; -// export const evt_project_members_visit = "project_members_visit"; -// export const evt_project_task_create = "project_task_create"; -// export const evt_project_invite_members_click = "project_invite_members_click"; -// export const evt_project_invite_members = "project_invite_members"; -// export const evt_project_refresh_click = "project_refresh_click"; -// export const evt_project_settings_click = "project_settings_click"; -// export const evt_project_import_tasks_click = "project_import_tasks_click"; -// export const evt_project_import_tasks = "project_import_tasks"; -// export const evt_project_update = "project_update"; -// export const evt_project_board_open_task = "project_board_open_task"; -// export const evt_project_board_transition_task = "project_board_transition_task"; -// export const evt_project_board_update_task_order = "project_board_update_task_order"; -// export const evt_project_board_column_setting_click = "project_board_column_setting_click"; -// export const evt_project_board_create_status_click = "project_board_create_status_click"; -// export const evt_project_board_create_status = "project_board_create_status"; -// export const evt_project_board_create_task_click = "project_board_create_task_click"; -// -// // Task list -// export const evt_project_task_list_visit = "project_task_list_visit"; -// export const evt_project_task_list_show_archived = "project_task_list_show_archived"; -// export const evt_project_task_list_bulk_change_status = "project_task_list_bulk_change_status"; -// -// export const evt_project_task_list_bulk_change_priority = "project_task_list_bulk_change_priority" -// export const evt_project_task_list_bulk_change_phase = "project_task_list_bulk_change_phase" -// export const evt_project_task_list_bulk_update_labels = "project_task_list_bulk_update_labels"; -// export const evt_project_task_list_bulk_assign_me = "project_task_list_bulk_assign_me"; -// export const evt_project_task_list_bulk_assign_members = "project_task_list_bulk_assign_members"; -// export const evt_project_task_list_bulk_archive = "project_task_list_bulk_archive"; -// export const evt_project_task_list_bulk_delete = "project_task_list_bulk_delete"; -// export const evt_project_task_list_context_menu_assign_me = "project_task_list_context_menu_assign_me"; -// export const evt_project_task_list_context_menu_archive = "project_task_list_context_menu_archive"; -// export const evt_project_task_list_context_menu_delete = "project_task_list_context_menu_delete"; -// export const evt_project_task_list_create_task = "project_task_list_create_task"; -// export const evt_project_task_list_create_subtask = "project_task_list_create_subtask"; -// export const evt_project_task_list_open_task = "project_task_list_open_task"; -// export const evt_project_task_list_drag_and_move = "project_task_list_drag_and_move"; -// export const evt_project_task_list_show_fields = "project_task_list_show_fields"; -// export const evt_project_task_list_search_task = "project_task_list_search_task"; -// -// // People -// export const evt_people_page_visit = "people_page_visit"; -// export const evt_people_refresh_click = "people_refresh_click"; -// export const evt_people_search = "people_search"; -// export const evt_people_create_click = "people_create_click"; -// export const evt_people_click = "people_click"; -// export const evt_people_create = "people_create"; -// export const evt_people_delete = "people_delete"; -// export const evt_people_activate = "people_activate"; -// export const evt_people_deactivate = "people_deactivate"; -// export const evt_project_import_from_template_click = "project_import_from_template_click" -// -// // Schedule -// export const evt_schedule_page_visit = "schedule_page_visit"; -// -// // Workload -// export const evt_workload_drag_change_date = "workload_drag_change_date"; -// export const evt_workload_drag_move = "workload_drag_move"; -// export const evt_workload_task_open = "workload_task_open"; -// -// // Roadmap -// export const evt_roadmap_task_create = "roadmap_task_create"; -// export const evt_roadmap_sub_task_create = "roadmap_sub_task_create"; -// export const evt_roadmap_drag_change_date = "roadmap_drag_change_date"; -// export const evt_roadmap_drag_move = "roadmap_drag_move"; -// export const evt_roadmap_task_open = "roadmap_task_open"; -// export const evt_roadmap_task_drag_n_sort = "roadmap_task_drag_n_sort"; -// -// // Settings -// export const evt_settings_profile_visit = "settings_profile_visit"; -// export const evt_settings_notifications_visit = "settings_notifications_visit"; -// export const evt_settings_clients_visit = "settings_clients_visit"; -// export const evt_settings_job_titles_visit = "settings_job_titles_visit"; -// export const evt_settings_labels_visit = "settings_labels_visit"; -// export const evt_settings_categories_visit = "settings_categories_visit"; -// export const evt_settings_task_templates_visit = "settings_task_templates_visit"; -// export const evt_settings_teams_visit = "settings_teams_visit"; -// export const evt_settings_change_password_visit = "settings_change_password_visit"; -// export const evt_settings_language_and_region_visit = "settings_language_and_region_visit"; -// export const evt_settings_profile_update = "settings_profile_update"; -// export const evt_settings_notifications_update = "settings_notifications_update"; -// export const evt_settings_clients_create = "settings_clients_create"; -// export const evt_settings_job_titles_create = "settings_job_titles_create"; -// export const evt_settings_labels_delete = "settings_labels_delete"; -// export const evt_settings_category_delete = "settings_category_delete"; -// export const evt_settings_task_templates_delete = "settings_task_templates_delete"; -// export const evt_settings_profile_picture_update = "settings_profile_picture_update"; -// -// // Common -// export const evt_common_switch_team = "common_switch_team"; -// export const evt_common_display_notifications = "common_display_notifications"; -// export const evt_common_logout = "common_logout"; -// -// // Reporting -// export const evt_reporting_overview = "reporting_overview_visit"; -// export const evt_reporting_allocation = "reporting_allocation_visit"; -// export const evt_reporting_projects_overview = "reporting_projects_overview_visit"; -// export const evt_reporting_projects_custom = "reporting_projects_custom_visit"; -// -// // Reporting -// export const evt_billing_current_bill = "billing_current_bill"; -// export const evt_billing_configuration = "billing_configuration"; -// export const evt_billing_view_plans_modal = "billing_view_plans_modal"; -// export const evt_billing_pause_plan = "billing_pause_plan"; -// export const evt_billing_resume_plan = "billing_resume_plan"; -// -// // Reporting -// export const evt_admin_center_teams_visit = "admin_center_teams_visit"; -// export const evt_admin_center_users_visit = "admin_center_users_visit"; -// export const evt_admin_center_overview_visit = "admin_center_overview_visit"; -// export const evt_admin_center_teams_delete = "admin_center_teams_delete"; -// export const evt_admin_center_team_settings = "admin_center_team_settings"; -// -// // Project -// -// export const evt_project_default_view_pinned = "pin_default_project_view" diff --git a/worklenz-frontend/src/app/store.ts b/worklenz-frontend/src/app/store.ts new file mode 100644 index 00000000..6bf7adcf --- /dev/null +++ b/worklenz-frontend/src/app/store.ts @@ -0,0 +1,162 @@ +import { configureStore } from '@reduxjs/toolkit'; + +// Auth & User +import authReducer from '@features/auth/authSlice'; +import userReducer from '@features/user/userSlice'; + +// Home Page +import homePageReducer from '@features/home-page/home-page.slice'; + +// Account Setup +import accountSetupReducer from '@features/account-setup/account-setup.slice'; + +// Core UI +import themeReducer from '@features/theme/themeSlice'; +import localesReducer from '@features/i18n/localesSlice'; +import alertsReducer from '@/services/alerts/alertSlice'; + +// Projects +import projectReducer from '@features/project/project.slice'; +import projectsReducer from '@features/projects/projectsSlice'; +import projectMemberReducer from '@features/projects/singleProject/members/projectMembersSlice'; +import projectViewTaskListColumnsReducer from '@features/projects/singleProject/taskListColumns/taskColumnsSlice'; +import phaseReducer from '@/features/projects/singleProject/phase/phases.slice'; +import updatesReducer from '../features/projects/singleProject/updates/updatesSlice'; +import statusReducer from '@features/projects/status/StatusSlice'; +import deleteStatusReducer from '@features/projects/status/DeleteStatusSlice'; +import bulkActionReducer from '@features/projects/bulkActions/bulkActionSlice'; +import projectInsightsReducer from '@features/projects/insights/project-insights.slice'; +import taskListCustomColumnsReducer from '@features/projects/singleProject/task-list-custom-columns/task-list-custom-columns-slice'; +import boardReducer from '@features/board/board-slice'; +import projectDrawerReducer from '@features/project/project-drawer.slice'; + +// Project Lookups +import projectCategoriesReducer from '@features/projects/lookups/projectCategories/projectCategoriesSlice'; +import projectStatusesReducer from '@features/projects/lookups/projectStatuses/projectStatusesSlice'; +import projectHealthReducer from '@features/projects/lookups/projectHealth/projectHealthSlice'; + +// Tasks +import taskReducer from '@features/tasks/tasks.slice'; +import createCardReducer from '@/features/board/create-card.slice'; +import priorityReducer from '@features/taskAttributes/taskPrioritySlice'; +import taskLabelsReducer from '@features/taskAttributes/taskLabelSlice'; +import taskStatusReducer, { deleteStatus } from '@features/taskAttributes/taskStatusSlice'; +import taskDrawerReducer from '@features/task-drawer/task-drawer.slice'; + +// Settings & Management +import memberReducer from '@features/settings/member/memberSlice'; +import clientReducer from '@features/settings/client/clientSlice'; +import jobReducer from '@features/settings/job/jobSlice'; +import teamReducer from '@features/teams/teamSlice'; +import billingReducer from '@/features/admin-center/billing/billing.slice'; +import categoriesReducer from '@features/settings/categories/categoriesSlice'; +import labelReducer from '@features/settings/label/labelSlice'; + +// Admin Center +import adminCenterReducer from '@features/admin-center/admin-center.slice'; + +// Features +import dateReducer from '@features/date/dateSlice'; +import notificationReducer from '@/features/navbar/notificationSlice'; +import buttonReducer from '@features/actionSetup/buttonSlice'; +import scheduleReducer from '../features/schedule/scheduleSlice'; + +// Reports +import reportingReducer from '@features/reporting/reporting.slice'; +import timeLogReducer from '../features/timeReport/projects/timeLogSlice'; +import taskTemplateReducer from '../features/settings/taskTemplates/taskTemplateSlice'; +import projectReportsTableColumnsReducer from '../features/reporting/projectReports/project-reports-table-column-slice/project-reports-table-column-slice'; +import projectReportsReducer from '../features/reporting/projectReports/project-reports-slice'; +import membersReportsReducer from '../features/reporting/membersReports/membersReportsSlice'; +import timeReportsOverviewReducer from '@features/reporting/time-reports/time-reports-overview.slice'; + +import roadmapReducer from '../features/roadmap/roadmap-slice'; +import teamMembersReducer from '@features/team-members/team-members.slice'; +import groupByFilterDropdownReducer from '../features/group-by-filter-dropdown/group-by-filter-dropdown-slice'; +import homePageApiService from '@/api/home-page/home-page.api.service'; +import { projectsApi } from '@/api/projects/projects.v1.api.service'; + +export const store = configureStore({ + middleware: getDefaultMiddleware => + getDefaultMiddleware({ + serializableCheck: false, + }).concat(homePageApiService.middleware, projectsApi.middleware), + reducer: { + // Auth & User + auth: authReducer, + userReducer: userReducer, + + // Account Setup + accountSetupReducer: accountSetupReducer, + + // Home Page + homePageReducer: homePageReducer, + [homePageApiService.reducerPath]: homePageApiService.reducer, + [projectsApi.reducerPath]: projectsApi.reducer, + // Core UI + themeReducer: themeReducer, + localesReducer: localesReducer, + alertsReducer: alertsReducer, + + // Projects + projectReducer: projectReducer, + projectsReducer: projectsReducer, + projectMemberReducer: projectMemberReducer, + teamMembersReducer: teamMembersReducer, + projectViewTaskListColumnsReducer: projectViewTaskListColumnsReducer, + phaseReducer: phaseReducer, + updatesReducer: updatesReducer, + statusReducer: statusReducer, + deleteStatusReducer: deleteStatusReducer, + bulkActionReducer: bulkActionReducer, + projectInsightsReducer: projectInsightsReducer, + taskListCustomColumnsReducer: taskListCustomColumnsReducer, + boardReducer: boardReducer, + projectDrawerReducer: projectDrawerReducer, + + // Project Lookups + projectCategoriesReducer: projectCategoriesReducer, + projectStatusesReducer: projectStatusesReducer, + projectHealthReducer: projectHealthReducer, + + // Tasks + taskReducer: taskReducer, + createCardReducer: createCardReducer, + priorityReducer: priorityReducer, + taskLabelsReducer: taskLabelsReducer, + taskStatusReducer: taskStatusReducer, + taskDrawerReducer: taskDrawerReducer, + + // Settings & Management + memberReducer: memberReducer, + clientReducer: clientReducer, + jobReducer: jobReducer, + teamReducer: teamReducer, + billingReducer: billingReducer, + categoriesReducer: categoriesReducer, + labelReducer: labelReducer, + + // Admin Center + adminCenterReducer: adminCenterReducer, + + // Features + dateReducer: dateReducer, + notificationReducer: notificationReducer, + button: buttonReducer, + scheduleReducer: scheduleReducer, + + // Reports + reportingReducer: reportingReducer, + timeLogReducer: timeLogReducer, + taskTemplateReducer: taskTemplateReducer, + projectReportsTableColumnsReducer: projectReportsTableColumnsReducer, + projectReportsReducer: projectReportsReducer, + membersReportsReducer: membersReportsReducer, + roadmapReducer: roadmapReducer, + groupByFilterDropdownReducer: groupByFilterDropdownReducer, + timeReportsOverviewReducer: timeReportsOverviewReducer, + }, +}); + +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; diff --git a/worklenz-frontend/src/assets/.gitkeep b/worklenz-frontend/src/assets/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/worklenz-frontend/src/assets/css/prebuilt-editor.css b/worklenz-frontend/src/assets/css/prebuilt-editor.css deleted file mode 100644 index 3433df71..00000000 --- a/worklenz-frontend/src/assets/css/prebuilt-editor.css +++ /dev/null @@ -1,16 +0,0 @@ -[class^=ant-]::-ms-clear,[class*=ant-]::-ms-clear,[class^=ant-] input::-ms-clear,[class*=ant-] input::-ms-clear,[class^=ant-] input::-ms-reveal,[class*=ant-] input::-ms-reveal{display:none}html,body{width:100%;height:100%}input::-ms-clear,input::-ms-reveal{display:none}*,*:before,*:after{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{margin:0;color:#000000d9;font-size:14px;font-family:-apple-system,BlinkMacSystemFont,Inter,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-variant:tabular-nums;line-height:1.5715;background-color:#fff;font-feature-settings:"tnum"}[tabindex="-1"]:focus{outline:none!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5em;color:#000000d9;font-weight:500}p{margin-top:0;margin-bottom:1em}abbr[title],abbr[data-original-title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;border-bottom:0;cursor:help}address{margin-bottom:1em;font-style:normal;line-height:inherit}input[type=text],input[type=password],input[type=number],textarea{-webkit-appearance:none}ol,ul,dl{margin-top:0;margin-bottom:1em}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:500}dd{margin-bottom:.5em;margin-left:0}blockquote{margin:0 0 1em}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#1890ff;text-decoration:none;background-color:transparent;outline:none;cursor:pointer;transition:color .3s;-webkit-text-decoration-skip:objects}a:hover{color:#40a9ff}a:active{color:#096dd9}a:active,a:hover{text-decoration:none;outline:0}a:focus{text-decoration:none;outline:0}a[disabled]{color:#00000040;cursor:not-allowed}pre,code,kbd,samp{font-size:1em;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace}pre{margin-top:0;margin-bottom:1em;overflow:auto}figure{margin:0 0 1em}img{vertical-align:middle;border-style:none}a,area,button,[role=button],input:not([type="range"]),label,select,summary,textarea{touch-action:manipulation}table{border-collapse:collapse}caption{padding-top:.75em;padding-bottom:.3em;color:#00000073;text-align:left;caption-side:bottom}input,button,select,optgroup,textarea{margin:0;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}button,html [type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{padding:0;border-style:none}input[type=radio],input[type=checkbox]{box-sizing:border-box;padding:0}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;margin:0;padding:0;border:0}legend{display:block;width:100%;max-width:100%;margin-bottom:.5em;padding:0;color:inherit;font-size:1.5em;line-height:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item}template{display:none}[hidden]{display:none!important}mark{padding:.2em;background-color:#feffe6}::selection{color:#fff;background:#1890ff}.clearfix:before{display:table;content:""}.clearfix:after{display:table;clear:both;content:""}.anticon{display:inline-block;color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizelegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.anticon>*{line-height:1}.anticon svg{display:inline-block}.anticon:before{display:none}.anticon .anticon-icon{display:block}.anticon>.anticon{line-height:0;vertical-align:0}.anticon[tabindex]{cursor:pointer}.anticon-spin:before{display:inline-block;animation:loadingCircle 1s infinite linear}.anticon-spin{display:inline-block;animation:loadingCircle 1s infinite linear}.ant-fade-enter,.ant-fade-appear,.ant-fade-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-fade-enter.ant-fade-enter-active,.ant-fade-appear.ant-fade-appear-active{animation-name:antFadeIn;animation-play-state:running}.ant-fade-leave.ant-fade-leave-active{animation-name:antFadeOut;animation-play-state:running;pointer-events:none}.ant-fade-enter,.ant-fade-appear{opacity:0;animation-timing-function:linear}.ant-fade-leave{animation-timing-function:linear}@keyframes antFadeIn{0%{opacity:0}to{opacity:1}}@keyframes antFadeOut{0%{opacity:1}to{opacity:0}}.ant-move-up-enter,.ant-move-up-appear,.ant-move-up-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-move-up-enter.ant-move-up-enter-active,.ant-move-up-appear.ant-move-up-appear-active{animation-name:antMoveUpIn;animation-play-state:running}.ant-move-up-leave.ant-move-up-leave-active{animation-name:antMoveUpOut;animation-play-state:running;pointer-events:none}.ant-move-up-enter,.ant-move-up-appear{opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-move-up-leave{animation-timing-function:cubic-bezier(.6,.04,.98,.34)}.ant-move-down-enter,.ant-move-down-appear,.ant-move-down-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-move-down-enter.ant-move-down-enter-active,.ant-move-down-appear.ant-move-down-appear-active{animation-name:antMoveDownIn;animation-play-state:running}.ant-move-down-leave.ant-move-down-leave-active{animation-name:antMoveDownOut;animation-play-state:running;pointer-events:none}.ant-move-down-enter,.ant-move-down-appear{opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-move-down-leave{animation-timing-function:cubic-bezier(.6,.04,.98,.34)}.ant-move-left-enter,.ant-move-left-appear,.ant-move-left-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-move-left-enter.ant-move-left-enter-active,.ant-move-left-appear.ant-move-left-appear-active{animation-name:antMoveLeftIn;animation-play-state:running}.ant-move-left-leave.ant-move-left-leave-active{animation-name:antMoveLeftOut;animation-play-state:running;pointer-events:none}.ant-move-left-enter,.ant-move-left-appear{opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-move-left-leave{animation-timing-function:cubic-bezier(.6,.04,.98,.34)}.ant-move-right-enter,.ant-move-right-appear,.ant-move-right-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-move-right-enter.ant-move-right-enter-active,.ant-move-right-appear.ant-move-right-appear-active{animation-name:antMoveRightIn;animation-play-state:running}.ant-move-right-leave.ant-move-right-leave-active{animation-name:antMoveRightOut;animation-play-state:running;pointer-events:none}.ant-move-right-enter,.ant-move-right-appear{opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-move-right-leave{animation-timing-function:cubic-bezier(.6,.04,.98,.34)}@keyframes antMoveDownIn{0%{transform:translateY(100%);transform-origin:0 0;opacity:0}to{transform:translateY(0);transform-origin:0 0;opacity:1}}@keyframes antMoveDownOut{0%{transform:translateY(0);transform-origin:0 0;opacity:1}to{transform:translateY(100%);transform-origin:0 0;opacity:0}}@keyframes antMoveLeftIn{0%{transform:translate(-100%);transform-origin:0 0;opacity:0}to{transform:translate(0);transform-origin:0 0;opacity:1}}@keyframes antMoveLeftOut{0%{transform:translate(0);transform-origin:0 0;opacity:1}to{transform:translate(-100%);transform-origin:0 0;opacity:0}}@keyframes antMoveRightIn{0%{transform:translate(100%);transform-origin:0 0;opacity:0}to{transform:translate(0);transform-origin:0 0;opacity:1}}@keyframes antMoveRightOut{0%{transform:translate(0);transform-origin:0 0;opacity:1}to{transform:translate(100%);transform-origin:0 0;opacity:0}}@keyframes antMoveUpIn{0%{transform:translateY(-100%);transform-origin:0 0;opacity:0}to{transform:translateY(0);transform-origin:0 0;opacity:1}}@keyframes antMoveUpOut{0%{transform:translateY(0);transform-origin:0 0;opacity:1}to{transform:translateY(-100%);transform-origin:0 0;opacity:0}}@keyframes loadingCircle{to{transform:rotate(360deg)}}[ant-click-animating=true],[ant-click-animating-without-extra-node=true]{position:relative}html{--antd-wave-shadow-color: #1890ff;--scroll-bar: 0}[ant-click-animating-without-extra-node=true]:after,.ant-click-animating-node{position:absolute;inset:0;display:block;border-radius:inherit;box-shadow:0 0 #1890ff;box-shadow:0 0 0 0 var(--antd-wave-shadow-color);opacity:.2;animation:fadeEffect 2s cubic-bezier(.08,.82,.17,1),waveEffect .4s cubic-bezier(.08,.82,.17,1);animation-fill-mode:forwards;content:"";pointer-events:none}@keyframes waveEffect{to{box-shadow:0 0 #1890ff;box-shadow:0 0 0 6px var(--antd-wave-shadow-color)}}@keyframes fadeEffect{to{opacity:0}}.ant-slide-up-enter,.ant-slide-up-appear,.ant-slide-up-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-slide-up-enter.ant-slide-up-enter-active,.ant-slide-up-appear.ant-slide-up-appear-active{animation-name:antSlideUpIn;animation-play-state:running}.ant-slide-up-leave.ant-slide-up-leave-active{animation-name:antSlideUpOut;animation-play-state:running;pointer-events:none}.ant-slide-up-enter,.ant-slide-up-appear{opacity:0;animation-timing-function:cubic-bezier(.23,1,.32,1)}.ant-slide-up-leave{animation-timing-function:cubic-bezier(.755,.05,.855,.06)}.ant-slide-down-enter,.ant-slide-down-appear,.ant-slide-down-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-slide-down-enter.ant-slide-down-enter-active,.ant-slide-down-appear.ant-slide-down-appear-active{animation-name:antSlideDownIn;animation-play-state:running}.ant-slide-down-leave.ant-slide-down-leave-active{animation-name:antSlideDownOut;animation-play-state:running;pointer-events:none}.ant-slide-down-enter,.ant-slide-down-appear{opacity:0;animation-timing-function:cubic-bezier(.23,1,.32,1)}.ant-slide-down-leave{animation-timing-function:cubic-bezier(.755,.05,.855,.06)}.ant-slide-left-enter,.ant-slide-left-appear,.ant-slide-left-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-slide-left-enter.ant-slide-left-enter-active,.ant-slide-left-appear.ant-slide-left-appear-active{animation-name:antSlideLeftIn;animation-play-state:running}.ant-slide-left-leave.ant-slide-left-leave-active{animation-name:antSlideLeftOut;animation-play-state:running;pointer-events:none}.ant-slide-left-enter,.ant-slide-left-appear{opacity:0;animation-timing-function:cubic-bezier(.23,1,.32,1)}.ant-slide-left-leave{animation-timing-function:cubic-bezier(.755,.05,.855,.06)}.ant-slide-right-enter,.ant-slide-right-appear,.ant-slide-right-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-slide-right-enter.ant-slide-right-enter-active,.ant-slide-right-appear.ant-slide-right-appear-active{animation-name:antSlideRightIn;animation-play-state:running}.ant-slide-right-leave.ant-slide-right-leave-active{animation-name:antSlideRightOut;animation-play-state:running;pointer-events:none}.ant-slide-right-enter,.ant-slide-right-appear{opacity:0;animation-timing-function:cubic-bezier(.23,1,.32,1)}.ant-slide-right-leave{animation-timing-function:cubic-bezier(.755,.05,.855,.06)}@keyframes antSlideUpIn{0%{transform:scaleY(.8);transform-origin:0% 0%;opacity:0}to{transform:scaleY(1);transform-origin:0% 0%;opacity:1}}@keyframes antSlideUpOut{0%{transform:scaleY(1);transform-origin:0% 0%;opacity:1}to{transform:scaleY(.8);transform-origin:0% 0%;opacity:0}}@keyframes antSlideDownIn{0%{transform:scaleY(.8);transform-origin:100% 100%;opacity:0}to{transform:scaleY(1);transform-origin:100% 100%;opacity:1}}@keyframes antSlideDownOut{0%{transform:scaleY(1);transform-origin:100% 100%;opacity:1}to{transform:scaleY(.8);transform-origin:100% 100%;opacity:0}}@keyframes antSlideLeftIn{0%{transform:scaleX(.8);transform-origin:0% 0%;opacity:0}to{transform:scaleX(1);transform-origin:0% 0%;opacity:1}}@keyframes antSlideLeftOut{0%{transform:scaleX(1);transform-origin:0% 0%;opacity:1}to{transform:scaleX(.8);transform-origin:0% 0%;opacity:0}}@keyframes antSlideRightIn{0%{transform:scaleX(.8);transform-origin:100% 0%;opacity:0}to{transform:scaleX(1);transform-origin:100% 0%;opacity:1}}@keyframes antSlideRightOut{0%{transform:scaleX(1);transform-origin:100% 0%;opacity:1}to{transform:scaleX(.8);transform-origin:100% 0%;opacity:0}}.ant-zoom-enter,.ant-zoom-appear,.ant-zoom-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-zoom-enter.ant-zoom-enter-active,.ant-zoom-appear.ant-zoom-appear-active{animation-name:antZoomIn;animation-play-state:running}.ant-zoom-leave.ant-zoom-leave-active{animation-name:antZoomOut;animation-play-state:running;pointer-events:none}.ant-zoom-enter,.ant-zoom-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-zoom-enter-prepare,.ant-zoom-appear-prepare{transform:none}.ant-zoom-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.ant-zoom-big-enter,.ant-zoom-big-appear,.ant-zoom-big-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-zoom-big-enter.ant-zoom-big-enter-active,.ant-zoom-big-appear.ant-zoom-big-appear-active{animation-name:antZoomBigIn;animation-play-state:running}.ant-zoom-big-leave.ant-zoom-big-leave-active{animation-name:antZoomBigOut;animation-play-state:running;pointer-events:none}.ant-zoom-big-enter,.ant-zoom-big-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-zoom-big-enter-prepare,.ant-zoom-big-appear-prepare{transform:none}.ant-zoom-big-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.ant-zoom-big-fast-enter,.ant-zoom-big-fast-appear,.ant-zoom-big-fast-leave{animation-duration:.1s;animation-fill-mode:both;animation-play-state:paused}.ant-zoom-big-fast-enter.ant-zoom-big-fast-enter-active,.ant-zoom-big-fast-appear.ant-zoom-big-fast-appear-active{animation-name:antZoomBigIn;animation-play-state:running}.ant-zoom-big-fast-leave.ant-zoom-big-fast-leave-active{animation-name:antZoomBigOut;animation-play-state:running;pointer-events:none}.ant-zoom-big-fast-enter,.ant-zoom-big-fast-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-zoom-big-fast-enter-prepare,.ant-zoom-big-fast-appear-prepare{transform:none}.ant-zoom-big-fast-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.ant-zoom-up-enter,.ant-zoom-up-appear,.ant-zoom-up-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-zoom-up-enter.ant-zoom-up-enter-active,.ant-zoom-up-appear.ant-zoom-up-appear-active{animation-name:antZoomUpIn;animation-play-state:running}.ant-zoom-up-leave.ant-zoom-up-leave-active{animation-name:antZoomUpOut;animation-play-state:running;pointer-events:none}.ant-zoom-up-enter,.ant-zoom-up-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-zoom-up-enter-prepare,.ant-zoom-up-appear-prepare{transform:none}.ant-zoom-up-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.ant-zoom-down-enter,.ant-zoom-down-appear,.ant-zoom-down-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-zoom-down-enter.ant-zoom-down-enter-active,.ant-zoom-down-appear.ant-zoom-down-appear-active{animation-name:antZoomDownIn;animation-play-state:running}.ant-zoom-down-leave.ant-zoom-down-leave-active{animation-name:antZoomDownOut;animation-play-state:running;pointer-events:none}.ant-zoom-down-enter,.ant-zoom-down-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-zoom-down-enter-prepare,.ant-zoom-down-appear-prepare{transform:none}.ant-zoom-down-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.ant-zoom-left-enter,.ant-zoom-left-appear,.ant-zoom-left-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-zoom-left-enter.ant-zoom-left-enter-active,.ant-zoom-left-appear.ant-zoom-left-appear-active{animation-name:antZoomLeftIn;animation-play-state:running}.ant-zoom-left-leave.ant-zoom-left-leave-active{animation-name:antZoomLeftOut;animation-play-state:running;pointer-events:none}.ant-zoom-left-enter,.ant-zoom-left-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-zoom-left-enter-prepare,.ant-zoom-left-appear-prepare{transform:none}.ant-zoom-left-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.ant-zoom-right-enter,.ant-zoom-right-appear,.ant-zoom-right-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-zoom-right-enter.ant-zoom-right-enter-active,.ant-zoom-right-appear.ant-zoom-right-appear-active{animation-name:antZoomRightIn;animation-play-state:running}.ant-zoom-right-leave.ant-zoom-right-leave-active{animation-name:antZoomRightOut;animation-play-state:running;pointer-events:none}.ant-zoom-right-enter,.ant-zoom-right-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-zoom-right-enter-prepare,.ant-zoom-right-appear-prepare{transform:none}.ant-zoom-right-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}@keyframes antZoomIn{0%{transform:scale(.2);opacity:0}to{transform:scale(1);opacity:1}}@keyframes antZoomOut{0%{transform:scale(1)}to{transform:scale(.2);opacity:0}}@keyframes antZoomBigIn{0%{transform:scale(.8);opacity:0}to{transform:scale(1);opacity:1}}@keyframes antZoomBigOut{0%{transform:scale(1)}to{transform:scale(.8);opacity:0}}@keyframes antZoomUpIn{0%{transform:scale(.8);transform-origin:50% 0%;opacity:0}to{transform:scale(1);transform-origin:50% 0%}}@keyframes antZoomUpOut{0%{transform:scale(1);transform-origin:50% 0%}to{transform:scale(.8);transform-origin:50% 0%;opacity:0}}@keyframes antZoomLeftIn{0%{transform:scale(.8);transform-origin:0% 50%;opacity:0}to{transform:scale(1);transform-origin:0% 50%}}@keyframes antZoomLeftOut{0%{transform:scale(1);transform-origin:0% 50%}to{transform:scale(.8);transform-origin:0% 50%;opacity:0}}@keyframes antZoomRightIn{0%{transform:scale(.8);transform-origin:100% 50%;opacity:0}to{transform:scale(1);transform-origin:100% 50%}}@keyframes antZoomRightOut{0%{transform:scale(1);transform-origin:100% 50%}to{transform:scale(.8);transform-origin:100% 50%;opacity:0}}@keyframes antZoomDownIn{0%{transform:scale(.8);transform-origin:50% 100%;opacity:0}to{transform:scale(1);transform-origin:50% 100%}}@keyframes antZoomDownOut{0%{transform:scale(1);transform-origin:50% 100%}to{transform:scale(.8);transform-origin:50% 100%;opacity:0}}.ant-motion-collapse-legacy{overflow:hidden}.ant-motion-collapse-legacy-active{transition:height .2s cubic-bezier(.645,.045,.355,1),opacity .2s cubic-bezier(.645,.045,.355,1)!important}.ant-motion-collapse{overflow:hidden;transition:height .2s cubic-bezier(.645,.045,.355,1),opacity .2s cubic-bezier(.645,.045,.355,1)!important}.cdk-overlay-container{pointer-events:none;top:0;left:0;height:100%;width:100%;position:fixed;z-index:1000}.cdk-visually-hidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;outline:0;-webkit-appearance:none;-moz-appearance:none}.cdk-overlay-backdrop{inset:0;-webkit-tap-highlight-color:transparent;transition:opacity .4s cubic-bezier(.25,.8,.25,1);opacity:0;position:absolute;pointer-events:auto;z-index:1000}.cdk-overlay-backdrop.ant-modal-mask{opacity:1}.cdk-overlay-pane{position:absolute;pointer-events:auto;z-index:1000}.cdk-overlay-connected-position-bounding-box{position:absolute;z-index:1000;display:flex;flex-direction:column;min-width:1px;min-height:1px}.cdk-global-scrollblock{position:fixed;width:100%;overflow-y:scroll}.cdk-global-scrollblock body{overflow-x:visible}.nz-overlay-transparent-backdrop,.nz-overlay-transparent-backdrop.cdk-overlay-backdrop-showing{opacity:0}.nz-animate-disabled.ant-scroll-number-only,.nz-animate-disabled.ant-drawer.ant-drawer-open .ant-drawer-mask{animation:none;transition:none}.nz-animate-disabled.ant-drawer>*{transition:none}.nz-animate-disabled .ant-modal-mask,.nz-animate-disabled .ant-modal,.nz-animate-disabled .ant-modal-mask.zoom-enter,.nz-animate-disabled .ant-modal.zoom-enter,.nz-animate-disabled .ant-modal-mask.zoom-leave,.nz-animate-disabled .ant-modal.zoom-leave,.nz-animate-disabled .ant-modal-mask.zoom-enter-active,.nz-animate-disabled .ant-modal.zoom-enter-active,.nz-animate-disabled .ant-modal-mask.zoom-leave-active,.nz-animate-disabled .ant-modal.zoom-leave-active{animation:none;transition:none}.nz-animate-disabled.ant-menu,.nz-animate-disabled.ant-menu .ant-menu-item,.nz-animate-disabled.ant-menu .ant-menu-submenu-title,.nz-animate-disabled.ant-menu .ant-menu-item .anticon,.nz-animate-disabled.ant-menu .ant-menu-submenu-title .anticon{transition:none}.nz-animate-disabled.ant-menu .ant-menu-item .anticon+span,.nz-animate-disabled.ant-menu .ant-menu-submenu-title .anticon+span{transition:none}.nz-animate-disabled.ant-tabs .ant-tabs-top-content.ant-tabs-content-animated,.nz-animate-disabled.ant-tabs .ant-tabs-bottom-content.ant-tabs-content-animated,.nz-animate-disabled.ant-tabs .ant-tabs-top-content>.ant-tabs-tabpane,.nz-animate-disabled.ant-tabs .ant-tabs-bottom-content>.ant-tabs-tabpane,.nz-animate-disabled.ant-tabs.ant-tabs-left .ant-tabs-ink-bar-animated,.nz-animate-disabled.ant-tabs.ant-tabs-right .ant-tabs-ink-bar-animated,.nz-animate-disabled.ant-tabs.ant-tabs-top .ant-tabs-ink-bar-animated,.nz-animate-disabled.ant-tabs.ant-tabs-bottom .ant-tabs-ink-bar-animated{transition:none}.nz-animate-disabled.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow svg{transition:none}.ant-affix{position:fixed;z-index:10}nz-affix{display:block}.ant-alert{box-sizing:border-box;margin:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:flex;align-items:center;padding:8px 15px;word-wrap:break-word;border-radius:4px}.ant-alert-content{flex:1;min-width:0}.ant-alert-icon{margin-right:8px}.ant-alert-description{display:none;font-size:14px;line-height:22px}.ant-alert-success{background-color:#f6ffed;border:1px solid #b7eb8f}.ant-alert-success .ant-alert-icon{color:#52c41a}.ant-alert-info{background-color:#e6f7ff;border:1px solid #91d5ff}.ant-alert-info .ant-alert-icon{color:#1890ff}.ant-alert-warning{background-color:#fffbe6;border:1px solid #ffe58f}.ant-alert-warning .ant-alert-icon{color:#faad14}.ant-alert-error{background-color:#fff2f0;border:1px solid #ffccc7}.ant-alert-error .ant-alert-icon{color:#ff4d4f}.ant-alert-error .ant-alert-description>pre{margin:0;padding:0}.ant-alert-action{margin-left:8px}.ant-alert-close-icon{margin-left:8px;padding:0;overflow:hidden;font-size:12px;line-height:12px;background-color:transparent;border:none;outline:none;cursor:pointer}.ant-alert-close-icon .anticon-close{color:#00000073;transition:color .3s}.ant-alert-close-icon .anticon-close:hover{color:#000000bf}.ant-alert-close-text{color:#00000073;transition:color .3s}.ant-alert-close-text:hover{color:#000000bf}.ant-alert-with-description{align-items:flex-start;padding:15px 15px 15px 24px}.ant-alert-with-description.ant-alert-no-icon{padding:15px}.ant-alert-with-description .ant-alert-icon{margin-right:15px;font-size:24px}.ant-alert-with-description .ant-alert-message{display:block;margin-bottom:4px;color:#000000d9;font-size:16px}.ant-alert-message{color:#000000d9}.ant-alert-with-description .ant-alert-description{display:block}.ant-alert.ant-alert-motion-leave{overflow:hidden;opacity:1;transition:max-height .3s cubic-bezier(.78,.14,.15,.86),opacity .3s cubic-bezier(.78,.14,.15,.86),padding-top .3s cubic-bezier(.78,.14,.15,.86),padding-bottom .3s cubic-bezier(.78,.14,.15,.86),margin-bottom .3s cubic-bezier(.78,.14,.15,.86)}.ant-alert.ant-alert-motion-leave-active{max-height:0;margin-bottom:0!important;padding-top:0;padding-bottom:0;opacity:0}.ant-alert-banner{margin-bottom:0;border:0;border-radius:0}.ant-alert.ant-alert-rtl{direction:rtl}.ant-alert-rtl .ant-alert-icon{margin-right:auto;margin-left:8px}.ant-alert-rtl .ant-alert-action,.ant-alert-rtl .ant-alert-close-icon{margin-right:8px;margin-left:auto}.ant-alert-rtl.ant-alert-with-description{padding-right:24px;padding-left:15px}.ant-alert-rtl.ant-alert-with-description .ant-alert-icon{margin-right:auto;margin-left:15px}nz-alert{display:block}.ant-alert-icon{line-height:1}.ant-anchor{box-sizing:border-box;margin:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;padding:0 0 0 2px}.ant-anchor-wrapper{margin-left:-4px;padding-left:4px;overflow:auto;background-color:transparent}.ant-anchor-ink{position:absolute;top:0;left:0;height:100%}.ant-anchor-ink:before{position:relative;display:block;width:2px;height:100%;margin:0 auto;background-color:#f0f0f0;content:" "}.ant-anchor-ink-ball{position:absolute;left:50%;display:none;width:8px;height:8px;background-color:#fff;border:2px solid #1890ff;border-radius:8px;transform:translate(-50%);transition:top .3s ease-in-out}.ant-anchor-ink-ball.visible{display:inline-block}.ant-anchor-fixed .ant-anchor-ink .ant-anchor-ink-ball{display:none}.ant-anchor-link{padding:4px 0 4px 16px}.ant-anchor-link-title{position:relative;display:block;margin-bottom:3px;overflow:hidden;color:#000000d9;white-space:nowrap;text-overflow:ellipsis;transition:all .3s}.ant-anchor-link-title:only-child{margin-bottom:0}.ant-anchor-link-active>.ant-anchor-link-title{color:#1890ff}.ant-anchor-link .ant-anchor-link{padding-top:2px;padding-bottom:2px}.ant-anchor-rtl{direction:rtl}.ant-anchor-rtl.ant-anchor-wrapper{margin-right:-4px;margin-left:0;padding-right:4px;padding-left:0}.ant-anchor-rtl .ant-anchor-ink{right:0;left:auto}.ant-anchor-rtl .ant-anchor-ink-ball{right:50%;left:0;transform:translate(50%)}.ant-anchor-rtl .ant-anchor-link{padding:4px 16px 4px 0}nz-link{display:block}.ant-avatar{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;overflow:hidden;color:#fff;white-space:nowrap;text-align:center;vertical-align:middle;background:#ccc;width:32px;height:32px;line-height:32px;border-radius:50%}.ant-avatar-image{background:transparent}.ant-avatar .ant-image-img{display:block}.ant-avatar-string{position:absolute;left:50%;transform-origin:0 center}.ant-avatar.ant-avatar-icon{font-size:18px}.ant-avatar.ant-avatar-icon>.anticon{margin:0}.ant-avatar-lg{width:40px;height:40px;line-height:40px;border-radius:50%}.ant-avatar-lg-string{position:absolute;left:50%;transform-origin:0 center}.ant-avatar-lg.ant-avatar-icon{font-size:24px}.ant-avatar-lg.ant-avatar-icon>.anticon{margin:0}.ant-avatar-sm{width:24px;height:24px;line-height:24px;border-radius:50%}.ant-avatar-sm-string{position:absolute;left:50%;transform-origin:0 center}.ant-avatar-sm.ant-avatar-icon{font-size:14px}.ant-avatar-sm.ant-avatar-icon>.anticon{margin:0}.ant-avatar-square{border-radius:4px}.ant-avatar>img{display:block;width:100%;height:100%;object-fit:cover}.ant-avatar-group{display:inline-flex}.ant-avatar-group .ant-avatar{border:1px solid #fff}.ant-avatar-group .ant-avatar:not(:first-child){margin-left:-8px}.ant-avatar-group-popover .ant-avatar+.ant-avatar{margin-left:3px}.ant-avatar-group-rtl .ant-avatar:not(:first-child){margin-right:-8px;margin-left:0}.ant-avatar-group-popover.ant-popover-rtl .ant-avatar+.ant-avatar{margin-right:3px;margin-left:0}.ant-back-top{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:fixed;right:100px;bottom:50px;z-index:10;width:40px;height:40px;cursor:pointer}.ant-back-top:empty{display:none}.ant-back-top-rtl{right:auto;left:100px;direction:rtl}.ant-back-top-content{width:40px;height:40px;overflow:hidden;color:#fff;text-align:center;background-color:#00000073;border-radius:20px;transition:all .3s}.ant-back-top-content:hover{background-color:#000000d9;transition:all .3s}.ant-back-top-icon{font-size:24px;line-height:40px}@media screen and (max-width: 768px){.ant-back-top{right:60px}.ant-back-top-rtl{right:auto;left:60px}}@media screen and (max-width: 480px){.ant-back-top{right:20px}.ant-back-top-rtl{right:auto;left:20px}}.ant-badge{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;line-height:1}.ant-badge-count{z-index:auto;min-width:20px;height:20px;padding:0 6px;color:#fff;font-weight:400;font-size:12px;line-height:20px;white-space:nowrap;text-align:center;background:#ff4d4f;border-radius:10px;box-shadow:0 0 0 1px #fff}.ant-badge-count a,.ant-badge-count a:hover{color:#fff}.ant-badge-count-sm{min-width:14px;height:14px;padding:0;font-size:12px;line-height:14px;border-radius:7px}.ant-badge-multiple-words{padding:0 8px}.ant-badge-dot{z-index:auto;width:6px;min-width:6px;height:6px;background:#ff4d4f;border-radius:100%;box-shadow:0 0 0 1px #fff}.ant-badge-dot.ant-scroll-number{transition:background 1.5s}.ant-badge-count,.ant-badge-dot,.ant-badge .ant-scroll-number-custom-component{position:absolute;top:0;right:0;transform:translate(50%,-50%);transform-origin:100% 0%}.ant-badge-count.anticon-spin,.ant-badge-dot.anticon-spin,.ant-badge .ant-scroll-number-custom-component.anticon-spin{animation:antBadgeLoadingCircle 1s infinite linear}.ant-badge-status{line-height:inherit;vertical-align:baseline}.ant-badge-status-dot{position:relative;top:-1px;display:inline-block;width:6px;height:6px;vertical-align:middle;border-radius:50%}.ant-badge-status-success{background-color:#52c41a}.ant-badge-status-processing{position:relative;background-color:#1890ff}.ant-badge-status-processing:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #1890ff;border-radius:50%;animation:antStatusProcessing 1.2s infinite ease-in-out;content:""}.ant-badge-status-default{background-color:#d9d9d9}.ant-badge-status-error{background-color:#ff4d4f}.ant-badge-status-warning{background-color:#faad14}.ant-badge-status-pink,.ant-badge-status-magenta{background:#eb2f96}.ant-badge-status-red{background:#f5222d}.ant-badge-status-volcano{background:#fa541c}.ant-badge-status-orange{background:#fa8c16}.ant-badge-status-yellow{background:#fadb14}.ant-badge-status-gold{background:#faad14}.ant-badge-status-cyan{background:#13c2c2}.ant-badge-status-lime{background:#a0d911}.ant-badge-status-green{background:#52c41a}.ant-badge-status-blue{background:#1890ff}.ant-badge-status-geekblue{background:#2f54eb}.ant-badge-status-purple{background:#722ed1}.ant-badge-status-text{margin-left:8px;color:#000000d9;font-size:14px}.ant-badge-zoom-appear,.ant-badge-zoom-enter{animation:antZoomBadgeIn .3s cubic-bezier(.12,.4,.29,1.46);animation-fill-mode:both}.ant-badge-zoom-leave{animation:antZoomBadgeOut .3s cubic-bezier(.71,-.46,.88,.6);animation-fill-mode:both}.ant-badge-not-a-wrapper .ant-badge-zoom-appear,.ant-badge-not-a-wrapper .ant-badge-zoom-enter{animation:antNoWrapperZoomBadgeIn .3s cubic-bezier(.12,.4,.29,1.46)}.ant-badge-not-a-wrapper .ant-badge-zoom-leave{animation:antNoWrapperZoomBadgeOut .3s cubic-bezier(.71,-.46,.88,.6)}.ant-badge-not-a-wrapper:not(.ant-badge-status){vertical-align:middle}.ant-badge-not-a-wrapper .ant-scroll-number-custom-component,.ant-badge-not-a-wrapper .ant-badge-count{transform:none}.ant-badge-not-a-wrapper .ant-scroll-number-custom-component,.ant-badge-not-a-wrapper .ant-scroll-number{position:relative;top:auto;display:block;transform-origin:50% 50%}@keyframes antStatusProcessing{0%{transform:scale(.8);opacity:.5}to{transform:scale(2.4);opacity:0}}.ant-scroll-number{overflow:hidden;direction:ltr}.ant-scroll-number-only{position:relative;display:inline-block;height:20px;transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-transform-style:preserve-3d;-webkit-backface-visibility:hidden}.ant-scroll-number-only>p.ant-scroll-number-only-unit{height:20px;margin:0;-webkit-transform-style:preserve-3d;-webkit-backface-visibility:hidden}.ant-scroll-number-symbol{vertical-align:top}@keyframes antZoomBadgeIn{0%{transform:scale(0) translate(50%,-50%);opacity:0}to{transform:scale(1) translate(50%,-50%)}}@keyframes antZoomBadgeOut{0%{transform:scale(1) translate(50%,-50%)}to{transform:scale(0) translate(50%,-50%);opacity:0}}@keyframes antNoWrapperZoomBadgeIn{0%{transform:scale(0);opacity:0}to{transform:scale(1)}}@keyframes antNoWrapperZoomBadgeOut{0%{transform:scale(1)}to{transform:scale(0);opacity:0}}@keyframes antBadgeLoadingCircle{0%{transform-origin:50%}to{transform:translate(50%,-50%) rotate(360deg);transform-origin:50%}}.ant-ribbon-wrapper{position:relative}.ant-ribbon{box-sizing:border-box;margin:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;top:8px;height:22px;padding:0 8px;color:#fff;line-height:22px;white-space:nowrap;background-color:#1890ff;border-radius:4px}.ant-ribbon-text{color:#fff}.ant-ribbon-corner{position:absolute;top:100%;width:8px;height:8px;color:currentcolor;border:4px solid;transform:scaleY(.75);transform-origin:top}.ant-ribbon-corner:after{position:absolute;top:-4px;left:-4px;width:inherit;height:inherit;color:#00000040;border:inherit;content:""}.ant-ribbon-color-pink,.ant-ribbon-color-magenta{color:#eb2f96;background:#eb2f96}.ant-ribbon-color-red{color:#f5222d;background:#f5222d}.ant-ribbon-color-volcano{color:#fa541c;background:#fa541c}.ant-ribbon-color-orange{color:#fa8c16;background:#fa8c16}.ant-ribbon-color-yellow{color:#fadb14;background:#fadb14}.ant-ribbon-color-gold{color:#faad14;background:#faad14}.ant-ribbon-color-cyan{color:#13c2c2;background:#13c2c2}.ant-ribbon-color-lime{color:#a0d911;background:#a0d911}.ant-ribbon-color-green{color:#52c41a;background:#52c41a}.ant-ribbon-color-blue{color:#1890ff;background:#1890ff}.ant-ribbon-color-geekblue{color:#2f54eb;background:#2f54eb}.ant-ribbon-color-purple{color:#722ed1;background:#722ed1}.ant-ribbon.ant-ribbon-placement-end{right:-8px;border-bottom-right-radius:0}.ant-ribbon.ant-ribbon-placement-end .ant-ribbon-corner{right:0;border-color:currentcolor transparent transparent currentcolor}.ant-ribbon.ant-ribbon-placement-start{left:-8px;border-bottom-left-radius:0}.ant-ribbon.ant-ribbon-placement-start .ant-ribbon-corner{left:0;border-color:currentcolor currentcolor transparent transparent}.ant-badge-rtl{direction:rtl}.ant-badge-rtl.ant-badge:not(.ant-badge-not-a-wrapper) .ant-badge-count,.ant-badge-rtl.ant-badge:not(.ant-badge-not-a-wrapper) .ant-badge-dot,.ant-badge-rtl.ant-badge:not(.ant-badge-not-a-wrapper) .ant-scroll-number-custom-component{right:auto;left:0;direction:ltr;transform:translate(-50%,-50%);transform-origin:0% 0%}.ant-badge-rtl.ant-badge:not(.ant-badge-not-a-wrapper) .ant-scroll-number-custom-component{right:auto;left:0;transform:translate(-50%,-50%);transform-origin:0% 0%}.ant-badge-rtl .ant-badge-status-text{margin-right:8px;margin-left:0}.ant-badge:not(.ant-badge-not-a-wrapper).ant-badge-rtl .ant-badge-zoom-appear,.ant-badge:not(.ant-badge-not-a-wrapper).ant-badge-rtl .ant-badge-zoom-enter{animation-name:antZoomBadgeInRtl}.ant-badge:not(.ant-badge-not-a-wrapper).ant-badge-rtl .ant-badge-zoom-leave{animation-name:antZoomBadgeOutRtl}.ant-ribbon-rtl{direction:rtl}.ant-ribbon-rtl.ant-ribbon-placement-end{right:unset;left:-8px;border-bottom-right-radius:4px;border-bottom-left-radius:0}.ant-ribbon-rtl.ant-ribbon-placement-end .ant-ribbon-corner{right:unset;left:0;border-color:currentcolor currentcolor transparent transparent}.ant-ribbon-rtl.ant-ribbon-placement-end .ant-ribbon-corner:after{border-color:currentcolor currentcolor transparent transparent}.ant-ribbon-rtl.ant-ribbon-placement-start{right:-8px;left:unset;border-bottom-right-radius:0;border-bottom-left-radius:4px}.ant-ribbon-rtl.ant-ribbon-placement-start .ant-ribbon-corner{right:0;left:unset;border-color:currentcolor transparent transparent currentcolor}.ant-ribbon-rtl.ant-ribbon-placement-start .ant-ribbon-corner:after{border-color:currentcolor transparent transparent currentcolor}@keyframes antZoomBadgeInRtl{0%{transform:scale(0) translate(-50%,-50%);opacity:0}to{transform:scale(1) translate(-50%,-50%)}}@keyframes antZoomBadgeOutRtl{0%{transform:scale(1) translate(-50%,-50%)}to{transform:scale(0) translate(-50%,-50%);opacity:0}}.ant-badge .ant-scroll-number:only-child{position:relative;top:auto;display:block}.ant-badge .ant-badge-count:only-child{transform:none}nz-ribbon{display:block}.ant-breadcrumb{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";color:#00000073;font-size:14px}.ant-breadcrumb .anticon{font-size:14px}.ant-breadcrumb ol{display:flex;flex-wrap:wrap;margin:0;padding:0;list-style:none}.ant-breadcrumb a{color:#00000073;transition:color .3s}.ant-breadcrumb a:hover{color:#000000d9}.ant-breadcrumb li:last-child{color:#000000d9}.ant-breadcrumb li:last-child a{color:#000000d9}li:last-child .ant-breadcrumb-separator{display:none}.ant-breadcrumb-separator{margin:0 8px;color:#00000073}.ant-breadcrumb-link>.anticon+span,.ant-breadcrumb-link>.anticon+a{margin-left:4px}.ant-breadcrumb-overlay-link>.anticon{margin-left:4px}.ant-breadcrumb-rtl{direction:rtl}.ant-breadcrumb-rtl:before{display:table;content:""}.ant-breadcrumb-rtl:after{display:table;clear:both;content:""}.ant-breadcrumb-rtl>span{float:right}.ant-breadcrumb-rtl .ant-breadcrumb-link>.anticon+span,.ant-breadcrumb-rtl .ant-breadcrumb-link>.anticon+a{margin-right:4px;margin-left:0}.ant-breadcrumb-rtl .ant-breadcrumb-overlay-link>.anticon{margin-right:4px;margin-left:0}.ant-breadcrumb-link .anticon+span{margin-left:4px}.ant-breadcrumb>nz-breadcrumb-item:last-child{color:#000000d9}.ant-breadcrumb>nz-breadcrumb-item:last-child a{color:#000000d9}.ant-breadcrumb-rtl>nz-breadcrumb-item{float:right}nz-breadcrumb{display:block}nz-breadcrumb-item:last-child .ant-breadcrumb-separator{display:none}.ant-btn{line-height:1.5715;position:relative;display:inline-block;font-weight:400;white-space:nowrap;text-align:center;background-image:none;border:1px solid transparent;box-shadow:0 2px #00000004;cursor:pointer;transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;user-select:none;touch-action:manipulation;height:32px;padding:4px 15px;font-size:14px;border-radius:4px;color:#000000d9;border-color:#d9d9d9;background:#fff}.ant-btn>.anticon{line-height:1}.ant-btn,.ant-btn:active,.ant-btn:focus{outline:0}.ant-btn:not([disabled]):hover{text-decoration:none}.ant-btn:not([disabled]):active{outline:0;box-shadow:none}.ant-btn[disabled]{cursor:not-allowed}.ant-btn[disabled]>*{pointer-events:none}.ant-btn-lg{height:40px;padding:6.4px 15px;font-size:16px;border-radius:4px}.ant-btn-sm{height:24px;padding:0 7px;font-size:14px;border-radius:4px}.ant-btn>a:only-child{color:currentcolor}.ant-btn>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn:hover,.ant-btn:focus{color:#40a9ff;border-color:#40a9ff;background:#fff}.ant-btn:hover>a:only-child,.ant-btn:focus>a:only-child{color:currentcolor}.ant-btn:hover>a:only-child:after,.ant-btn:focus>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn:active{color:#096dd9;border-color:#096dd9;background:#fff}.ant-btn:active>a:only-child{color:currentcolor}.ant-btn:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn[disabled],.ant-btn[disabled]:hover,.ant-btn[disabled]:focus,.ant-btn[disabled]:active{color:#00000040;border-color:#d9d9d9;background:#f5f5f5;text-shadow:none;box-shadow:none}.ant-btn[disabled]>a:only-child,.ant-btn[disabled]:hover>a:only-child,.ant-btn[disabled]:focus>a:only-child,.ant-btn[disabled]:active>a:only-child{color:currentcolor}.ant-btn[disabled]>a:only-child:after,.ant-btn[disabled]:hover>a:only-child:after,.ant-btn[disabled]:focus>a:only-child:after,.ant-btn[disabled]:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn:hover,.ant-btn:focus,.ant-btn:active{text-decoration:none;background:#fff}.ant-btn>span{display:inline-block}.ant-btn-primary{color:#fff;border-color:#1890ff;background:#1890ff;text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px #0000000b}.ant-btn-primary>a:only-child{color:currentcolor}.ant-btn-primary>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-primary:hover,.ant-btn-primary:focus{color:#fff;border-color:#40a9ff;background:#40a9ff}.ant-btn-primary:hover>a:only-child,.ant-btn-primary:focus>a:only-child{color:currentcolor}.ant-btn-primary:hover>a:only-child:after,.ant-btn-primary:focus>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-primary:active{color:#fff;border-color:#096dd9;background:#096dd9}.ant-btn-primary:active>a:only-child{color:currentcolor}.ant-btn-primary:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-primary[disabled],.ant-btn-primary[disabled]:hover,.ant-btn-primary[disabled]:focus,.ant-btn-primary[disabled]:active{color:#00000040;border-color:#d9d9d9;background:#f5f5f5;text-shadow:none;box-shadow:none}.ant-btn-primary[disabled]>a:only-child,.ant-btn-primary[disabled]:hover>a:only-child,.ant-btn-primary[disabled]:focus>a:only-child,.ant-btn-primary[disabled]:active>a:only-child{color:currentcolor}.ant-btn-primary[disabled]>a:only-child:after,.ant-btn-primary[disabled]:hover>a:only-child:after,.ant-btn-primary[disabled]:focus>a:only-child:after,.ant-btn-primary[disabled]:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-group .ant-btn-primary:not(:first-child):not(:last-child){border-right-color:#40a9ff;border-left-color:#40a9ff}.ant-btn-group .ant-btn-primary:not(:first-child):not(:last-child):disabled{border-color:#d9d9d9}.ant-btn-group .ant-btn-primary:first-child:not(:last-child){border-right-color:#40a9ff}.ant-btn-group .ant-btn-primary:first-child:not(:last-child)[disabled]{border-right-color:#d9d9d9}.ant-btn-group .ant-btn-primary:last-child:not(:first-child),.ant-btn-group .ant-btn-primary+.ant-btn-primary{border-left-color:#40a9ff}.ant-btn-group .ant-btn-primary:last-child:not(:first-child)[disabled],.ant-btn-group .ant-btn-primary+.ant-btn-primary[disabled]{border-left-color:#d9d9d9}.ant-btn-ghost{color:#000000d9;border-color:#d9d9d9;background:transparent}.ant-btn-ghost>a:only-child{color:currentcolor}.ant-btn-ghost>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-ghost:hover,.ant-btn-ghost:focus{color:#40a9ff;border-color:#40a9ff;background:transparent}.ant-btn-ghost:hover>a:only-child,.ant-btn-ghost:focus>a:only-child{color:currentcolor}.ant-btn-ghost:hover>a:only-child:after,.ant-btn-ghost:focus>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-ghost:active{color:#096dd9;border-color:#096dd9;background:transparent}.ant-btn-ghost:active>a:only-child{color:currentcolor}.ant-btn-ghost:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-ghost[disabled],.ant-btn-ghost[disabled]:hover,.ant-btn-ghost[disabled]:focus,.ant-btn-ghost[disabled]:active{color:#00000040;border-color:#d9d9d9;background:#f5f5f5;text-shadow:none;box-shadow:none}.ant-btn-ghost[disabled]>a:only-child,.ant-btn-ghost[disabled]:hover>a:only-child,.ant-btn-ghost[disabled]:focus>a:only-child,.ant-btn-ghost[disabled]:active>a:only-child{color:currentcolor}.ant-btn-ghost[disabled]>a:only-child:after,.ant-btn-ghost[disabled]:hover>a:only-child:after,.ant-btn-ghost[disabled]:focus>a:only-child:after,.ant-btn-ghost[disabled]:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-dashed{color:#000000d9;border-color:#d9d9d9;background:#fff;border-style:dashed}.ant-btn-dashed>a:only-child{color:currentcolor}.ant-btn-dashed>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-dashed:hover,.ant-btn-dashed:focus{color:#40a9ff;border-color:#40a9ff;background:#fff}.ant-btn-dashed:hover>a:only-child,.ant-btn-dashed:focus>a:only-child{color:currentcolor}.ant-btn-dashed:hover>a:only-child:after,.ant-btn-dashed:focus>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-dashed:active{color:#096dd9;border-color:#096dd9;background:#fff}.ant-btn-dashed:active>a:only-child{color:currentcolor}.ant-btn-dashed:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-dashed[disabled],.ant-btn-dashed[disabled]:hover,.ant-btn-dashed[disabled]:focus,.ant-btn-dashed[disabled]:active{color:#00000040;border-color:#d9d9d9;background:#f5f5f5;text-shadow:none;box-shadow:none}.ant-btn-dashed[disabled]>a:only-child,.ant-btn-dashed[disabled]:hover>a:only-child,.ant-btn-dashed[disabled]:focus>a:only-child,.ant-btn-dashed[disabled]:active>a:only-child{color:currentcolor}.ant-btn-dashed[disabled]>a:only-child:after,.ant-btn-dashed[disabled]:hover>a:only-child:after,.ant-btn-dashed[disabled]:focus>a:only-child:after,.ant-btn-dashed[disabled]:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-danger{color:#fff;border-color:#ff4d4f;background:#ff4d4f;text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px #0000000b}.ant-btn-danger>a:only-child{color:currentcolor}.ant-btn-danger>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-danger:hover,.ant-btn-danger:focus{color:#fff;border-color:#ff7875;background:#ff7875}.ant-btn-danger:hover>a:only-child,.ant-btn-danger:focus>a:only-child{color:currentcolor}.ant-btn-danger:hover>a:only-child:after,.ant-btn-danger:focus>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-danger:active{color:#fff;border-color:#d9363e;background:#d9363e}.ant-btn-danger:active>a:only-child{color:currentcolor}.ant-btn-danger:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-danger[disabled],.ant-btn-danger[disabled]:hover,.ant-btn-danger[disabled]:focus,.ant-btn-danger[disabled]:active{color:#00000040;border-color:#d9d9d9;background:#f5f5f5;text-shadow:none;box-shadow:none}.ant-btn-danger[disabled]>a:only-child,.ant-btn-danger[disabled]:hover>a:only-child,.ant-btn-danger[disabled]:focus>a:only-child,.ant-btn-danger[disabled]:active>a:only-child{color:currentcolor}.ant-btn-danger[disabled]>a:only-child:after,.ant-btn-danger[disabled]:hover>a:only-child:after,.ant-btn-danger[disabled]:focus>a:only-child:after,.ant-btn-danger[disabled]:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-link{color:#1890ff;border-color:transparent;background:transparent;box-shadow:none}.ant-btn-link>a:only-child{color:currentcolor}.ant-btn-link>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-link:hover,.ant-btn-link:focus{color:#40a9ff;border-color:#40a9ff;background:transparent}.ant-btn-link:hover>a:only-child,.ant-btn-link:focus>a:only-child{color:currentcolor}.ant-btn-link:hover>a:only-child:after,.ant-btn-link:focus>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-link:active{color:#096dd9;border-color:#096dd9;background:transparent}.ant-btn-link:active>a:only-child{color:currentcolor}.ant-btn-link:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-link[disabled],.ant-btn-link[disabled]:hover,.ant-btn-link[disabled]:focus,.ant-btn-link[disabled]:active{color:#00000040;border-color:#d9d9d9;background:#f5f5f5;text-shadow:none;box-shadow:none}.ant-btn-link:hover{background:transparent}.ant-btn-link:hover,.ant-btn-link:focus,.ant-btn-link:active{border-color:transparent}.ant-btn-link[disabled],.ant-btn-link[disabled]:hover,.ant-btn-link[disabled]:focus,.ant-btn-link[disabled]:active{color:#00000040;border-color:transparent;background:transparent;text-shadow:none;box-shadow:none}.ant-btn-link[disabled]>a:only-child,.ant-btn-link[disabled]:hover>a:only-child,.ant-btn-link[disabled]:focus>a:only-child,.ant-btn-link[disabled]:active>a:only-child{color:currentcolor}.ant-btn-link[disabled]>a:only-child:after,.ant-btn-link[disabled]:hover>a:only-child:after,.ant-btn-link[disabled]:focus>a:only-child:after,.ant-btn-link[disabled]:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-text{color:#000000d9;border-color:transparent;background:transparent;box-shadow:none}.ant-btn-text>a:only-child{color:currentcolor}.ant-btn-text>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-text:hover,.ant-btn-text:focus{color:#40a9ff;border-color:#40a9ff;background:transparent}.ant-btn-text:hover>a:only-child,.ant-btn-text:focus>a:only-child{color:currentcolor}.ant-btn-text:hover>a:only-child:after,.ant-btn-text:focus>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-text:active{color:#096dd9;border-color:#096dd9;background:transparent}.ant-btn-text:active>a:only-child{color:currentcolor}.ant-btn-text:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-text[disabled],.ant-btn-text[disabled]:hover,.ant-btn-text[disabled]:focus,.ant-btn-text[disabled]:active{color:#00000040;border-color:#d9d9d9;background:#f5f5f5;text-shadow:none;box-shadow:none}.ant-btn-text:hover,.ant-btn-text:focus{color:#000000d9;background:rgba(0,0,0,.018);border-color:transparent}.ant-btn-text:active{color:#000000d9;background:rgba(0,0,0,.028);border-color:transparent}.ant-btn-text[disabled],.ant-btn-text[disabled]:hover,.ant-btn-text[disabled]:focus,.ant-btn-text[disabled]:active{color:#00000040;border-color:transparent;background:transparent;text-shadow:none;box-shadow:none}.ant-btn-text[disabled]>a:only-child,.ant-btn-text[disabled]:hover>a:only-child,.ant-btn-text[disabled]:focus>a:only-child,.ant-btn-text[disabled]:active>a:only-child{color:currentcolor}.ant-btn-text[disabled]>a:only-child:after,.ant-btn-text[disabled]:hover>a:only-child:after,.ant-btn-text[disabled]:focus>a:only-child:after,.ant-btn-text[disabled]:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-dangerous{color:#ff4d4f;border-color:#ff4d4f;background:#fff}.ant-btn-dangerous>a:only-child{color:currentcolor}.ant-btn-dangerous>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-dangerous:hover,.ant-btn-dangerous:focus{color:#ff7875;border-color:#ff7875;background:#fff}.ant-btn-dangerous:hover>a:only-child,.ant-btn-dangerous:focus>a:only-child{color:currentcolor}.ant-btn-dangerous:hover>a:only-child:after,.ant-btn-dangerous:focus>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-dangerous:active{color:#d9363e;border-color:#d9363e;background:#fff}.ant-btn-dangerous:active>a:only-child{color:currentcolor}.ant-btn-dangerous:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-dangerous[disabled],.ant-btn-dangerous[disabled]:hover,.ant-btn-dangerous[disabled]:focus,.ant-btn-dangerous[disabled]:active{color:#00000040;border-color:#d9d9d9;background:#f5f5f5;text-shadow:none;box-shadow:none}.ant-btn-dangerous[disabled]>a:only-child,.ant-btn-dangerous[disabled]:hover>a:only-child,.ant-btn-dangerous[disabled]:focus>a:only-child,.ant-btn-dangerous[disabled]:active>a:only-child{color:currentcolor}.ant-btn-dangerous[disabled]>a:only-child:after,.ant-btn-dangerous[disabled]:hover>a:only-child:after,.ant-btn-dangerous[disabled]:focus>a:only-child:after,.ant-btn-dangerous[disabled]:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-primary{color:#fff;border-color:#ff4d4f;background:#ff4d4f;text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px #0000000b}.ant-btn-dangerous.ant-btn-primary>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-primary>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-primary:hover,.ant-btn-dangerous.ant-btn-primary:focus{color:#fff;border-color:#ff7875;background:#ff7875}.ant-btn-dangerous.ant-btn-primary:hover>a:only-child,.ant-btn-dangerous.ant-btn-primary:focus>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-primary:hover>a:only-child:after,.ant-btn-dangerous.ant-btn-primary:focus>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-primary:active{color:#fff;border-color:#d9363e;background:#d9363e}.ant-btn-dangerous.ant-btn-primary:active>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-primary:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-primary[disabled],.ant-btn-dangerous.ant-btn-primary[disabled]:hover,.ant-btn-dangerous.ant-btn-primary[disabled]:focus,.ant-btn-dangerous.ant-btn-primary[disabled]:active{color:#00000040;border-color:#d9d9d9;background:#f5f5f5;text-shadow:none;box-shadow:none}.ant-btn-dangerous.ant-btn-primary[disabled]>a:only-child,.ant-btn-dangerous.ant-btn-primary[disabled]:hover>a:only-child,.ant-btn-dangerous.ant-btn-primary[disabled]:focus>a:only-child,.ant-btn-dangerous.ant-btn-primary[disabled]:active>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-primary[disabled]>a:only-child:after,.ant-btn-dangerous.ant-btn-primary[disabled]:hover>a:only-child:after,.ant-btn-dangerous.ant-btn-primary[disabled]:focus>a:only-child:after,.ant-btn-dangerous.ant-btn-primary[disabled]:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-link{color:#ff4d4f;border-color:transparent;background:transparent;box-shadow:none}.ant-btn-dangerous.ant-btn-link>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-link>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-link:hover,.ant-btn-dangerous.ant-btn-link:focus{color:#40a9ff;border-color:#40a9ff;background:transparent}.ant-btn-dangerous.ant-btn-link:active{color:#096dd9;border-color:#096dd9;background:transparent}.ant-btn-dangerous.ant-btn-link[disabled],.ant-btn-dangerous.ant-btn-link[disabled]:hover,.ant-btn-dangerous.ant-btn-link[disabled]:focus,.ant-btn-dangerous.ant-btn-link[disabled]:active{color:#00000040;border-color:#d9d9d9;background:#f5f5f5;text-shadow:none;box-shadow:none}.ant-btn-dangerous.ant-btn-link:hover,.ant-btn-dangerous.ant-btn-link:focus{color:#ff7875;border-color:transparent;background:transparent}.ant-btn-dangerous.ant-btn-link:hover>a:only-child,.ant-btn-dangerous.ant-btn-link:focus>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-link:hover>a:only-child:after,.ant-btn-dangerous.ant-btn-link:focus>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-link:active{color:#d9363e;border-color:transparent;background:transparent}.ant-btn-dangerous.ant-btn-link:active>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-link:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-link[disabled],.ant-btn-dangerous.ant-btn-link[disabled]:hover,.ant-btn-dangerous.ant-btn-link[disabled]:focus,.ant-btn-dangerous.ant-btn-link[disabled]:active{color:#00000040;border-color:transparent;background:transparent;text-shadow:none;box-shadow:none}.ant-btn-dangerous.ant-btn-link[disabled]>a:only-child,.ant-btn-dangerous.ant-btn-link[disabled]:hover>a:only-child,.ant-btn-dangerous.ant-btn-link[disabled]:focus>a:only-child,.ant-btn-dangerous.ant-btn-link[disabled]:active>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-link[disabled]>a:only-child:after,.ant-btn-dangerous.ant-btn-link[disabled]:hover>a:only-child:after,.ant-btn-dangerous.ant-btn-link[disabled]:focus>a:only-child:after,.ant-btn-dangerous.ant-btn-link[disabled]:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-text{color:#ff4d4f;border-color:transparent;background:transparent;box-shadow:none}.ant-btn-dangerous.ant-btn-text>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-text>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-text:hover,.ant-btn-dangerous.ant-btn-text:focus{color:#40a9ff;border-color:#40a9ff;background:transparent}.ant-btn-dangerous.ant-btn-text:active{color:#096dd9;border-color:#096dd9;background:transparent}.ant-btn-dangerous.ant-btn-text[disabled],.ant-btn-dangerous.ant-btn-text[disabled]:hover,.ant-btn-dangerous.ant-btn-text[disabled]:focus,.ant-btn-dangerous.ant-btn-text[disabled]:active{color:#00000040;border-color:#d9d9d9;background:#f5f5f5;text-shadow:none;box-shadow:none}.ant-btn-dangerous.ant-btn-text:hover,.ant-btn-dangerous.ant-btn-text:focus{color:#ff7875;border-color:transparent;background:rgba(0,0,0,.018)}.ant-btn-dangerous.ant-btn-text:hover>a:only-child,.ant-btn-dangerous.ant-btn-text:focus>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-text:hover>a:only-child:after,.ant-btn-dangerous.ant-btn-text:focus>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-text:active{color:#d9363e;border-color:transparent;background:rgba(0,0,0,.028)}.ant-btn-dangerous.ant-btn-text:active>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-text:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-text[disabled],.ant-btn-dangerous.ant-btn-text[disabled]:hover,.ant-btn-dangerous.ant-btn-text[disabled]:focus,.ant-btn-dangerous.ant-btn-text[disabled]:active{color:#00000040;border-color:transparent;background:transparent;text-shadow:none;box-shadow:none}.ant-btn-dangerous.ant-btn-text[disabled]>a:only-child,.ant-btn-dangerous.ant-btn-text[disabled]:hover>a:only-child,.ant-btn-dangerous.ant-btn-text[disabled]:focus>a:only-child,.ant-btn-dangerous.ant-btn-text[disabled]:active>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-text[disabled]>a:only-child:after,.ant-btn-dangerous.ant-btn-text[disabled]:hover>a:only-child:after,.ant-btn-dangerous.ant-btn-text[disabled]:focus>a:only-child:after,.ant-btn-dangerous.ant-btn-text[disabled]:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-icon-only{width:32px;height:32px;padding:2.4px 0;font-size:16px;border-radius:4px;vertical-align:-3px}.ant-btn-icon-only>*{font-size:16px}.ant-btn-icon-only.ant-btn-lg{width:40px;height:40px;padding:4.9px 0;font-size:18px;border-radius:4px}.ant-btn-icon-only.ant-btn-lg>*{font-size:18px}.ant-btn-icon-only.ant-btn-sm{width:24px;height:24px;padding:0;font-size:14px;border-radius:4px}.ant-btn-icon-only.ant-btn-sm>*{font-size:14px}.ant-btn-icon-only>.anticon{display:flex;justify-content:center}a.ant-btn-icon-only{vertical-align:-1px}a.ant-btn-icon-only>.anticon{display:inline}.ant-btn-round{height:32px;padding:4px 16px;font-size:14px;border-radius:32px}.ant-btn-round.ant-btn-lg{height:40px;padding:6.4px 20px;font-size:16px;border-radius:40px}.ant-btn-round.ant-btn-sm{height:24px;padding:0 12px;font-size:14px;border-radius:24px}.ant-btn-round.ant-btn-icon-only{width:auto}.ant-btn-circle{min-width:32px;padding-right:0;padding-left:0;text-align:center;border-radius:50%}.ant-btn-circle.ant-btn-lg{min-width:40px;border-radius:50%}.ant-btn-circle.ant-btn-sm{min-width:24px;border-radius:50%}.ant-btn:before{position:absolute;inset:-1px;z-index:1;display:none;background:#fff;border-radius:inherit;opacity:.35;transition:opacity .2s;content:"";pointer-events:none}.ant-btn .anticon{transition:margin-left .3s cubic-bezier(.645,.045,.355,1)}.ant-btn .anticon.anticon-plus>svg,.ant-btn .anticon.anticon-minus>svg{shape-rendering:optimizespeed}.ant-btn.ant-btn-loading{position:relative;cursor:default}.ant-btn.ant-btn-loading:before{display:block}.ant-btn>.ant-btn-loading-icon{transition:width .3s cubic-bezier(.645,.045,.355,1),opacity .3s cubic-bezier(.645,.045,.355,1)}.ant-btn>.ant-btn-loading-icon .anticon{padding-right:8px;animation:none}.ant-btn>.ant-btn-loading-icon .anticon svg{animation:loadingCircle 1s infinite linear}.ant-btn>.ant-btn-loading-icon:only-child .anticon{padding-right:0}.ant-btn-group{position:relative;display:inline-flex}.ant-btn-group>.ant-btn,.ant-btn-group>span>.ant-btn{position:relative}.ant-btn-group>.ant-btn:hover,.ant-btn-group>span>.ant-btn:hover,.ant-btn-group>.ant-btn:focus,.ant-btn-group>span>.ant-btn:focus,.ant-btn-group>.ant-btn:active,.ant-btn-group>span>.ant-btn:active{z-index:2}.ant-btn-group>.ant-btn[disabled],.ant-btn-group>span>.ant-btn[disabled]{z-index:0}.ant-btn-group .ant-btn-icon-only{font-size:14px}.ant-btn-group .ant-btn+.ant-btn,.ant-btn+.ant-btn-group,.ant-btn-group span+.ant-btn,.ant-btn-group .ant-btn+span,.ant-btn-group>span+span,.ant-btn-group+.ant-btn,.ant-btn-group+.ant-btn-group{margin-left:-1px}.ant-btn-group .ant-btn-primary+.ant-btn:not(.ant-btn-primary):not([disabled]){border-left-color:transparent}.ant-btn-group .ant-btn{border-radius:0}.ant-btn-group>.ant-btn:first-child,.ant-btn-group>span:first-child>.ant-btn{margin-left:0}.ant-btn-group>.ant-btn:only-child{border-radius:4px}.ant-btn-group>span:only-child>.ant-btn{border-radius:4px}.ant-btn-group>.ant-btn:first-child:not(:last-child),.ant-btn-group>span:first-child:not(:last-child)>.ant-btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.ant-btn-group>.ant-btn:last-child:not(:first-child),.ant-btn-group>span:last-child:not(:first-child)>.ant-btn{border-top-right-radius:4px;border-bottom-right-radius:4px}.ant-btn-group-sm>.ant-btn:only-child{border-radius:4px}.ant-btn-group-sm>span:only-child>.ant-btn{border-radius:4px}.ant-btn-group-sm>.ant-btn:first-child:not(:last-child),.ant-btn-group-sm>span:first-child:not(:last-child)>.ant-btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.ant-btn-group-sm>.ant-btn:last-child:not(:first-child),.ant-btn-group-sm>span:last-child:not(:first-child)>.ant-btn{border-top-right-radius:4px;border-bottom-right-radius:4px}.ant-btn-group>.ant-btn-group{float:left}.ant-btn-group>.ant-btn-group:not(:first-child):not(:last-child)>.ant-btn{border-radius:0}.ant-btn-group>.ant-btn-group:first-child:not(:last-child)>.ant-btn:last-child{padding-right:8px;border-top-right-radius:0;border-bottom-right-radius:0}.ant-btn-group>.ant-btn-group:last-child:not(:first-child)>.ant-btn:first-child{padding-left:8px;border-top-left-radius:0;border-bottom-left-radius:0}.ant-btn-rtl.ant-btn-group .ant-btn+.ant-btn,.ant-btn-rtl.ant-btn+.ant-btn-group,.ant-btn-rtl.ant-btn-group span+.ant-btn,.ant-btn-rtl.ant-btn-group .ant-btn+span,.ant-btn-rtl.ant-btn-group>span+span,.ant-btn-rtl.ant-btn-group+.ant-btn,.ant-btn-rtl.ant-btn-group+.ant-btn-group,.ant-btn-group-rtl.ant-btn-group .ant-btn+.ant-btn,.ant-btn-group-rtl.ant-btn+.ant-btn-group,.ant-btn-group-rtl.ant-btn-group span+.ant-btn,.ant-btn-group-rtl.ant-btn-group .ant-btn+span,.ant-btn-group-rtl.ant-btn-group>span+span,.ant-btn-group-rtl.ant-btn-group+.ant-btn,.ant-btn-group-rtl.ant-btn-group+.ant-btn-group{margin-right:-1px;margin-left:auto}.ant-btn-group.ant-btn-group-rtl{direction:rtl}.ant-btn-group-rtl.ant-btn-group>.ant-btn:first-child:not(:last-child),.ant-btn-group-rtl.ant-btn-group>span:first-child:not(:last-child)>.ant-btn{border-radius:0 4px 4px 0}.ant-btn-group-rtl.ant-btn-group>.ant-btn:last-child:not(:first-child),.ant-btn-group-rtl.ant-btn-group>span:last-child:not(:first-child)>.ant-btn{border-radius:4px 0 0 4px}.ant-btn-group-rtl.ant-btn-group-sm>.ant-btn:first-child:not(:last-child),.ant-btn-group-rtl.ant-btn-group-sm>span:first-child:not(:last-child)>.ant-btn{border-radius:0 4px 4px 0}.ant-btn-group-rtl.ant-btn-group-sm>.ant-btn:last-child:not(:first-child),.ant-btn-group-rtl.ant-btn-group-sm>span:last-child:not(:first-child)>.ant-btn{border-radius:4px 0 0 4px}.ant-btn:focus>span,.ant-btn:active>span{position:relative}.ant-btn>.anticon+span,.ant-btn>span+.anticon{margin-left:8px}.ant-btn.ant-btn-background-ghost{color:#fff;border-color:#fff}.ant-btn.ant-btn-background-ghost,.ant-btn.ant-btn-background-ghost:hover,.ant-btn.ant-btn-background-ghost:active,.ant-btn.ant-btn-background-ghost:focus{background:transparent}.ant-btn.ant-btn-background-ghost:hover,.ant-btn.ant-btn-background-ghost:focus{color:#40a9ff;border-color:#40a9ff}.ant-btn.ant-btn-background-ghost:active{color:#096dd9;border-color:#096dd9}.ant-btn.ant-btn-background-ghost[disabled]{color:#00000040;background:transparent;border-color:#d9d9d9}.ant-btn-background-ghost.ant-btn-primary{color:#1890ff;border-color:#1890ff;text-shadow:none}.ant-btn-background-ghost.ant-btn-primary>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-primary>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-primary:hover,.ant-btn-background-ghost.ant-btn-primary:focus{color:#40a9ff;border-color:#40a9ff}.ant-btn-background-ghost.ant-btn-primary:hover>a:only-child,.ant-btn-background-ghost.ant-btn-primary:focus>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-primary:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary:focus>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-primary:active{color:#096dd9;border-color:#096dd9}.ant-btn-background-ghost.ant-btn-primary:active>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-primary:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-primary[disabled],.ant-btn-background-ghost.ant-btn-primary[disabled]:hover,.ant-btn-background-ghost.ant-btn-primary[disabled]:focus,.ant-btn-background-ghost.ant-btn-primary[disabled]:active{color:#00000040;border-color:#d9d9d9;background:#f5f5f5;text-shadow:none;box-shadow:none}.ant-btn-background-ghost.ant-btn-primary[disabled]>a:only-child,.ant-btn-background-ghost.ant-btn-primary[disabled]:hover>a:only-child,.ant-btn-background-ghost.ant-btn-primary[disabled]:focus>a:only-child,.ant-btn-background-ghost.ant-btn-primary[disabled]:active>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-primary[disabled]>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary[disabled]:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary[disabled]:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary[disabled]:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-danger{color:#ff4d4f;border-color:#ff4d4f;text-shadow:none}.ant-btn-background-ghost.ant-btn-danger>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-danger>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-danger:hover,.ant-btn-background-ghost.ant-btn-danger:focus{color:#ff7875;border-color:#ff7875}.ant-btn-background-ghost.ant-btn-danger:hover>a:only-child,.ant-btn-background-ghost.ant-btn-danger:focus>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-danger:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger:focus>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-danger:active{color:#d9363e;border-color:#d9363e}.ant-btn-background-ghost.ant-btn-danger:active>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-danger:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-danger[disabled],.ant-btn-background-ghost.ant-btn-danger[disabled]:hover,.ant-btn-background-ghost.ant-btn-danger[disabled]:focus,.ant-btn-background-ghost.ant-btn-danger[disabled]:active{color:#00000040;border-color:#d9d9d9;background:#f5f5f5;text-shadow:none;box-shadow:none}.ant-btn-background-ghost.ant-btn-danger[disabled]>a:only-child,.ant-btn-background-ghost.ant-btn-danger[disabled]:hover>a:only-child,.ant-btn-background-ghost.ant-btn-danger[disabled]:focus>a:only-child,.ant-btn-background-ghost.ant-btn-danger[disabled]:active>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-danger[disabled]>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger[disabled]:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger[disabled]:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger[disabled]:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-dangerous{color:#ff4d4f;border-color:#ff4d4f;text-shadow:none}.ant-btn-background-ghost.ant-btn-dangerous>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-dangerous>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-dangerous:hover,.ant-btn-background-ghost.ant-btn-dangerous:focus{color:#ff7875;border-color:#ff7875}.ant-btn-background-ghost.ant-btn-dangerous:hover>a:only-child,.ant-btn-background-ghost.ant-btn-dangerous:focus>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-dangerous:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-dangerous:focus>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-dangerous:active{color:#d9363e;border-color:#d9363e}.ant-btn-background-ghost.ant-btn-dangerous:active>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-dangerous:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-dangerous[disabled],.ant-btn-background-ghost.ant-btn-dangerous[disabled]:hover,.ant-btn-background-ghost.ant-btn-dangerous[disabled]:focus,.ant-btn-background-ghost.ant-btn-dangerous[disabled]:active{color:#00000040;border-color:#d9d9d9;background:#f5f5f5;text-shadow:none;box-shadow:none}.ant-btn-background-ghost.ant-btn-dangerous[disabled]>a:only-child,.ant-btn-background-ghost.ant-btn-dangerous[disabled]:hover>a:only-child,.ant-btn-background-ghost.ant-btn-dangerous[disabled]:focus>a:only-child,.ant-btn-background-ghost.ant-btn-dangerous[disabled]:active>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-dangerous[disabled]>a:only-child:after,.ant-btn-background-ghost.ant-btn-dangerous[disabled]:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-dangerous[disabled]:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-dangerous[disabled]:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link{color:#ff4d4f;border-color:transparent;text-shadow:none}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:hover,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:focus{color:#ff7875;border-color:transparent}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:hover>a:only-child,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:focus>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:focus>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:active{color:#d9363e;border-color:transparent}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:active>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled],.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:hover,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:focus,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:active{color:#00000040;border-color:#d9d9d9;background:#f5f5f5;text-shadow:none;box-shadow:none}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]>a:only-child,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:hover>a:only-child,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:focus>a:only-child,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:active>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]>a:only-child:after,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:active>a:only-child:after{position:absolute;inset:0;background:transparent;content:""}.ant-btn-two-chinese-chars:first-letter{letter-spacing:.34em}.ant-btn-two-chinese-chars>*:not(.anticon){margin-right:-.34em;letter-spacing:.34em}.ant-btn.ant-btn-block{width:100%}.ant-btn:empty{display:inline-block;width:0;visibility:hidden;content:"\a0"}a.ant-btn{padding-top:.01px!important;line-height:30px}a.ant-btn-lg{line-height:38px}a.ant-btn-sm{line-height:22px}.ant-btn-rtl{direction:rtl}.ant-btn-group-rtl.ant-btn-group .ant-btn-primary:last-child:not(:first-child),.ant-btn-group-rtl.ant-btn-group .ant-btn-primary+.ant-btn-primary{border-right-color:#40a9ff;border-left-color:#d9d9d9}.ant-btn-group-rtl.ant-btn-group .ant-btn-primary:last-child:not(:first-child)[disabled],.ant-btn-group-rtl.ant-btn-group .ant-btn-primary+.ant-btn-primary[disabled]{border-right-color:#d9d9d9;border-left-color:#40a9ff}.ant-btn-rtl.ant-btn>.ant-btn-loading-icon .anticon{padding-right:0;padding-left:8px}.ant-btn>.ant-btn-loading-icon:only-child .anticon{padding-right:0;padding-left:0}.ant-btn-rtl.ant-btn>.anticon+span,.ant-btn-rtl.ant-btn>span+.anticon{margin-right:8px;margin-left:0}.ant-card{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;background:#fff;border-radius:4px}.ant-card-rtl{direction:rtl}.ant-card-hoverable{cursor:pointer;transition:box-shadow .3s,border-color .3s}.ant-card-hoverable:hover{border-color:transparent;box-shadow:0 1px 2px -2px #00000029,0 3px 6px #0000001f,0 5px 12px 4px #00000017}.ant-card-bordered{border:1px solid #f0f0f0}.ant-card-head{min-height:48px;margin-bottom:-1px;padding:0 24px;color:#000000d9;font-weight:500;font-size:16px;background:transparent;border-bottom:1px solid #f0f0f0;border-radius:4px 4px 0 0}.ant-card-head:before{display:table;content:""}.ant-card-head:after{display:table;clear:both;content:""}.ant-card-head-wrapper{display:flex;align-items:center}.ant-card-head-title{display:inline-block;flex:1;padding:16px 0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-card-head-title>.ant-typography,.ant-card-head-title>.ant-typography-edit-content{left:0;margin-top:0;margin-bottom:0}.ant-card-head .ant-tabs-top{clear:both;margin-bottom:-17px;color:#000000d9;font-weight:400;font-size:14px}.ant-card-head .ant-tabs-top-bar{border-bottom:1px solid #f0f0f0}.ant-card-extra{float:right;margin-left:auto;padding:16px 0;color:#000000d9;font-weight:400;font-size:14px}.ant-card-rtl .ant-card-extra{margin-right:auto;margin-left:0}.ant-card-body{padding:24px}.ant-card-body:before{display:table;content:""}.ant-card-body:after{display:table;clear:both;content:""}.ant-card-contain-grid:not(.ant-card-loading) .ant-card-body{margin:-1px 0 0 -1px;padding:0}.ant-card-grid{float:left;width:33.33%;padding:24px;border:0;border-radius:0;box-shadow:1px 0 #f0f0f0,0 1px #f0f0f0,1px 1px #f0f0f0,1px 0 #f0f0f0 inset,0 1px #f0f0f0 inset;transition:all .3s}.ant-card-rtl .ant-card-grid{float:right}.ant-card-grid-hoverable:hover{position:relative;z-index:1;box-shadow:0 1px 2px -2px #00000029,0 3px 6px #0000001f,0 5px 12px 4px #00000017}.ant-card-contain-tabs>.ant-card-head .ant-card-head-title{min-height:32px;padding-bottom:0}.ant-card-contain-tabs>.ant-card-head .ant-card-extra{padding-bottom:0}.ant-card-bordered .ant-card-cover{margin-top:-1px;margin-right:-1px;margin-left:-1px}.ant-card-cover>*{display:block;width:100%}.ant-card-cover img{border-radius:4px 4px 0 0}.ant-card-actions{margin:0;padding:0;list-style:none;background:#fff;border-top:1px solid #f0f0f0}.ant-card-actions:before{display:table;content:""}.ant-card-actions:after{display:table;clear:both;content:""}.ant-card-actions>li{float:left;margin:12px 0;color:#00000073;text-align:center}.ant-card-rtl .ant-card-actions>li{float:right}.ant-card-actions>li>span{position:relative;display:block;min-width:32px;font-size:14px;line-height:1.5715;cursor:pointer}.ant-card-actions>li>span:hover{color:#1890ff;transition:color .3s}.ant-card-actions>li>span a:not(.ant-btn),.ant-card-actions>li>span>.anticon{display:inline-block;width:100%;color:#00000073;line-height:22px;transition:color .3s}.ant-card-actions>li>span a:not(.ant-btn):hover,.ant-card-actions>li>span>.anticon:hover{color:#1890ff}.ant-card-actions>li>span>.anticon{font-size:16px;line-height:22px}.ant-card-actions>li:not(:last-child){border-right:1px solid #f0f0f0}.ant-card-rtl .ant-card-actions>li:not(:last-child){border-right:none;border-left:1px solid #f0f0f0}.ant-card-type-inner .ant-card-head{padding:0 24px;background:#fafafa}.ant-card-type-inner .ant-card-head-title{padding:12px 0;font-size:14px}.ant-card-type-inner .ant-card-body{padding:16px 24px}.ant-card-type-inner .ant-card-extra{padding:13.5px 0}.ant-card-meta{margin:-4px 0}.ant-card-meta:before{display:table;content:""}.ant-card-meta:after{display:table;clear:both;content:""}.ant-card-meta-avatar{float:left;padding-right:16px}.ant-card-rtl .ant-card-meta-avatar{float:right;padding-right:0;padding-left:16px}.ant-card-meta-detail{overflow:hidden}.ant-card-meta-detail>div:not(:last-child){margin-bottom:8px}.ant-card-meta-title{overflow:hidden;color:#000000d9;font-weight:500;font-size:16px;white-space:nowrap;text-overflow:ellipsis}.ant-card-meta-description{color:#00000073}.ant-card-loading{overflow:hidden}.ant-card-loading .ant-card-body{-webkit-user-select:none;user-select:none}.ant-card-loading-content p{margin:0}.ant-card-loading-block{height:14px;margin:4px 0;background:linear-gradient(90deg,rgba(207,216,220,.2),rgba(207,216,220,.4),rgba(207,216,220,.2));background-size:600% 600%;border-radius:4px;animation:card-loading 1.4s ease infinite}@keyframes card-loading{0%,to{background-position:0 50%}50%{background-position:100% 50%}}.ant-card-small>.ant-card-head{min-height:36px;padding:0 12px;font-size:14px}.ant-card-small>.ant-card-head>.ant-card-head-wrapper>.ant-card-head-title{padding:8px 0}.ant-card-small>.ant-card-head>.ant-card-head-wrapper>.ant-card-extra{padding:8px 0;font-size:14px}.ant-card-small>.ant-card-body{padding:12px}.ant-card-rtl .ant-skeleton-header{padding:0 0 0 16px}nz-card{display:block}nz-card-meta{display:block}nz-card-loading{display:block}.ant-carousel{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum"}.ant-carousel .slick-slider{position:relative;display:block;box-sizing:border-box;touch-action:pan-y;-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent}.ant-carousel .slick-list{position:relative;display:block;margin:0;padding:0;overflow:hidden}.ant-carousel .slick-list:focus{outline:none}.ant-carousel .slick-list.dragging{cursor:pointer}.ant-carousel .slick-list .slick-slide{pointer-events:none}.ant-carousel .slick-list .slick-slide input.ant-radio-input,.ant-carousel .slick-list .slick-slide input.ant-checkbox-input{visibility:hidden}.ant-carousel .slick-list .slick-slide.slick-active{pointer-events:auto}.ant-carousel .slick-list .slick-slide.slick-active input.ant-radio-input,.ant-carousel .slick-list .slick-slide.slick-active input.ant-checkbox-input{visibility:visible}.ant-carousel .slick-list .slick-slide>div>div{vertical-align:bottom}.ant-carousel .slick-slider .slick-track,.ant-carousel .slick-slider .slick-list{transform:translateZ(0);touch-action:pan-y}.ant-carousel .slick-track{position:relative;top:0;left:0;display:block}.ant-carousel .slick-track:before,.ant-carousel .slick-track:after{display:table;content:""}.ant-carousel .slick-track:after{clear:both}.slick-loading .ant-carousel .slick-track{visibility:hidden}.ant-carousel .slick-slide{display:none;float:left;height:100%;min-height:1px}.ant-carousel .slick-slide img{display:block}.ant-carousel .slick-slide.slick-loading img{display:none}.ant-carousel .slick-slide.dragging img{pointer-events:none}.ant-carousel .slick-initialized .slick-slide{display:block}.ant-carousel .slick-loading .slick-slide{visibility:hidden}.ant-carousel .slick-vertical .slick-slide{display:block;height:auto}.ant-carousel .slick-arrow.slick-hidden{display:none}.ant-carousel .slick-prev,.ant-carousel .slick-next{position:absolute;top:50%;display:block;width:20px;height:20px;margin-top:-10px;padding:0;color:transparent;font-size:0;line-height:0;background:transparent;border:0;outline:none;cursor:pointer}.ant-carousel .slick-prev:hover,.ant-carousel .slick-next:hover,.ant-carousel .slick-prev:focus,.ant-carousel .slick-next:focus{color:transparent;background:transparent;outline:none}.ant-carousel .slick-prev:hover:before,.ant-carousel .slick-next:hover:before,.ant-carousel .slick-prev:focus:before,.ant-carousel .slick-next:focus:before{opacity:1}.ant-carousel .slick-prev.slick-disabled:before,.ant-carousel .slick-next.slick-disabled:before{opacity:.25}.ant-carousel .slick-prev{left:-25px}.ant-carousel .slick-prev:before{content:"\2190"}.ant-carousel .slick-next{right:-25px}.ant-carousel .slick-next:before{content:"\2192"}.ant-carousel .slick-dots{position:absolute;right:0;bottom:0;left:0;z-index:15;display:flex!important;justify-content:center;margin-right:15%;margin-left:15%;padding-left:0;list-style:none}.ant-carousel .slick-dots-bottom{bottom:12px}.ant-carousel .slick-dots-top{top:12px;bottom:auto}.ant-carousel .slick-dots li{position:relative;display:inline-block;flex:0 1 auto;box-sizing:content-box;width:16px;height:3px;margin:0 3px;padding:0;text-align:center;text-indent:-999px;vertical-align:top;transition:all .5s}.ant-carousel .slick-dots li button{display:block;width:100%;height:3px;padding:0;color:transparent;font-size:0;background:#fff;border:0;border-radius:1px;outline:none;cursor:pointer;opacity:.3;transition:all .5s}.ant-carousel .slick-dots li button:hover,.ant-carousel .slick-dots li button:focus{opacity:.75}.ant-carousel .slick-dots li.slick-active{width:24px}.ant-carousel .slick-dots li.slick-active button{background:#fff;opacity:1}.ant-carousel .slick-dots li.slick-active:hover,.ant-carousel .slick-dots li.slick-active:focus{opacity:1}.ant-carousel-vertical .slick-dots{top:50%;bottom:auto;flex-direction:column;width:3px;height:auto;margin:0;transform:translateY(-50%)}.ant-carousel-vertical .slick-dots-left{right:auto;left:12px}.ant-carousel-vertical .slick-dots-right{right:12px;left:auto}.ant-carousel-vertical .slick-dots li{width:3px;height:16px;margin:4px 2px;vertical-align:baseline}.ant-carousel-vertical .slick-dots li button{width:3px;height:16px}.ant-carousel-vertical .slick-dots li.slick-active,.ant-carousel-vertical .slick-dots li.slick-active button{width:3px;height:24px}.ant-carousel-rtl{direction:rtl}.ant-carousel-rtl .ant-carousel .slick-track{right:0;left:auto}.ant-carousel-rtl .ant-carousel .slick-prev{right:-25px;left:auto}.ant-carousel-rtl .ant-carousel .slick-prev:before{content:"\2192"}.ant-carousel-rtl .ant-carousel .slick-next{right:auto;left:-25px}.ant-carousel-rtl .ant-carousel .slick-next:before{content:"\2190"}.ant-carousel-rtl.ant-carousel .slick-dots{flex-direction:row-reverse}.ant-carousel-rtl.ant-carousel-vertical .slick-dots{flex-direction:column}nz-carousel{display:block;position:relative;overflow:hidden;width:100%;height:100%}.slick-dots{display:block}.slick-track{opacity:1}.slick-list{direction:ltr}@keyframes antCheckboxEffect{0%{transform:scale(1);opacity:.5}to{transform:scale(1.6);opacity:0}}.ant-checkbox{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;top:.2em;line-height:1;white-space:nowrap;outline:none;cursor:pointer}.ant-checkbox-wrapper:hover .ant-checkbox-inner,.ant-checkbox:hover .ant-checkbox-inner,.ant-checkbox-input:focus+.ant-checkbox-inner{border-color:#1890ff}.ant-checkbox-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #1890ff;border-radius:4px;visibility:hidden;animation:antCheckboxEffect .36s ease-in-out;animation-fill-mode:backwards;content:""}.ant-checkbox:hover:after,.ant-checkbox-wrapper:hover .ant-checkbox:after{visibility:visible}.ant-checkbox-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;direction:ltr;background-color:#fff;border:1px solid #d9d9d9;border-radius:4px;border-collapse:separate;transition:all .3s}.ant-checkbox-inner:after{position:absolute;top:50%;left:21.5%;display:table;width:5.71428571px;height:9.14285714px;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(0) translate(-50%,-50%);opacity:0;transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;content:" "}.ant-checkbox-input{position:absolute;inset:0;z-index:1;width:100%;height:100%;cursor:pointer;opacity:0}.ant-checkbox-checked .ant-checkbox-inner:after{position:absolute;display:table;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(1) translate(-50%,-50%);opacity:1;transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;content:" "}.ant-checkbox-checked .ant-checkbox-inner{background-color:#1890ff;border-color:#1890ff}.ant-checkbox-disabled{cursor:not-allowed}.ant-checkbox-disabled.ant-checkbox-checked .ant-checkbox-inner:after{border-color:#00000040;animation-name:none}.ant-checkbox-disabled .ant-checkbox-input{cursor:not-allowed;pointer-events:none}.ant-checkbox-disabled .ant-checkbox-inner{background-color:#f5f5f5;border-color:#d9d9d9!important}.ant-checkbox-disabled .ant-checkbox-inner:after{border-color:#f5f5f5;border-collapse:separate;animation-name:none}.ant-checkbox-disabled+span{color:#00000040;cursor:not-allowed}.ant-checkbox-disabled:hover:after,.ant-checkbox-wrapper:hover .ant-checkbox-disabled:after{visibility:hidden}.ant-checkbox-wrapper{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-flex;align-items:baseline;line-height:unset;cursor:pointer}.ant-checkbox-wrapper:after{display:inline-block;width:0;overflow:hidden;content:"\a0"}.ant-checkbox-wrapper.ant-checkbox-wrapper-disabled{cursor:not-allowed}.ant-checkbox-wrapper+.ant-checkbox-wrapper{margin-left:8px}.ant-checkbox-wrapper.ant-checkbox-wrapper-in-form-item input[type=checkbox]{width:14px;height:14px}.ant-checkbox+span{padding-right:8px;padding-left:8px}.ant-checkbox-group{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-block}.ant-checkbox-group-item{margin-right:8px}.ant-checkbox-group-item:last-child{margin-right:0}.ant-checkbox-group-item+.ant-checkbox-group-item{margin-left:0}.ant-checkbox-indeterminate .ant-checkbox-inner{background-color:#fff;border-color:#d9d9d9}.ant-checkbox-indeterminate .ant-checkbox-inner:after{top:50%;left:50%;width:8px;height:8px;background-color:#1890ff;border:0;transform:translate(-50%,-50%) scale(1);opacity:1;content:" "}.ant-checkbox-indeterminate.ant-checkbox-disabled .ant-checkbox-inner:after{background-color:#00000040;border-color:#00000040}.ant-checkbox-rtl{direction:rtl}.ant-checkbox-group-rtl .ant-checkbox-group-item{margin-right:0;margin-left:8px}.ant-checkbox-group-rtl .ant-checkbox-group-item:last-child{margin-left:0!important}.ant-checkbox-group-rtl .ant-checkbox-group-item+.ant-checkbox-group-item{margin-left:8px}.ant-checkbox+span:empty{display:none}.ant-collapse{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";background-color:#fafafa;border:1px solid #d9d9d9;border-bottom:0;border-radius:4px}.ant-collapse>.ant-collapse-item{border-bottom:1px solid #d9d9d9}.ant-collapse>.ant-collapse-item:last-child,.ant-collapse>.ant-collapse-item:last-child>.ant-collapse-header{border-radius:0 0 4px 4px}.ant-collapse>.ant-collapse-item>.ant-collapse-header{position:relative;display:flex;flex-wrap:nowrap;align-items:flex-start;padding:12px 16px;color:#000000d9;line-height:1.5715;cursor:pointer;transition:all .3s,visibility 0s}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{display:inline-block;margin-right:12px;font-size:12px;vertical-align:-1px}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow svg{transition:transform .24s}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-extra{margin-left:auto}.ant-collapse>.ant-collapse-item>.ant-collapse-header:focus{outline:none}.ant-collapse>.ant-collapse-item .ant-collapse-header-collapsible-only{cursor:default}.ant-collapse>.ant-collapse-item .ant-collapse-header-collapsible-only .ant-collapse-header-text{cursor:pointer}.ant-collapse>.ant-collapse-item.ant-collapse-no-arrow>.ant-collapse-header{padding-left:12px}.ant-collapse-icon-position-right>.ant-collapse-item>.ant-collapse-header{position:relative;padding:12px 40px 12px 16px}.ant-collapse-icon-position-right>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{position:absolute;top:50%;right:16px;left:auto;margin:0;transform:translateY(-50%)}.ant-collapse-content{color:#000000d9;background-color:#fff;border-top:1px solid #d9d9d9}.ant-collapse-content>.ant-collapse-content-box{padding:16px}.ant-collapse-content-hidden{display:none}.ant-collapse-item:last-child>.ant-collapse-content{border-radius:0 0 4px 4px}.ant-collapse-borderless{background-color:#fafafa;border:0}.ant-collapse-borderless>.ant-collapse-item{border-bottom:1px solid #d9d9d9}.ant-collapse-borderless>.ant-collapse-item:last-child,.ant-collapse-borderless>.ant-collapse-item:last-child .ant-collapse-header{border-radius:0}.ant-collapse-borderless>.ant-collapse-item:last-child{border-bottom:0}.ant-collapse-borderless>.ant-collapse-item>.ant-collapse-content{background-color:transparent;border-top:0}.ant-collapse-borderless>.ant-collapse-item>.ant-collapse-content>.ant-collapse-content-box{padding-top:4px}.ant-collapse-ghost{background-color:transparent;border:0}.ant-collapse-ghost>.ant-collapse-item{border-bottom:0}.ant-collapse-ghost>.ant-collapse-item>.ant-collapse-content{background-color:transparent;border-top:0}.ant-collapse-ghost>.ant-collapse-item>.ant-collapse-content>.ant-collapse-content-box{padding-top:12px;padding-bottom:12px}.ant-collapse .ant-collapse-item-disabled>.ant-collapse-header,.ant-collapse .ant-collapse-item-disabled>.ant-collapse-header>.arrow{color:#00000040;cursor:not-allowed}.ant-collapse-rtl{direction:rtl}.ant-collapse-rtl .ant-collapse>.ant-collapse-item>.ant-collapse-header{padding:12px 40px 12px 16px}.ant-collapse-rtl.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{margin-right:0;margin-left:12px}.ant-collapse-rtl.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow svg{transform:rotate(180deg)}.ant-collapse-rtl.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-extra{margin-right:auto;margin-left:0}.ant-collapse-rtl.ant-collapse>.ant-collapse-item.ant-collapse-no-arrow>.ant-collapse-header{padding-right:12px;padding-left:0}nz-collapse{display:block}nz-collapse-panel{display:block}.ant-comment{position:relative;background-color:inherit}.ant-comment-inner{display:flex;padding:16px 0}.ant-comment-avatar{position:relative;flex-shrink:0;margin-right:12px;cursor:pointer}.ant-comment-avatar img{width:32px;height:32px;border-radius:50%}.ant-comment-content{position:relative;flex:1 1 auto;min-width:1px;font-size:14px;word-wrap:break-word}.ant-comment-content-author{display:flex;flex-wrap:wrap;justify-content:flex-start;margin-bottom:4px;font-size:14px}.ant-comment-content-author>a,.ant-comment-content-author>span{padding-right:8px;font-size:12px;line-height:18px}.ant-comment-content-author-name{color:#00000073;font-size:14px;transition:color .3s}.ant-comment-content-author-name>*{color:#00000073}.ant-comment-content-author-name>*:hover{color:#00000073}.ant-comment-content-author-time{color:#ccc;white-space:nowrap;cursor:auto}.ant-comment-content-detail p{margin-bottom:inherit;white-space:pre-wrap}.ant-comment-actions{margin-top:12px;margin-bottom:inherit;padding-left:0}.ant-comment-actions>li{display:inline-block;color:#00000073}.ant-comment-actions>li>span{margin-right:10px;color:#00000073;font-size:12px;cursor:pointer;transition:color .3s;-webkit-user-select:none;user-select:none}.ant-comment-actions>li>span:hover{color:#595959}.ant-comment-nested{margin-left:44px}.ant-comment-rtl{direction:rtl}.ant-comment-rtl .ant-comment-avatar{margin-right:0;margin-left:12px}.ant-comment-rtl .ant-comment-content-author>a,.ant-comment-rtl .ant-comment-content-author>span{padding-right:0;padding-left:8px}.ant-comment-rtl .ant-comment-actions{padding-right:0}.ant-comment-rtl .ant-comment-actions>li>span{margin-right:0;margin-left:10px}.ant-comment-rtl .ant-comment-nested{margin-right:44px;margin-left:0}nz-comment{display:block}nz-comment-content{display:block}.ant-picker-status-error.ant-picker,.ant-picker-status-error.ant-picker:not([disabled]):hover{background-color:#fff;border-color:#ff4d4f}.ant-picker-status-error.ant-picker-focused,.ant-picker-status-error.ant-picker:focus{border-color:#ff7875;box-shadow:0 0 0 2px #ff4d4f33;border-right-width:1px;outline:0}.ant-picker-status-warning.ant-picker,.ant-picker-status-warning.ant-picker:not([disabled]):hover{background-color:#fff;border-color:#faad14}.ant-picker-status-warning.ant-picker-focused,.ant-picker-status-warning.ant-picker:focus{border-color:#ffc53d;box-shadow:0 0 0 2px #faad1433;border-right-width:1px;outline:0}.ant-picker{box-sizing:border-box;margin:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";padding:4px 11px;position:relative;display:inline-flex;align-items:center;background:#fff;border:1px solid #d9d9d9;border-radius:4px;transition:border .3s,box-shadow .3s}.ant-picker:hover,.ant-picker-focused{border-color:#40a9ff;border-right-width:1px}.ant-input-rtl .ant-picker:hover,.ant-input-rtl .ant-picker-focused{border-right-width:0;border-left-width:1px!important}.ant-picker-focused{border-color:#40a9ff;box-shadow:0 0 0 2px #1890ff33;border-right-width:1px;outline:0}.ant-input-rtl .ant-picker-focused{border-right-width:0;border-left-width:1px!important}.ant-picker.ant-picker-disabled{background:#f5f5f5;border-color:#d9d9d9;cursor:not-allowed}.ant-picker.ant-picker-disabled .ant-picker-suffix{color:#00000040}.ant-picker.ant-picker-borderless{background-color:transparent!important;border-color:transparent!important;box-shadow:none!important}.ant-picker-input{position:relative;display:inline-flex;align-items:center;width:100%}.ant-picker-input>input{position:relative;display:inline-block;width:100%;min-width:0;color:#000000d9;font-size:14px;line-height:1.5715;background-color:#fff;background-image:none;border:1px solid #d9d9d9;border-radius:4px;transition:all .3s;flex:auto;min-width:1px;height:auto;padding:0;background:transparent;border:0}.ant-picker-input>input::placeholder{color:#bfbfbf;-webkit-user-select:none;user-select:none}.ant-picker-input>input:placeholder-shown{text-overflow:ellipsis}.ant-picker-input>input:hover{border-color:#40a9ff;border-right-width:1px}.ant-input-rtl .ant-picker-input>input:hover{border-right-width:0;border-left-width:1px!important}.ant-picker-input>input:focus,.ant-picker-input>input-focused{border-color:#40a9ff;box-shadow:0 0 0 2px #1890ff33;border-right-width:1px;outline:0}.ant-input-rtl .ant-picker-input>input:focus,.ant-input-rtl .ant-picker-input>input-focused{border-right-width:0;border-left-width:1px!important}.ant-picker-input>input-disabled{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;box-shadow:none;cursor:not-allowed;opacity:1}.ant-picker-input>input-disabled:hover{border-color:#d9d9d9;border-right-width:1px}.ant-picker-input>input[disabled]{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;box-shadow:none;cursor:not-allowed;opacity:1}.ant-picker-input>input[disabled]:hover{border-color:#d9d9d9;border-right-width:1px}.ant-picker-input>input-borderless,.ant-picker-input>input-borderless:hover,.ant-picker-input>input-borderless:focus,.ant-picker-input>input-borderless-focused,.ant-picker-input>input-borderless-disabled,.ant-picker-input>input-borderless[disabled]{background-color:transparent;border:none;box-shadow:none}textarea.ant-picker-input>input{max-width:100%;height:auto;min-height:32px;line-height:1.5715;vertical-align:bottom;transition:all .3s,height 0s}.ant-picker-input>input-lg{padding:6.5px 11px;font-size:16px}.ant-picker-input>input-sm{padding:0 7px}.ant-picker-input>input-rtl{direction:rtl}.ant-picker-input>input:focus{box-shadow:none}.ant-picker-input>input[disabled]{background:transparent}.ant-picker-input:hover .ant-picker-clear{opacity:1}.ant-picker-input-placeholder>input{color:#bfbfbf}.ant-picker-large{padding:6.5px 11px}.ant-picker-large .ant-picker-input>input{font-size:16px}.ant-picker-small{padding:0 7px}.ant-picker-suffix{display:flex;flex:none;align-self:center;margin-left:4px;color:#00000040;line-height:1;pointer-events:none}.ant-picker-suffix>*{vertical-align:top}.ant-picker-suffix>*:not(:last-child){margin-right:8px}.ant-picker-clear{position:absolute;top:50%;right:0;color:#00000040;line-height:1;background:#fff;transform:translateY(-50%);cursor:pointer;opacity:0;transition:opacity .3s,color .3s}.ant-picker-clear>*{vertical-align:top}.ant-picker-clear:hover{color:#00000073}.ant-picker-separator{position:relative;display:inline-block;width:1em;height:16px;color:#00000040;font-size:16px;vertical-align:top;cursor:default}.ant-picker-focused .ant-picker-separator{color:#00000073}.ant-picker-disabled .ant-picker-range-separator .ant-picker-separator{cursor:not-allowed}.ant-picker-range{position:relative;display:inline-flex}.ant-picker-range .ant-picker-clear{right:11px}.ant-picker-range:hover .ant-picker-clear{opacity:1}.ant-picker-range .ant-picker-active-bar{bottom:-1px;height:2px;margin-left:11px;background:#1890ff;opacity:0;transition:all .3s ease-out;pointer-events:none}.ant-picker-range.ant-picker-focused .ant-picker-active-bar{opacity:1}.ant-picker-range-separator{align-items:center;padding:0 8px;line-height:1}.ant-picker-range.ant-picker-small .ant-picker-clear{right:7px}.ant-picker-range.ant-picker-small .ant-picker-active-bar{margin-left:7px}.ant-picker-dropdown{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;top:-9999px;left:-9999px;z-index:1050}.ant-picker-dropdown-hidden{display:none}.ant-picker-dropdown-placement-bottomLeft .ant-picker-range-arrow{top:2.58561808px;display:block;transform:rotate(-135deg) translateY(1px)}.ant-picker-dropdown-placement-topLeft .ant-picker-range-arrow{bottom:2.58561808px;display:block;transform:rotate(45deg)}.ant-picker-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-picker-dropdown-placement-topLeft,.ant-picker-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-picker-dropdown-placement-topRight,.ant-picker-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-picker-dropdown-placement-topLeft,.ant-picker-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-picker-dropdown-placement-topRight{animation-name:antSlideDownIn}.ant-picker-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-picker-dropdown-placement-bottomLeft,.ant-picker-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-picker-dropdown-placement-bottomRight,.ant-picker-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-picker-dropdown-placement-bottomLeft,.ant-picker-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-picker-dropdown-placement-bottomRight{animation-name:antSlideUpIn}.ant-picker-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-picker-dropdown-placement-topLeft,.ant-picker-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-picker-dropdown-placement-topRight{animation-name:antSlideDownOut}.ant-picker-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-picker-dropdown-placement-bottomLeft,.ant-picker-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-picker-dropdown-placement-bottomRight{animation-name:antSlideUpOut}.ant-picker-dropdown-range{padding:7.54247233px 0}.ant-picker-dropdown-range-hidden{display:none}.ant-picker-dropdown .ant-picker-panel>.ant-picker-time-panel{padding-top:4px}.ant-picker-ranges{margin-bottom:0;padding:4px 12px;overflow:hidden;line-height:34px;text-align:left;list-style:none}.ant-picker-ranges>li{display:inline-block}.ant-picker-ranges .ant-picker-preset>.ant-tag-blue{color:#1890ff;background:#e6f7ff;border-color:#91d5ff;cursor:pointer}.ant-picker-ranges .ant-picker-ok{float:right;margin-left:8px}.ant-picker-range-wrapper{display:flex}.ant-picker-range-arrow{position:absolute;z-index:1;display:none;width:11.3137085px;height:11.3137085px;margin-left:16.5px;background:linear-gradient(135deg,transparent 40%,#fff 40%);box-shadow:2px 2px 6px -2px #0000001a;transition:left .3s ease-out;border-radius:0 0 2px;pointer-events:none}.ant-picker-range-arrow:before{position:absolute;top:-11.3137085px;left:-11.3137085px;width:33.9411255px;height:33.9411255px;background:#fff;background-repeat:no-repeat;background-position:-10px -10px;content:"";clip-path:path("M 9.849242404917499 24.091883092036785 A 5 5 0 0 1 13.384776310850237 22.627416997969522 L 20.627416997969522 22.627416997969522 A 2 2 0 0 0 22.627416997969522 20.627416997969522 L 22.627416997969522 13.384776310850237 A 5 5 0 0 1 24.091883092036785 9.849242404917499 L 23.091883092036785 9.849242404917499 L 9.849242404917499 23.091883092036785 Z")}.ant-picker-panel-container{overflow:hidden;vertical-align:top;background:#fff;border-radius:4px;box-shadow:0 3px 6px -4px #0000001f,0 6px 16px #00000014,0 9px 28px 8px #0000000d;transition:margin .3s}.ant-picker-panel-container .ant-picker-panels{display:inline-flex;flex-wrap:nowrap;direction:ltr}.ant-picker-panel-container .ant-picker-panel{vertical-align:top;background:transparent;border-width:0 0 1px 0;border-radius:0}.ant-picker-panel-container .ant-picker-panel .ant-picker-content,.ant-picker-panel-container .ant-picker-panel table{text-align:center}.ant-picker-panel-container .ant-picker-panel-focused{border-color:#f0f0f0}.ant-picker-panel{display:inline-flex;flex-direction:column;text-align:center;background:#fff;border:1px solid #f0f0f0;border-radius:4px;outline:none}.ant-picker-panel-focused{border-color:#1890ff}.ant-picker-decade-panel,.ant-picker-year-panel,.ant-picker-quarter-panel,.ant-picker-month-panel,.ant-picker-week-panel,.ant-picker-date-panel,.ant-picker-time-panel{display:flex;flex-direction:column;width:280px}.ant-picker-header{display:flex;padding:0 8px;color:#000000d9;border-bottom:1px solid #f0f0f0}.ant-picker-header>*{flex:none}.ant-picker-header button{padding:0;color:#00000040;line-height:40px;background:transparent;border:0;cursor:pointer;transition:color .3s}.ant-picker-header>button{min-width:1.6em;font-size:14px}.ant-picker-header>button:hover{color:#000000d9}.ant-picker-header-view{flex:auto;font-weight:500;line-height:40px}.ant-picker-header-view button{color:inherit;font-weight:inherit}.ant-picker-header-view button:not(:first-child){margin-left:8px}.ant-picker-header-view button:hover{color:#1890ff}.ant-picker-prev-icon,.ant-picker-next-icon,.ant-picker-super-prev-icon,.ant-picker-super-next-icon{position:relative;display:inline-block;width:7px;height:7px}.ant-picker-prev-icon:before,.ant-picker-next-icon:before,.ant-picker-super-prev-icon:before,.ant-picker-super-next-icon:before{position:absolute;top:0;left:0;display:inline-block;width:7px;height:7px;border:0 solid currentcolor;border-width:1.5px 0 0 1.5px;content:""}.ant-picker-super-prev-icon:after,.ant-picker-super-next-icon:after{position:absolute;top:4px;left:4px;display:inline-block;width:7px;height:7px;border:0 solid currentcolor;border-width:1.5px 0 0 1.5px;content:""}.ant-picker-prev-icon,.ant-picker-super-prev-icon{transform:rotate(-45deg)}.ant-picker-next-icon,.ant-picker-super-next-icon{transform:rotate(135deg)}.ant-picker-content{width:100%;table-layout:fixed;border-collapse:collapse}.ant-picker-content th,.ant-picker-content td{position:relative;min-width:24px;font-weight:400}.ant-picker-content th{height:30px;color:#000000d9;line-height:30px}.ant-picker-cell{padding:3px 0;color:#00000040;cursor:pointer}.ant-picker-cell-in-view{color:#000000d9}.ant-picker-cell:before{position:absolute;top:50%;right:0;left:0;z-index:1;height:24px;transform:translateY(-50%);transition:all .3s;content:""}.ant-picker-cell:hover:not(.ant-picker-cell-in-view) .ant-picker-cell-inner,.ant-picker-cell:hover:not(.ant-picker-cell-selected):not(.ant-picker-cell-range-start):not(.ant-picker-cell-range-end):not(.ant-picker-cell-range-hover-start):not(.ant-picker-cell-range-hover-end) .ant-picker-cell-inner{background:#f5f5f5}.ant-picker-cell-in-view.ant-picker-cell-today .ant-picker-cell-inner:before{position:absolute;inset:0;z-index:1;border:1px solid #1890ff;border-radius:4px;content:""}.ant-picker-cell-in-view.ant-picker-cell-in-range{position:relative}.ant-picker-cell-in-view.ant-picker-cell-in-range:before{background:#e6f7ff}.ant-picker-cell-in-view.ant-picker-cell-selected .ant-picker-cell-inner,.ant-picker-cell-in-view.ant-picker-cell-range-start .ant-picker-cell-inner,.ant-picker-cell-in-view.ant-picker-cell-range-end .ant-picker-cell-inner{color:#fff;background:#1890ff}.ant-picker-cell-in-view.ant-picker-cell-range-start:not(.ant-picker-cell-range-start-single):before,.ant-picker-cell-in-view.ant-picker-cell-range-end:not(.ant-picker-cell-range-end-single):before{background:#e6f7ff}.ant-picker-cell-in-view.ant-picker-cell-range-start:before{left:50%}.ant-picker-cell-in-view.ant-picker-cell-range-end:before{right:50%}.ant-picker-cell-in-view.ant-picker-cell-range-hover-start:not(.ant-picker-cell-in-range):not(.ant-picker-cell-range-start):not(.ant-picker-cell-range-end):after,.ant-picker-cell-in-view.ant-picker-cell-range-hover-end:not(.ant-picker-cell-in-range):not(.ant-picker-cell-range-start):not(.ant-picker-cell-range-end):after,.ant-picker-cell-in-view.ant-picker-cell-range-hover-start.ant-picker-cell-range-start-single:after,.ant-picker-cell-in-view.ant-picker-cell-range-hover-start.ant-picker-cell-range-start.ant-picker-cell-range-end.ant-picker-cell-range-end-near-hover:after,.ant-picker-cell-in-view.ant-picker-cell-range-hover-end.ant-picker-cell-range-start.ant-picker-cell-range-end.ant-picker-cell-range-start-near-hover:after,.ant-picker-cell-in-view.ant-picker-cell-range-hover-end.ant-picker-cell-range-end-single:after,.ant-picker-cell-in-view.ant-picker-cell-range-hover:not(.ant-picker-cell-in-range):after{position:absolute;top:50%;z-index:0;height:24px;border-top:1px dashed #7ec1ff;border-bottom:1px dashed #7ec1ff;transform:translateY(-50%);transition:all .3s;content:""}.ant-picker-cell-range-hover-start:after,.ant-picker-cell-range-hover-end:after,.ant-picker-cell-range-hover:after{right:0;left:2px}.ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover:before,.ant-picker-cell-in-view.ant-picker-cell-range-start.ant-picker-cell-range-hover:before,.ant-picker-cell-in-view.ant-picker-cell-range-end.ant-picker-cell-range-hover:before,.ant-picker-cell-in-view.ant-picker-cell-range-start:not(.ant-picker-cell-range-start-single).ant-picker-cell-range-hover-start:before,.ant-picker-cell-in-view.ant-picker-cell-range-end:not(.ant-picker-cell-range-end-single).ant-picker-cell-range-hover-end:before,.ant-picker-panel>:not(.ant-picker-date-panel) .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start:before,.ant-picker-panel>:not(.ant-picker-date-panel) .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end:before{background:#cbe6ff}.ant-picker-cell-in-view.ant-picker-cell-range-start:not(.ant-picker-cell-range-start-single):not(.ant-picker-cell-range-end) .ant-picker-cell-inner{border-radius:4px 0 0 4px}.ant-picker-cell-in-view.ant-picker-cell-range-end:not(.ant-picker-cell-range-end-single):not(.ant-picker-cell-range-start) .ant-picker-cell-inner{border-radius:0 4px 4px 0}.ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start .ant-picker-cell-inner:after,.ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end .ant-picker-cell-inner:after{position:absolute;top:0;bottom:0;z-index:-1;background:#cbe6ff;transition:all .3s;content:""}.ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start .ant-picker-cell-inner:after{right:-6px;left:0}.ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end .ant-picker-cell-inner:after{right:0;left:-6px}.ant-picker-cell-range-hover.ant-picker-cell-range-start:after{right:50%}.ant-picker-cell-range-hover.ant-picker-cell-range-end:after{left:50%}tr>.ant-picker-cell-in-view.ant-picker-cell-range-hover:first-child:after,tr>.ant-picker-cell-in-view.ant-picker-cell-range-hover-end:first-child:after,.ant-picker-cell-in-view.ant-picker-cell-start.ant-picker-cell-range-hover-edge-start.ant-picker-cell-range-hover-edge-start-near-range:after,.ant-picker-cell-in-view.ant-picker-cell-range-hover-edge-start:not(.ant-picker-cell-range-hover-edge-start-near-range):after,.ant-picker-cell-in-view.ant-picker-cell-range-hover-start:after{left:6px;border-left:1px dashed #7ec1ff;border-top-left-radius:4px;border-bottom-left-radius:4px}tr>.ant-picker-cell-in-view.ant-picker-cell-range-hover:last-child:after,tr>.ant-picker-cell-in-view.ant-picker-cell-range-hover-start:last-child:after,.ant-picker-cell-in-view.ant-picker-cell-end.ant-picker-cell-range-hover-edge-end.ant-picker-cell-range-hover-edge-end-near-range:after,.ant-picker-cell-in-view.ant-picker-cell-range-hover-edge-end:not(.ant-picker-cell-range-hover-edge-end-near-range):after,.ant-picker-cell-in-view.ant-picker-cell-range-hover-end:after{right:6px;border-right:1px dashed #7ec1ff;border-top-right-radius:4px;border-bottom-right-radius:4px}.ant-picker-cell-disabled{color:#00000040;pointer-events:none}.ant-picker-cell-disabled .ant-picker-cell-inner{background:transparent}.ant-picker-cell-disabled:before{background:rgba(0,0,0,.04)}.ant-picker-cell-disabled.ant-picker-cell-today .ant-picker-cell-inner:before{border-color:#00000040}.ant-picker-decade-panel .ant-picker-content,.ant-picker-year-panel .ant-picker-content,.ant-picker-quarter-panel .ant-picker-content,.ant-picker-month-panel .ant-picker-content{height:264px}.ant-picker-decade-panel .ant-picker-cell-inner,.ant-picker-year-panel .ant-picker-cell-inner,.ant-picker-quarter-panel .ant-picker-cell-inner,.ant-picker-month-panel .ant-picker-cell-inner{padding:0 8px}.ant-picker-quarter-panel .ant-picker-content{height:56px}.ant-picker-footer{width:min-content;min-width:100%;line-height:38px;text-align:center;border-bottom:1px solid transparent}.ant-picker-panel .ant-picker-footer{border-top:1px solid #f0f0f0}.ant-picker-footer-extra{padding:0 12px;line-height:38px;text-align:left}.ant-picker-footer-extra:not(:last-child){border-bottom:1px solid #f0f0f0}.ant-picker-now{text-align:left}.ant-picker-today-btn{color:#1890ff}.ant-picker-today-btn:hover{color:#40a9ff}.ant-picker-today-btn:active{color:#096dd9}.ant-picker-today-btn.ant-picker-today-btn-disabled{color:#00000040;cursor:not-allowed}.ant-picker-decade-panel .ant-picker-cell-inner{padding:0 4px}.ant-picker-decade-panel .ant-picker-cell:before{display:none}.ant-picker-year-panel .ant-picker-body,.ant-picker-quarter-panel .ant-picker-body,.ant-picker-month-panel .ant-picker-body{padding:0 8px}.ant-picker-year-panel .ant-picker-cell-inner,.ant-picker-quarter-panel .ant-picker-cell-inner,.ant-picker-month-panel .ant-picker-cell-inner{width:60px}.ant-picker-year-panel .ant-picker-cell-range-hover-start:after,.ant-picker-quarter-panel .ant-picker-cell-range-hover-start:after,.ant-picker-month-panel .ant-picker-cell-range-hover-start:after{left:14px;border-left:1px dashed #7ec1ff;border-radius:4px 0 0 4px}.ant-picker-panel-rtl .ant-picker-year-panel .ant-picker-cell-range-hover-start:after,.ant-picker-panel-rtl .ant-picker-quarter-panel .ant-picker-cell-range-hover-start:after,.ant-picker-panel-rtl .ant-picker-month-panel .ant-picker-cell-range-hover-start:after{right:14px;border-right:1px dashed #7ec1ff;border-radius:0 4px 4px 0}.ant-picker-year-panel .ant-picker-cell-range-hover-end:after,.ant-picker-quarter-panel .ant-picker-cell-range-hover-end:after,.ant-picker-month-panel .ant-picker-cell-range-hover-end:after{right:14px;border-right:1px dashed #7ec1ff;border-radius:0 4px 4px 0}.ant-picker-panel-rtl .ant-picker-year-panel .ant-picker-cell-range-hover-end:after,.ant-picker-panel-rtl .ant-picker-quarter-panel .ant-picker-cell-range-hover-end:after,.ant-picker-panel-rtl .ant-picker-month-panel .ant-picker-cell-range-hover-end:after{left:14px;border-left:1px dashed #7ec1ff;border-radius:4px 0 0 4px}.ant-picker-week-panel .ant-picker-body{padding:8px 12px}.ant-picker-week-panel .ant-picker-cell:hover .ant-picker-cell-inner,.ant-picker-week-panel .ant-picker-cell-selected .ant-picker-cell-inner,.ant-picker-week-panel .ant-picker-cell .ant-picker-cell-inner{background:transparent!important}.ant-picker-week-panel-row td{transition:background .3s}.ant-picker-week-panel-row:hover td{background:#f5f5f5}.ant-picker-week-panel-row-selected td,.ant-picker-week-panel-row-selected:hover td{background:#1890ff}.ant-picker-week-panel-row-selected td.ant-picker-cell-week,.ant-picker-week-panel-row-selected:hover td.ant-picker-cell-week{color:#ffffff80}.ant-picker-week-panel-row-selected td.ant-picker-cell-today .ant-picker-cell-inner:before,.ant-picker-week-panel-row-selected:hover td.ant-picker-cell-today .ant-picker-cell-inner:before{border-color:#fff}.ant-picker-week-panel-row-selected td .ant-picker-cell-inner,.ant-picker-week-panel-row-selected:hover td .ant-picker-cell-inner{color:#fff}.ant-picker-date-panel .ant-picker-body{padding:8px 12px}.ant-picker-date-panel .ant-picker-content{width:252px}.ant-picker-date-panel .ant-picker-content th{width:36px}.ant-picker-datetime-panel{display:flex}.ant-picker-datetime-panel .ant-picker-time-panel{border-left:1px solid #f0f0f0}.ant-picker-datetime-panel .ant-picker-date-panel,.ant-picker-datetime-panel .ant-picker-time-panel{transition:opacity .3s}.ant-picker-datetime-panel-active .ant-picker-date-panel,.ant-picker-datetime-panel-active .ant-picker-time-panel{opacity:.3}.ant-picker-datetime-panel-active .ant-picker-date-panel-active,.ant-picker-datetime-panel-active .ant-picker-time-panel-active{opacity:1}.ant-picker-time-panel{width:auto;min-width:auto}.ant-picker-time-panel .ant-picker-content{display:flex;flex:auto;height:224px}.ant-picker-time-panel-column{flex:1 0 auto;width:56px;margin:0;padding:0;overflow-y:hidden;text-align:left;list-style:none;transition:background .3s}.ant-picker-time-panel-column:after{display:block;height:196px;content:""}.ant-picker-datetime-panel .ant-picker-time-panel-column:after{height:198px}.ant-picker-time-panel-column:not(:first-child){border-left:1px solid #f0f0f0}.ant-picker-time-panel-column-active{background:rgba(230,247,255,.2)}.ant-picker-time-panel-column:hover{overflow-y:auto}.ant-picker-time-panel-column>li{margin:0;padding:0}.ant-picker-time-panel-column>li.ant-picker-time-panel-cell .ant-picker-time-panel-cell-inner{display:block;width:100%;height:28px;margin:0;padding:0 0 0 14px;color:#000000d9;line-height:28px;border-radius:0;cursor:pointer;transition:background .3s}.ant-picker-time-panel-column>li.ant-picker-time-panel-cell .ant-picker-time-panel-cell-inner:hover{background:#f5f5f5}.ant-picker-time-panel-column>li.ant-picker-time-panel-cell-selected .ant-picker-time-panel-cell-inner{background:#e6f7ff}.ant-picker-time-panel-column>li.ant-picker-time-panel-cell-disabled .ant-picker-time-panel-cell-inner{color:#00000040;background:transparent;cursor:not-allowed}_:-ms-fullscreen .ant-picker-range-wrapper .ant-picker-month-panel .ant-picker-cell,:root .ant-picker-range-wrapper .ant-picker-month-panel .ant-picker-cell,_:-ms-fullscreen .ant-picker-range-wrapper .ant-picker-year-panel .ant-picker-cell,:root .ant-picker-range-wrapper .ant-picker-year-panel .ant-picker-cell{padding:21px 0}.ant-picker-rtl{direction:rtl}.ant-picker-rtl .ant-picker-suffix{margin-right:4px;margin-left:0}.ant-picker-rtl .ant-picker-clear{right:auto;left:0}.ant-picker-rtl .ant-picker-separator{transform:rotate(180deg)}.ant-picker-panel-rtl .ant-picker-header-view button:not(:first-child){margin-right:8px;margin-left:0}.ant-picker-rtl.ant-picker-range .ant-picker-clear{right:auto;left:11px}.ant-picker-rtl.ant-picker-range .ant-picker-active-bar{margin-right:11px;margin-left:0}.ant-picker-rtl.ant-picker-range.ant-picker-small .ant-picker-active-bar{margin-right:7px}.ant-picker-dropdown-rtl .ant-picker-ranges{text-align:right}.ant-picker-dropdown-rtl .ant-picker-ranges .ant-picker-ok{float:left;margin-right:8px;margin-left:0}.ant-picker-panel-rtl{direction:rtl}.ant-picker-panel-rtl .ant-picker-prev-icon,.ant-picker-panel-rtl .ant-picker-super-prev-icon{transform:rotate(135deg)}.ant-picker-panel-rtl .ant-picker-next-icon,.ant-picker-panel-rtl .ant-picker-super-next-icon{transform:rotate(-45deg)}.ant-picker-cell .ant-picker-cell-inner{position:relative;z-index:2;display:inline-block;min-width:24px;height:24px;line-height:24px;border-radius:4px;transition:background .3s,border .3s}.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-start:before{right:50%;left:0}.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-end:before{right:0;left:50%}.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-start.ant-picker-cell-range-end:before{right:50%;left:50%}.ant-picker-panel-rtl .ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start .ant-picker-cell-inner:after{right:0;left:-6px}.ant-picker-panel-rtl .ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end .ant-picker-cell-inner:after{right:-6px;left:0}.ant-picker-panel-rtl .ant-picker-cell-range-hover.ant-picker-cell-range-start:after{right:0;left:50%}.ant-picker-panel-rtl .ant-picker-cell-range-hover.ant-picker-cell-range-end:after{right:50%;left:0}.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-start:not(.ant-picker-cell-range-start-single):not(.ant-picker-cell-range-end) .ant-picker-cell-inner{border-radius:0 4px 4px 0}.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-end:not(.ant-picker-cell-range-end-single):not(.ant-picker-cell-range-start) .ant-picker-cell-inner{border-radius:4px 0 0 4px}.ant-picker-panel-rtl tr>.ant-picker-cell-in-view.ant-picker-cell-range-hover:not(.ant-picker-cell-selected):first-child:after,.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-start.ant-picker-cell-range-hover-edge-start.ant-picker-cell-range-hover-edge-start-near-range:after,.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-hover-edge-start:not(.ant-picker-cell-range-hover-edge-start-near-range):after,.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-hover-start:after{right:6px;left:0;border-right:1px dashed #7ec1ff;border-left:none;border-radius:0 4px 4px 0}.ant-picker-panel-rtl tr>.ant-picker-cell-in-view.ant-picker-cell-range-hover:not(.ant-picker-cell-selected):last-child:after,.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-end.ant-picker-cell-range-hover-edge-end.ant-picker-cell-range-hover-edge-end-near-range:after,.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-hover-edge-end:not(.ant-picker-cell-range-hover-edge-end-near-range):after,.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-hover-end:after{right:0;left:6px;border-right:none;border-left:1px dashed #7ec1ff;border-radius:4px 0 0 4px}.ant-picker-panel-rtl tr>.ant-picker-cell-in-view.ant-picker-cell-range-hover-start:last-child:after,.ant-picker-panel-rtl tr>.ant-picker-cell-in-view.ant-picker-cell-range-hover-end:first-child:after,.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-start.ant-picker-cell-range-hover-edge-start:not(.ant-picker-cell-range-hover):after,.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-start.ant-picker-cell-range-hover-end.ant-picker-cell-range-hover-edge-start:not(.ant-picker-cell-range-hover):after,.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-end.ant-picker-cell-range-hover-start.ant-picker-cell-range-hover-edge-end:not(.ant-picker-cell-range-hover):after,.ant-picker-panel-rtl tr>.ant-picker-cell-in-view.ant-picker-cell-start.ant-picker-cell-range-hover.ant-picker-cell-range-hover-edge-start:last-child:after,.ant-picker-panel-rtl tr>.ant-picker-cell-in-view.ant-picker-cell-end.ant-picker-cell-range-hover.ant-picker-cell-range-hover-edge-end:first-child:after{right:6px;left:6px;border-right:1px dashed #7ec1ff;border-left:1px dashed #7ec1ff;border-radius:4px}.ant-picker-dropdown-rtl .ant-picker-footer-extra{direction:rtl;text-align:right}.ant-picker-panel-rtl .ant-picker-time-panel{direction:ltr}.ant-picker-inline{border:none;padding:0}.ant-picker-inline .ant-picker-range-arrow{display:none!important}.ant-picker-inline .ant-picker-dropdown{z-index:auto}.ant-picker-dropdown{top:unset;left:unset}.ant-picker-panel-container.ant-picker-week-number .ant-picker-date-panel .ant-picker-content{width:100%}.ant-picker-panel-container.ant-picker-week-number .ant-picker-date-panel .ant-picker-content th{width:inherit}.ant-picker-range-arrow{margin-right:16.5px}.ant-descriptions-header{display:flex;align-items:center;margin-bottom:20px}.ant-descriptions-title{flex:auto;overflow:hidden;color:#000000d9;font-weight:700;font-size:16px;line-height:1.5715;white-space:nowrap;text-overflow:ellipsis}.ant-descriptions-extra{margin-left:auto;color:#000000d9;font-size:14px}.ant-descriptions-view{width:100%;border-radius:4px}.ant-descriptions-view table{width:100%;table-layout:fixed}.ant-descriptions-row>th,.ant-descriptions-row>td{padding-bottom:16px}.ant-descriptions-row:last-child{border-bottom:none}.ant-descriptions-item-label{color:#000000d9;font-weight:400;font-size:14px;line-height:1.5715;text-align:start}.ant-descriptions-item-label:after{content:":";position:relative;top:-.5px;margin:0 8px 0 2px}.ant-descriptions-item-label.ant-descriptions-item-no-colon:after{content:" "}.ant-descriptions-item-no-label:after{margin:0;content:""}.ant-descriptions-item-content{display:table-cell;flex:1;color:#000000d9;font-size:14px;line-height:1.5715;word-break:break-word;overflow-wrap:break-word}.ant-descriptions-item{padding-bottom:0;vertical-align:top}.ant-descriptions-item-container{display:flex}.ant-descriptions-item-container .ant-descriptions-item-label,.ant-descriptions-item-container .ant-descriptions-item-content{display:inline-flex;align-items:baseline}.ant-descriptions-middle .ant-descriptions-row>th,.ant-descriptions-middle .ant-descriptions-row>td{padding-bottom:12px}.ant-descriptions-small .ant-descriptions-row>th,.ant-descriptions-small .ant-descriptions-row>td{padding-bottom:8px}.ant-descriptions-bordered .ant-descriptions-view{border:1px solid #f0f0f0}.ant-descriptions-bordered .ant-descriptions-view>table{table-layout:auto;border-collapse:collapse}.ant-descriptions-bordered .ant-descriptions-item-label,.ant-descriptions-bordered .ant-descriptions-item-content{padding:16px 24px;border-right:1px solid #f0f0f0}.ant-descriptions-bordered .ant-descriptions-item-label:last-child,.ant-descriptions-bordered .ant-descriptions-item-content:last-child{border-right:none}.ant-descriptions-bordered .ant-descriptions-item-label{background-color:#fafafa}.ant-descriptions-bordered .ant-descriptions-item-label:after{display:none}.ant-descriptions-bordered .ant-descriptions-row{border-bottom:1px solid #f0f0f0}.ant-descriptions-bordered .ant-descriptions-row:last-child{border-bottom:none}.ant-descriptions-bordered.ant-descriptions-middle .ant-descriptions-item-label,.ant-descriptions-bordered.ant-descriptions-middle .ant-descriptions-item-content{padding:12px 24px}.ant-descriptions-bordered.ant-descriptions-small .ant-descriptions-item-label,.ant-descriptions-bordered.ant-descriptions-small .ant-descriptions-item-content{padding:8px 16px}.ant-descriptions-rtl{direction:rtl}.ant-descriptions-rtl .ant-descriptions-item-label:after{margin:0 2px 0 8px}.ant-descriptions-rtl.ant-descriptions-bordered .ant-descriptions-item-label,.ant-descriptions-rtl.ant-descriptions-bordered .ant-descriptions-item-content{border-right:none;border-left:1px solid #f0f0f0}.ant-descriptions-rtl.ant-descriptions-bordered .ant-descriptions-item-label:last-child,.ant-descriptions-rtl.ant-descriptions-bordered .ant-descriptions-item-content:last-child{border-left:none}nz-descriptions{display:block}.ant-divider{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";border-top:1px solid rgba(0,0,0,.06)}.ant-divider-vertical{position:relative;top:-.06em;display:inline-block;height:.9em;margin:0 8px;vertical-align:middle;border-top:0;border-left:1px solid rgba(0,0,0,.06)}.ant-divider-horizontal{display:flex;clear:both;width:100%;min-width:100%;margin:24px 0}.ant-divider-horizontal.ant-divider-with-text{display:flex;margin:16px 0;color:#000000d9;font-weight:500;font-size:16px;white-space:nowrap;text-align:center;border-top:0;border-top-color:#0000000f}.ant-divider-horizontal.ant-divider-with-text:before,.ant-divider-horizontal.ant-divider-with-text:after{position:relative;top:50%;width:50%;border-top:1px solid transparent;border-top-color:inherit;border-bottom:0;transform:translateY(50%);content:""}.ant-divider-horizontal.ant-divider-with-text-left:before{top:50%;width:5%}.ant-divider-horizontal.ant-divider-with-text-left:after{top:50%;width:95%}.ant-divider-horizontal.ant-divider-with-text-right:before{top:50%;width:95%}.ant-divider-horizontal.ant-divider-with-text-right:after{top:50%;width:5%}.ant-divider-inner-text{display:inline-block;padding:0 1em}.ant-divider-dashed{background:none;border-color:#0000000f;border-style:dashed;border-width:1px 0 0}.ant-divider-horizontal.ant-divider-with-text.ant-divider-dashed:before,.ant-divider-horizontal.ant-divider-with-text.ant-divider-dashed:after{border-style:dashed none none}.ant-divider-vertical.ant-divider-dashed{border-width:0 0 0 1px}.ant-divider-plain.ant-divider-with-text{color:#000000d9;font-weight:400;font-size:14px}.ant-divider-horizontal.ant-divider-with-text-left.ant-divider-no-default-orientation-margin-left:before{width:0}.ant-divider-horizontal.ant-divider-with-text-left.ant-divider-no-default-orientation-margin-left:after{width:100%}.ant-divider-horizontal.ant-divider-with-text-left.ant-divider-no-default-orientation-margin-left .ant-divider-inner-text{padding-left:0}.ant-divider-horizontal.ant-divider-with-text-right.ant-divider-no-default-orientation-margin-right:before{width:100%}.ant-divider-horizontal.ant-divider-with-text-right.ant-divider-no-default-orientation-margin-right:after{width:0}.ant-divider-horizontal.ant-divider-with-text-right.ant-divider-no-default-orientation-margin-right .ant-divider-inner-text{padding-right:0}.ant-divider-rtl{direction:rtl}.ant-divider-rtl.ant-divider-horizontal.ant-divider-with-text-left:before{width:95%}.ant-divider-rtl.ant-divider-horizontal.ant-divider-with-text-left:after{width:5%}.ant-divider-rtl.ant-divider-horizontal.ant-divider-with-text-right:before{width:5%}.ant-divider-rtl.ant-divider-horizontal.ant-divider-with-text-right:after{width:95%}.ant-drawer{position:fixed;z-index:1000;width:0%;height:100%;transition:width 0s ease .3s,height 0s ease .3s}.ant-drawer-content-wrapper{position:absolute;width:100%;height:100%;transition:transform .3s cubic-bezier(.23,1,.32,1),box-shadow .3s cubic-bezier(.23,1,.32,1)}.ant-drawer .ant-drawer-content{width:100%;height:100%}.ant-drawer-left,.ant-drawer-right{top:0;width:0%;height:100%}.ant-drawer-left .ant-drawer-content-wrapper,.ant-drawer-right .ant-drawer-content-wrapper{height:100%}.ant-drawer-left.ant-drawer-open,.ant-drawer-right.ant-drawer-open{width:100%;transition:transform .3s cubic-bezier(.23,1,.32,1)}.ant-drawer-left,.ant-drawer-left .ant-drawer-content-wrapper{left:0}.ant-drawer-left.ant-drawer-open .ant-drawer-content-wrapper{box-shadow:6px 0 16px -8px #00000014,9px 0 28px #0000000d,12px 0 48px 16px #00000008}.ant-drawer-right,.ant-drawer-right .ant-drawer-content-wrapper{right:0}.ant-drawer-right.ant-drawer-open .ant-drawer-content-wrapper{box-shadow:-6px 0 16px -8px #00000014,-9px 0 28px #0000000d,-12px 0 48px 16px #00000008}.ant-drawer-right.ant-drawer-open.no-mask{right:1px;transform:translate(1px)}.ant-drawer-top,.ant-drawer-bottom{left:0;width:100%;height:0%}.ant-drawer-top .ant-drawer-content-wrapper,.ant-drawer-bottom .ant-drawer-content-wrapper{width:100%}.ant-drawer-top.ant-drawer-open,.ant-drawer-bottom.ant-drawer-open{height:100%;transition:transform .3s cubic-bezier(.23,1,.32,1)}.ant-drawer-top{top:0}.ant-drawer-top.ant-drawer-open .ant-drawer-content-wrapper{box-shadow:0 6px 16px -8px #00000014,0 9px 28px #0000000d,0 12px 48px 16px #00000008}.ant-drawer-bottom,.ant-drawer-bottom .ant-drawer-content-wrapper{bottom:0}.ant-drawer-bottom.ant-drawer-open .ant-drawer-content-wrapper{box-shadow:0 -6px 16px -8px #00000014,0 -9px 28px #0000000d,0 -12px 48px 16px #00000008}.ant-drawer-bottom.ant-drawer-open.no-mask{bottom:1px;transform:translateY(1px)}.ant-drawer.ant-drawer-open .ant-drawer-mask{height:100%;opacity:1;transition:none;animation:antdDrawerFadeIn .3s cubic-bezier(.23,1,.32,1);pointer-events:auto}.ant-drawer-title{flex:1;margin:0;color:#000000d9;font-weight:500;font-size:16px;line-height:22px}.ant-drawer-content{position:relative;z-index:1;overflow:auto;background-color:#fff;background-clip:padding-box;border:0}.ant-drawer-close{display:inline-block;margin-right:12px;color:#00000073;font-weight:700;font-size:16px;font-style:normal;line-height:1;text-align:center;text-transform:none;text-decoration:none;background:transparent;border:0;outline:0;cursor:pointer;transition:color .3s;text-rendering:auto}.ant-drawer-close:focus,.ant-drawer-close:hover{color:#000000bf;text-decoration:none}.ant-drawer-header{position:relative;display:flex;align-items:center;justify-content:space-between;padding:16px 24px;color:#000000d9;background:#fff;border-bottom:1px solid #f0f0f0;border-radius:4px 4px 0 0}.ant-drawer-header-title{display:flex;flex:1;align-items:center;justify-content:space-between}.ant-drawer-header-close-only{padding-bottom:0;border:none}.ant-drawer-wrapper-body{display:flex;flex-flow:column nowrap;width:100%;height:100%}.ant-drawer-body{flex-grow:1;padding:24px;overflow:auto;font-size:14px;line-height:1.5715;word-wrap:break-word}.ant-drawer-footer{flex-shrink:0;padding:10px 16px;border-top:1px solid #f0f0f0}.ant-drawer-mask{position:absolute;top:0;left:0;width:100%;height:0;background-color:#00000073;opacity:0;transition:opacity .3s linear,height 0s ease .3s;pointer-events:none}.ant-drawer .ant-picker-clear{background:#fff}@keyframes antdDrawerFadeIn{0%{opacity:0}to{opacity:1}}.ant-drawer-rtl{direction:rtl}.ant-drawer-rtl .ant-drawer-close{margin-right:0;margin-left:12px}.ant-dropdown-menu-item.ant-dropdown-menu-item-danger{color:#ff4d4f}.ant-dropdown-menu-item.ant-dropdown-menu-item-danger:hover{color:#fff;background-color:#ff4d4f}.ant-dropdown{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;top:-9999px;left:-9999px;z-index:1050;display:block}.ant-dropdown:before{position:absolute;inset:-4px 0 -4px -7px;z-index:-9999;opacity:.0001;content:" "}.ant-dropdown-wrap{position:relative}.ant-dropdown-wrap .ant-btn>.anticon-down{font-size:10px}.ant-dropdown-wrap .anticon-down:before{transition:transform .2s}.ant-dropdown-wrap-open .anticon-down:before{transform:rotate(180deg)}.ant-dropdown-hidden,.ant-dropdown-menu-hidden,.ant-dropdown-menu-submenu-hidden{display:none}.ant-dropdown-show-arrow.ant-dropdown-placement-topLeft,.ant-dropdown-show-arrow.ant-dropdown-placement-top,.ant-dropdown-show-arrow.ant-dropdown-placement-topRight{padding-bottom:15.3137085px}.ant-dropdown-show-arrow.ant-dropdown-placement-bottomLeft,.ant-dropdown-show-arrow.ant-dropdown-placement-bottom,.ant-dropdown-show-arrow.ant-dropdown-placement-bottomRight{padding-top:15.3137085px}.ant-dropdown-arrow{position:absolute;z-index:1;display:block;width:11.3137085px;height:11.3137085px;background:linear-gradient(135deg,transparent 40%,#fff 40%);border-radius:0 0 2px;pointer-events:none}.ant-dropdown-arrow:before{position:absolute;top:-11.3137085px;left:-11.3137085px;width:33.9411255px;height:33.9411255px;background:#fff;background-repeat:no-repeat;background-position:-10px -10px;content:"";clip-path:path("M 9.849242404917499 24.091883092036785 A 5 5 0 0 1 13.384776310850237 22.627416997969522 L 20.627416997969522 22.627416997969522 A 2 2 0 0 0 22.627416997969522 20.627416997969522 L 22.627416997969522 13.384776310850237 A 5 5 0 0 1 24.091883092036785 9.849242404917499 L 23.091883092036785 9.849242404917499 L 9.849242404917499 23.091883092036785 Z")}.ant-dropdown-placement-top>.ant-dropdown-arrow,.ant-dropdown-placement-topLeft>.ant-dropdown-arrow,.ant-dropdown-placement-topRight>.ant-dropdown-arrow{bottom:10px;box-shadow:3px 3px 7px -3px #0000001a;transform:rotate(45deg)}.ant-dropdown-placement-top>.ant-dropdown-arrow{left:50%;transform:translate(-50%) rotate(45deg)}.ant-dropdown-placement-topLeft>.ant-dropdown-arrow{left:16px}.ant-dropdown-placement-topRight>.ant-dropdown-arrow{right:16px}.ant-dropdown-placement-bottom>.ant-dropdown-arrow,.ant-dropdown-placement-bottomLeft>.ant-dropdown-arrow,.ant-dropdown-placement-bottomRight>.ant-dropdown-arrow{top:9.41421356px;box-shadow:2px 2px 5px -2px #0000001a;transform:rotate(-135deg) translateY(-.5px)}.ant-dropdown-placement-bottom>.ant-dropdown-arrow{left:50%;transform:translate(-50%) rotate(-135deg) translateY(-.5px)}.ant-dropdown-placement-bottomLeft>.ant-dropdown-arrow{left:16px}.ant-dropdown-placement-bottomRight>.ant-dropdown-arrow{right:16px}.ant-dropdown-menu{position:relative;margin:0;padding:4px 0;text-align:left;list-style-type:none;background-color:#fff;background-clip:padding-box;border-radius:4px;outline:none;box-shadow:0 3px 6px -4px #0000001f,0 6px 16px #00000014,0 9px 28px 8px #0000000d}.ant-dropdown-menu-item-group-title{padding:5px 12px;color:#00000073;transition:all .3s}.ant-dropdown-menu-submenu-popup{position:absolute;z-index:1050;background:transparent;box-shadow:none;transform-origin:0 0}.ant-dropdown-menu-submenu-popup ul,.ant-dropdown-menu-submenu-popup li{list-style:none}.ant-dropdown-menu-submenu-popup ul{margin-right:.3em;margin-left:.3em}.ant-dropdown-menu-item{position:relative;display:flex;align-items:center}.ant-dropdown-menu-item-icon{min-width:12px;margin-right:8px;font-size:12px}.ant-dropdown-menu-title-content{flex:auto}.ant-dropdown-menu-title-content>a{color:inherit;transition:all .3s}.ant-dropdown-menu-title-content>a:hover{color:inherit}.ant-dropdown-menu-title-content>a:after{position:absolute;inset:0;content:""}.ant-dropdown-menu-item,.ant-dropdown-menu-submenu-title{clear:both;margin:0;padding:5px 12px;color:#000000d9;font-weight:400;font-size:14px;line-height:22px;cursor:pointer;transition:all .3s}.ant-dropdown-menu-item-selected,.ant-dropdown-menu-submenu-title-selected{color:#1890ff;background-color:#e6f7ff}.ant-dropdown-menu-item:hover,.ant-dropdown-menu-submenu-title:hover,.ant-dropdown-menu-item.ant-dropdown-menu-item-active,.ant-dropdown-menu-item.ant-dropdown-menu-submenu-title-active,.ant-dropdown-menu-submenu-title.ant-dropdown-menu-item-active,.ant-dropdown-menu-submenu-title.ant-dropdown-menu-submenu-title-active{background-color:#f5f5f5}.ant-dropdown-menu-item-disabled,.ant-dropdown-menu-submenu-title-disabled{color:#00000040;cursor:not-allowed}.ant-dropdown-menu-item-disabled:hover,.ant-dropdown-menu-submenu-title-disabled:hover{color:#00000040;background-color:#fff;cursor:not-allowed}.ant-dropdown-menu-item-disabled a,.ant-dropdown-menu-submenu-title-disabled a{pointer-events:none}.ant-dropdown-menu-item-divider,.ant-dropdown-menu-submenu-title-divider{height:1px;margin:4px 0;overflow:hidden;line-height:0;background-color:#f0f0f0}.ant-dropdown-menu-item .ant-dropdown-menu-submenu-expand-icon,.ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-expand-icon{position:absolute;right:8px}.ant-dropdown-menu-item .ant-dropdown-menu-submenu-expand-icon .ant-dropdown-menu-submenu-arrow-icon,.ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-expand-icon .ant-dropdown-menu-submenu-arrow-icon{margin-right:0!important;color:#00000073;font-size:10px;font-style:normal}.ant-dropdown-menu-item-group-list{margin:0 8px;padding:0;list-style:none}.ant-dropdown-menu-submenu-title{padding-right:24px}.ant-dropdown-menu-submenu-vertical{position:relative}.ant-dropdown-menu-submenu-vertical>.ant-dropdown-menu{position:absolute;top:0;left:100%;min-width:100%;margin-left:4px;transform-origin:0 0}.ant-dropdown-menu-submenu.ant-dropdown-menu-submenu-disabled .ant-dropdown-menu-submenu-title,.ant-dropdown-menu-submenu.ant-dropdown-menu-submenu-disabled .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow-icon{color:#00000040;background-color:#fff;cursor:not-allowed}.ant-dropdown-menu-submenu-selected .ant-dropdown-menu-submenu-title{color:#1890ff}.ant-dropdown.ant-slide-down-enter.ant-slide-down-enter-active.ant-dropdown-placement-bottomLeft,.ant-dropdown.ant-slide-down-appear.ant-slide-down-appear-active.ant-dropdown-placement-bottomLeft,.ant-dropdown.ant-slide-down-enter.ant-slide-down-enter-active.ant-dropdown-placement-bottom,.ant-dropdown.ant-slide-down-appear.ant-slide-down-appear-active.ant-dropdown-placement-bottom,.ant-dropdown.ant-slide-down-enter.ant-slide-down-enter-active.ant-dropdown-placement-bottomRight,.ant-dropdown.ant-slide-down-appear.ant-slide-down-appear-active.ant-dropdown-placement-bottomRight{animation-name:antSlideUpIn}.ant-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-dropdown-placement-topLeft,.ant-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-dropdown-placement-topLeft,.ant-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-dropdown-placement-top,.ant-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-dropdown-placement-top,.ant-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-dropdown-placement-topRight,.ant-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-dropdown-placement-topRight{animation-name:antSlideDownIn}.ant-dropdown.ant-slide-down-leave.ant-slide-down-leave-active.ant-dropdown-placement-bottomLeft,.ant-dropdown.ant-slide-down-leave.ant-slide-down-leave-active.ant-dropdown-placement-bottom,.ant-dropdown.ant-slide-down-leave.ant-slide-down-leave-active.ant-dropdown-placement-bottomRight{animation-name:antSlideUpOut}.ant-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-dropdown-placement-topLeft,.ant-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-dropdown-placement-top,.ant-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-dropdown-placement-topRight{animation-name:antSlideDownOut}.ant-dropdown-trigger>.anticon.anticon-down,.ant-dropdown-link>.anticon.anticon-down,.ant-dropdown-button>.anticon.anticon-down{font-size:10px;vertical-align:baseline}.ant-dropdown-button{white-space:nowrap}.ant-dropdown-button.ant-btn-group>.ant-btn-loading,.ant-dropdown-button.ant-btn-group>.ant-btn-loading+.ant-btn{cursor:default;pointer-events:none}.ant-dropdown-button.ant-btn-group>.ant-btn-loading+.ant-btn:before{display:block}.ant-dropdown-button.ant-btn-group>.ant-btn:last-child:not(:first-child):not(.ant-btn-icon-only){padding-right:8px;padding-left:8px}.ant-dropdown-menu-dark,.ant-dropdown-menu-dark .ant-dropdown-menu{background:#001529}.ant-dropdown-menu-dark .ant-dropdown-menu-item,.ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title,.ant-dropdown-menu-dark .ant-dropdown-menu-item>a,.ant-dropdown-menu-dark .ant-dropdown-menu-item>.anticon+span>a{color:#ffffffa6}.ant-dropdown-menu-dark .ant-dropdown-menu-item .ant-dropdown-menu-submenu-arrow:after,.ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow:after,.ant-dropdown-menu-dark .ant-dropdown-menu-item>a .ant-dropdown-menu-submenu-arrow:after,.ant-dropdown-menu-dark .ant-dropdown-menu-item>.anticon+span>a .ant-dropdown-menu-submenu-arrow:after{color:#ffffffa6}.ant-dropdown-menu-dark .ant-dropdown-menu-item:hover,.ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title:hover,.ant-dropdown-menu-dark .ant-dropdown-menu-item>a:hover,.ant-dropdown-menu-dark .ant-dropdown-menu-item>.anticon+span>a:hover{color:#fff;background:transparent}.ant-dropdown-menu-dark .ant-dropdown-menu-item-selected,.ant-dropdown-menu-dark .ant-dropdown-menu-item-selected:hover,.ant-dropdown-menu-dark .ant-dropdown-menu-item-selected>a{color:#fff;background:#1890ff}.ant-dropdown-rtl{direction:rtl}.ant-dropdown-rtl.ant-dropdown:before{right:-7px;left:0}.ant-dropdown-menu.ant-dropdown-menu-rtl,.ant-dropdown-rtl .ant-dropdown-menu-item-group-title,.ant-dropdown-menu-submenu-rtl .ant-dropdown-menu-item-group-title{direction:rtl;text-align:right}.ant-dropdown-menu-submenu-popup.ant-dropdown-menu-submenu-rtl{transform-origin:100% 0}.ant-dropdown-rtl .ant-dropdown-menu-submenu-popup ul,.ant-dropdown-rtl .ant-dropdown-menu-submenu-popup li,.ant-dropdown-rtl .ant-dropdown-menu-item,.ant-dropdown-rtl .ant-dropdown-menu-submenu-title{text-align:right}.ant-dropdown-rtl .ant-dropdown-menu-item>.anticon:first-child,.ant-dropdown-rtl .ant-dropdown-menu-submenu-title>.anticon:first-child,.ant-dropdown-rtl .ant-dropdown-menu-item>span>.anticon:first-child,.ant-dropdown-rtl .ant-dropdown-menu-submenu-title>span>.anticon:first-child{margin-right:0;margin-left:8px}.ant-dropdown-rtl .ant-dropdown-menu-item .ant-dropdown-menu-submenu-expand-icon,.ant-dropdown-rtl .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-expand-icon{right:auto;left:8px}.ant-dropdown-rtl .ant-dropdown-menu-item .ant-dropdown-menu-submenu-expand-icon .ant-dropdown-menu-submenu-arrow-icon,.ant-dropdown-rtl .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-expand-icon .ant-dropdown-menu-submenu-arrow-icon{margin-left:0!important;transform:scaleX(-1)}.ant-dropdown-rtl .ant-dropdown-menu-submenu-title{padding-right:12px;padding-left:24px}.ant-dropdown-rtl .ant-dropdown-menu-submenu-vertical>.ant-dropdown-menu{right:100%;left:0;margin-right:4px;margin-left:0}.ant-dropdown-menu>ul{list-style:inherit;margin:0;padding:0}.ant-dropdown{top:0;left:0;position:relative;width:100%;margin-top:6px;margin-bottom:6px}.ant-dropdown-rtl .ant-dropdown-menu-item .ant-dropdown-menu-submenu-arrow,.ant-dropdown-rtl .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow{transform:rotate(180deg)}.ant-empty{margin:0 8px;font-size:14px;line-height:1.5715;text-align:center}.ant-empty-image{height:100px;margin-bottom:8px}.ant-empty-image img{height:100%}.ant-empty-image svg{height:100%;margin:auto}.ant-empty-footer{margin-top:16px}.ant-empty-normal{margin:32px 0;color:#00000040}.ant-empty-normal .ant-empty-image{height:40px}.ant-empty-small{margin:8px 0;color:#00000040}.ant-empty-small .ant-empty-image{height:35px}.ant-empty-img-default-ellipse{fill:#f5f5f5;fill-opacity:.8}.ant-empty-img-default-path-1{fill:#aeb8c2}.ant-empty-img-default-path-2{fill:url(#linearGradient-1)}.ant-empty-img-default-path-3{fill:#f5f5f7}.ant-empty-img-default-path-4,.ant-empty-img-default-path-5{fill:#dce0e6}.ant-empty-img-default-g{fill:#fff}.ant-empty-img-simple-ellipse{fill:#f5f5f5}.ant-empty-img-simple-g{stroke:#d9d9d9}.ant-empty-img-simple-path{fill:#fafafa}.ant-empty-rtl{direction:rtl}nz-empty{display:block}.ant-row{display:flex;flex-flow:row wrap}.ant-row:before,.ant-row:after{display:flex}.ant-row-no-wrap{flex-wrap:nowrap}.ant-row-start{justify-content:flex-start}.ant-row-center{justify-content:center}.ant-row-end{justify-content:flex-end}.ant-row-space-between{justify-content:space-between}.ant-row-space-around{justify-content:space-around}.ant-row-space-evenly{justify-content:space-evenly}.ant-row-top{align-items:flex-start}.ant-row-middle{align-items:center}.ant-row-bottom{align-items:flex-end}.ant-col{position:relative;max-width:100%;min-height:1px}.ant-col-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-push-24{left:100%}.ant-col-pull-24{right:100%}.ant-col-offset-24{margin-left:100%}.ant-col-order-24{order:24}.ant-col-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-push-23{left:95.83333333%}.ant-col-pull-23{right:95.83333333%}.ant-col-offset-23{margin-left:95.83333333%}.ant-col-order-23{order:23}.ant-col-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-push-22{left:91.66666667%}.ant-col-pull-22{right:91.66666667%}.ant-col-offset-22{margin-left:91.66666667%}.ant-col-order-22{order:22}.ant-col-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-push-21{left:87.5%}.ant-col-pull-21{right:87.5%}.ant-col-offset-21{margin-left:87.5%}.ant-col-order-21{order:21}.ant-col-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-push-20{left:83.33333333%}.ant-col-pull-20{right:83.33333333%}.ant-col-offset-20{margin-left:83.33333333%}.ant-col-order-20{order:20}.ant-col-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-push-19{left:79.16666667%}.ant-col-pull-19{right:79.16666667%}.ant-col-offset-19{margin-left:79.16666667%}.ant-col-order-19{order:19}.ant-col-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-push-18{left:75%}.ant-col-pull-18{right:75%}.ant-col-offset-18{margin-left:75%}.ant-col-order-18{order:18}.ant-col-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-push-17{left:70.83333333%}.ant-col-pull-17{right:70.83333333%}.ant-col-offset-17{margin-left:70.83333333%}.ant-col-order-17{order:17}.ant-col-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-push-16{left:66.66666667%}.ant-col-pull-16{right:66.66666667%}.ant-col-offset-16{margin-left:66.66666667%}.ant-col-order-16{order:16}.ant-col-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-push-15{left:62.5%}.ant-col-pull-15{right:62.5%}.ant-col-offset-15{margin-left:62.5%}.ant-col-order-15{order:15}.ant-col-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-push-14{left:58.33333333%}.ant-col-pull-14{right:58.33333333%}.ant-col-offset-14{margin-left:58.33333333%}.ant-col-order-14{order:14}.ant-col-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-push-13{left:54.16666667%}.ant-col-pull-13{right:54.16666667%}.ant-col-offset-13{margin-left:54.16666667%}.ant-col-order-13{order:13}.ant-col-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-push-12{left:50%}.ant-col-pull-12{right:50%}.ant-col-offset-12{margin-left:50%}.ant-col-order-12{order:12}.ant-col-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-push-11{left:45.83333333%}.ant-col-pull-11{right:45.83333333%}.ant-col-offset-11{margin-left:45.83333333%}.ant-col-order-11{order:11}.ant-col-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-push-10{left:41.66666667%}.ant-col-pull-10{right:41.66666667%}.ant-col-offset-10{margin-left:41.66666667%}.ant-col-order-10{order:10}.ant-col-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-push-9{left:37.5%}.ant-col-pull-9{right:37.5%}.ant-col-offset-9{margin-left:37.5%}.ant-col-order-9{order:9}.ant-col-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-push-8{left:33.33333333%}.ant-col-pull-8{right:33.33333333%}.ant-col-offset-8{margin-left:33.33333333%}.ant-col-order-8{order:8}.ant-col-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-push-7{left:29.16666667%}.ant-col-pull-7{right:29.16666667%}.ant-col-offset-7{margin-left:29.16666667%}.ant-col-order-7{order:7}.ant-col-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-push-6{left:25%}.ant-col-pull-6{right:25%}.ant-col-offset-6{margin-left:25%}.ant-col-order-6{order:6}.ant-col-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-push-5{left:20.83333333%}.ant-col-pull-5{right:20.83333333%}.ant-col-offset-5{margin-left:20.83333333%}.ant-col-order-5{order:5}.ant-col-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-push-4{left:16.66666667%}.ant-col-pull-4{right:16.66666667%}.ant-col-offset-4{margin-left:16.66666667%}.ant-col-order-4{order:4}.ant-col-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-push-3{left:12.5%}.ant-col-pull-3{right:12.5%}.ant-col-offset-3{margin-left:12.5%}.ant-col-order-3{order:3}.ant-col-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-push-2{left:8.33333333%}.ant-col-pull-2{right:8.33333333%}.ant-col-offset-2{margin-left:8.33333333%}.ant-col-order-2{order:2}.ant-col-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-push-1{left:4.16666667%}.ant-col-pull-1{right:4.16666667%}.ant-col-offset-1{margin-left:4.16666667%}.ant-col-order-1{order:1}.ant-col-0{display:none}.ant-col-offset-0{margin-left:0}.ant-col-order-0{order:0}.ant-col-offset-0.ant-col-rtl{margin-right:0}.ant-col-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}.ant-col-xs-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-xs-push-24{left:100%}.ant-col-xs-pull-24{right:100%}.ant-col-xs-offset-24{margin-left:100%}.ant-col-xs-order-24{order:24}.ant-col-xs-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-xs-push-23{left:95.83333333%}.ant-col-xs-pull-23{right:95.83333333%}.ant-col-xs-offset-23{margin-left:95.83333333%}.ant-col-xs-order-23{order:23}.ant-col-xs-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-xs-push-22{left:91.66666667%}.ant-col-xs-pull-22{right:91.66666667%}.ant-col-xs-offset-22{margin-left:91.66666667%}.ant-col-xs-order-22{order:22}.ant-col-xs-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-xs-push-21{left:87.5%}.ant-col-xs-pull-21{right:87.5%}.ant-col-xs-offset-21{margin-left:87.5%}.ant-col-xs-order-21{order:21}.ant-col-xs-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-xs-push-20{left:83.33333333%}.ant-col-xs-pull-20{right:83.33333333%}.ant-col-xs-offset-20{margin-left:83.33333333%}.ant-col-xs-order-20{order:20}.ant-col-xs-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-xs-push-19{left:79.16666667%}.ant-col-xs-pull-19{right:79.16666667%}.ant-col-xs-offset-19{margin-left:79.16666667%}.ant-col-xs-order-19{order:19}.ant-col-xs-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-xs-push-18{left:75%}.ant-col-xs-pull-18{right:75%}.ant-col-xs-offset-18{margin-left:75%}.ant-col-xs-order-18{order:18}.ant-col-xs-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-xs-push-17{left:70.83333333%}.ant-col-xs-pull-17{right:70.83333333%}.ant-col-xs-offset-17{margin-left:70.83333333%}.ant-col-xs-order-17{order:17}.ant-col-xs-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-xs-push-16{left:66.66666667%}.ant-col-xs-pull-16{right:66.66666667%}.ant-col-xs-offset-16{margin-left:66.66666667%}.ant-col-xs-order-16{order:16}.ant-col-xs-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-xs-push-15{left:62.5%}.ant-col-xs-pull-15{right:62.5%}.ant-col-xs-offset-15{margin-left:62.5%}.ant-col-xs-order-15{order:15}.ant-col-xs-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-xs-push-14{left:58.33333333%}.ant-col-xs-pull-14{right:58.33333333%}.ant-col-xs-offset-14{margin-left:58.33333333%}.ant-col-xs-order-14{order:14}.ant-col-xs-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-xs-push-13{left:54.16666667%}.ant-col-xs-pull-13{right:54.16666667%}.ant-col-xs-offset-13{margin-left:54.16666667%}.ant-col-xs-order-13{order:13}.ant-col-xs-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-xs-push-12{left:50%}.ant-col-xs-pull-12{right:50%}.ant-col-xs-offset-12{margin-left:50%}.ant-col-xs-order-12{order:12}.ant-col-xs-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-xs-push-11{left:45.83333333%}.ant-col-xs-pull-11{right:45.83333333%}.ant-col-xs-offset-11{margin-left:45.83333333%}.ant-col-xs-order-11{order:11}.ant-col-xs-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-xs-push-10{left:41.66666667%}.ant-col-xs-pull-10{right:41.66666667%}.ant-col-xs-offset-10{margin-left:41.66666667%}.ant-col-xs-order-10{order:10}.ant-col-xs-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-xs-push-9{left:37.5%}.ant-col-xs-pull-9{right:37.5%}.ant-col-xs-offset-9{margin-left:37.5%}.ant-col-xs-order-9{order:9}.ant-col-xs-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-xs-push-8{left:33.33333333%}.ant-col-xs-pull-8{right:33.33333333%}.ant-col-xs-offset-8{margin-left:33.33333333%}.ant-col-xs-order-8{order:8}.ant-col-xs-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-xs-push-7{left:29.16666667%}.ant-col-xs-pull-7{right:29.16666667%}.ant-col-xs-offset-7{margin-left:29.16666667%}.ant-col-xs-order-7{order:7}.ant-col-xs-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-xs-push-6{left:25%}.ant-col-xs-pull-6{right:25%}.ant-col-xs-offset-6{margin-left:25%}.ant-col-xs-order-6{order:6}.ant-col-xs-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-xs-push-5{left:20.83333333%}.ant-col-xs-pull-5{right:20.83333333%}.ant-col-xs-offset-5{margin-left:20.83333333%}.ant-col-xs-order-5{order:5}.ant-col-xs-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-xs-push-4{left:16.66666667%}.ant-col-xs-pull-4{right:16.66666667%}.ant-col-xs-offset-4{margin-left:16.66666667%}.ant-col-xs-order-4{order:4}.ant-col-xs-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-xs-push-3{left:12.5%}.ant-col-xs-pull-3{right:12.5%}.ant-col-xs-offset-3{margin-left:12.5%}.ant-col-xs-order-3{order:3}.ant-col-xs-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-xs-push-2{left:8.33333333%}.ant-col-xs-pull-2{right:8.33333333%}.ant-col-xs-offset-2{margin-left:8.33333333%}.ant-col-xs-order-2{order:2}.ant-col-xs-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-xs-push-1{left:4.16666667%}.ant-col-xs-pull-1{right:4.16666667%}.ant-col-xs-offset-1{margin-left:4.16666667%}.ant-col-xs-order-1{order:1}.ant-col-xs-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-xs-push-0{left:auto}.ant-col-xs-pull-0{right:auto}.ant-col-xs-offset-0{margin-left:0}.ant-col-xs-order-0{order:0}.ant-col-push-0.ant-col-rtl{right:auto}.ant-col-pull-0.ant-col-rtl{left:auto}.ant-col-xs-push-0.ant-col-rtl{right:auto}.ant-col-xs-pull-0.ant-col-rtl{left:auto}.ant-col-xs-offset-0.ant-col-rtl{margin-right:0}.ant-col-xs-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-xs-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-xs-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-xs-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-xs-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-xs-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-xs-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-xs-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-xs-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-xs-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-xs-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-xs-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-xs-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-xs-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-xs-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-xs-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-xs-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-xs-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-xs-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-xs-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-xs-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-xs-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-xs-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-xs-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-xs-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-xs-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-xs-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-xs-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-xs-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-xs-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-xs-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-xs-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-xs-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-xs-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-xs-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-xs-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-xs-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-xs-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-xs-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-xs-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-xs-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-xs-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-xs-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-xs-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-xs-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-xs-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-xs-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-xs-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-xs-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-xs-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-xs-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-xs-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-xs-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-xs-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-xs-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-xs-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-xs-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-xs-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-xs-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-xs-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-xs-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-xs-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-xs-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-xs-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-xs-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-xs-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-xs-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-xs-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-xs-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-xs-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-xs-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-xs-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}@media (min-width: 576px){.ant-col-sm-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-sm-push-24{left:100%}.ant-col-sm-pull-24{right:100%}.ant-col-sm-offset-24{margin-left:100%}.ant-col-sm-order-24{order:24}.ant-col-sm-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-sm-push-23{left:95.83333333%}.ant-col-sm-pull-23{right:95.83333333%}.ant-col-sm-offset-23{margin-left:95.83333333%}.ant-col-sm-order-23{order:23}.ant-col-sm-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-sm-push-22{left:91.66666667%}.ant-col-sm-pull-22{right:91.66666667%}.ant-col-sm-offset-22{margin-left:91.66666667%}.ant-col-sm-order-22{order:22}.ant-col-sm-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-sm-push-21{left:87.5%}.ant-col-sm-pull-21{right:87.5%}.ant-col-sm-offset-21{margin-left:87.5%}.ant-col-sm-order-21{order:21}.ant-col-sm-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-sm-push-20{left:83.33333333%}.ant-col-sm-pull-20{right:83.33333333%}.ant-col-sm-offset-20{margin-left:83.33333333%}.ant-col-sm-order-20{order:20}.ant-col-sm-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-sm-push-19{left:79.16666667%}.ant-col-sm-pull-19{right:79.16666667%}.ant-col-sm-offset-19{margin-left:79.16666667%}.ant-col-sm-order-19{order:19}.ant-col-sm-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-sm-push-18{left:75%}.ant-col-sm-pull-18{right:75%}.ant-col-sm-offset-18{margin-left:75%}.ant-col-sm-order-18{order:18}.ant-col-sm-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-sm-push-17{left:70.83333333%}.ant-col-sm-pull-17{right:70.83333333%}.ant-col-sm-offset-17{margin-left:70.83333333%}.ant-col-sm-order-17{order:17}.ant-col-sm-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-sm-push-16{left:66.66666667%}.ant-col-sm-pull-16{right:66.66666667%}.ant-col-sm-offset-16{margin-left:66.66666667%}.ant-col-sm-order-16{order:16}.ant-col-sm-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-sm-push-15{left:62.5%}.ant-col-sm-pull-15{right:62.5%}.ant-col-sm-offset-15{margin-left:62.5%}.ant-col-sm-order-15{order:15}.ant-col-sm-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-sm-push-14{left:58.33333333%}.ant-col-sm-pull-14{right:58.33333333%}.ant-col-sm-offset-14{margin-left:58.33333333%}.ant-col-sm-order-14{order:14}.ant-col-sm-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-sm-push-13{left:54.16666667%}.ant-col-sm-pull-13{right:54.16666667%}.ant-col-sm-offset-13{margin-left:54.16666667%}.ant-col-sm-order-13{order:13}.ant-col-sm-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-sm-push-12{left:50%}.ant-col-sm-pull-12{right:50%}.ant-col-sm-offset-12{margin-left:50%}.ant-col-sm-order-12{order:12}.ant-col-sm-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-sm-push-11{left:45.83333333%}.ant-col-sm-pull-11{right:45.83333333%}.ant-col-sm-offset-11{margin-left:45.83333333%}.ant-col-sm-order-11{order:11}.ant-col-sm-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-sm-push-10{left:41.66666667%}.ant-col-sm-pull-10{right:41.66666667%}.ant-col-sm-offset-10{margin-left:41.66666667%}.ant-col-sm-order-10{order:10}.ant-col-sm-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-sm-push-9{left:37.5%}.ant-col-sm-pull-9{right:37.5%}.ant-col-sm-offset-9{margin-left:37.5%}.ant-col-sm-order-9{order:9}.ant-col-sm-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-sm-push-8{left:33.33333333%}.ant-col-sm-pull-8{right:33.33333333%}.ant-col-sm-offset-8{margin-left:33.33333333%}.ant-col-sm-order-8{order:8}.ant-col-sm-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-sm-push-7{left:29.16666667%}.ant-col-sm-pull-7{right:29.16666667%}.ant-col-sm-offset-7{margin-left:29.16666667%}.ant-col-sm-order-7{order:7}.ant-col-sm-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-sm-push-6{left:25%}.ant-col-sm-pull-6{right:25%}.ant-col-sm-offset-6{margin-left:25%}.ant-col-sm-order-6{order:6}.ant-col-sm-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-sm-push-5{left:20.83333333%}.ant-col-sm-pull-5{right:20.83333333%}.ant-col-sm-offset-5{margin-left:20.83333333%}.ant-col-sm-order-5{order:5}.ant-col-sm-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-sm-push-4{left:16.66666667%}.ant-col-sm-pull-4{right:16.66666667%}.ant-col-sm-offset-4{margin-left:16.66666667%}.ant-col-sm-order-4{order:4}.ant-col-sm-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-sm-push-3{left:12.5%}.ant-col-sm-pull-3{right:12.5%}.ant-col-sm-offset-3{margin-left:12.5%}.ant-col-sm-order-3{order:3}.ant-col-sm-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-sm-push-2{left:8.33333333%}.ant-col-sm-pull-2{right:8.33333333%}.ant-col-sm-offset-2{margin-left:8.33333333%}.ant-col-sm-order-2{order:2}.ant-col-sm-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-sm-push-1{left:4.16666667%}.ant-col-sm-pull-1{right:4.16666667%}.ant-col-sm-offset-1{margin-left:4.16666667%}.ant-col-sm-order-1{order:1}.ant-col-sm-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-sm-push-0{left:auto}.ant-col-sm-pull-0{right:auto}.ant-col-sm-offset-0{margin-left:0}.ant-col-sm-order-0{order:0}.ant-col-push-0.ant-col-rtl{right:auto}.ant-col-pull-0.ant-col-rtl{left:auto}.ant-col-sm-push-0.ant-col-rtl{right:auto}.ant-col-sm-pull-0.ant-col-rtl{left:auto}.ant-col-sm-offset-0.ant-col-rtl{margin-right:0}.ant-col-sm-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-sm-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-sm-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-sm-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-sm-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-sm-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-sm-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-sm-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-sm-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-sm-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-sm-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-sm-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-sm-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-sm-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-sm-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-sm-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-sm-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-sm-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-sm-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-sm-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-sm-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-sm-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-sm-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-sm-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-sm-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-sm-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-sm-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-sm-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-sm-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-sm-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-sm-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-sm-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-sm-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-sm-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-sm-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-sm-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-sm-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-sm-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-sm-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-sm-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-sm-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-sm-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-sm-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-sm-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-sm-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-sm-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-sm-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-sm-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-sm-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-sm-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-sm-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-sm-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-sm-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-sm-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-sm-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-sm-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-sm-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-sm-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-sm-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-sm-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-sm-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-sm-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-sm-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-sm-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-sm-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-sm-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-sm-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-sm-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-sm-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-sm-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-sm-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-sm-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}}@media (min-width: 768px){.ant-col-md-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-md-push-24{left:100%}.ant-col-md-pull-24{right:100%}.ant-col-md-offset-24{margin-left:100%}.ant-col-md-order-24{order:24}.ant-col-md-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-md-push-23{left:95.83333333%}.ant-col-md-pull-23{right:95.83333333%}.ant-col-md-offset-23{margin-left:95.83333333%}.ant-col-md-order-23{order:23}.ant-col-md-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-md-push-22{left:91.66666667%}.ant-col-md-pull-22{right:91.66666667%}.ant-col-md-offset-22{margin-left:91.66666667%}.ant-col-md-order-22{order:22}.ant-col-md-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-md-push-21{left:87.5%}.ant-col-md-pull-21{right:87.5%}.ant-col-md-offset-21{margin-left:87.5%}.ant-col-md-order-21{order:21}.ant-col-md-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-md-push-20{left:83.33333333%}.ant-col-md-pull-20{right:83.33333333%}.ant-col-md-offset-20{margin-left:83.33333333%}.ant-col-md-order-20{order:20}.ant-col-md-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-md-push-19{left:79.16666667%}.ant-col-md-pull-19{right:79.16666667%}.ant-col-md-offset-19{margin-left:79.16666667%}.ant-col-md-order-19{order:19}.ant-col-md-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-md-push-18{left:75%}.ant-col-md-pull-18{right:75%}.ant-col-md-offset-18{margin-left:75%}.ant-col-md-order-18{order:18}.ant-col-md-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-md-push-17{left:70.83333333%}.ant-col-md-pull-17{right:70.83333333%}.ant-col-md-offset-17{margin-left:70.83333333%}.ant-col-md-order-17{order:17}.ant-col-md-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-md-push-16{left:66.66666667%}.ant-col-md-pull-16{right:66.66666667%}.ant-col-md-offset-16{margin-left:66.66666667%}.ant-col-md-order-16{order:16}.ant-col-md-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-md-push-15{left:62.5%}.ant-col-md-pull-15{right:62.5%}.ant-col-md-offset-15{margin-left:62.5%}.ant-col-md-order-15{order:15}.ant-col-md-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-md-push-14{left:58.33333333%}.ant-col-md-pull-14{right:58.33333333%}.ant-col-md-offset-14{margin-left:58.33333333%}.ant-col-md-order-14{order:14}.ant-col-md-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-md-push-13{left:54.16666667%}.ant-col-md-pull-13{right:54.16666667%}.ant-col-md-offset-13{margin-left:54.16666667%}.ant-col-md-order-13{order:13}.ant-col-md-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-md-push-12{left:50%}.ant-col-md-pull-12{right:50%}.ant-col-md-offset-12{margin-left:50%}.ant-col-md-order-12{order:12}.ant-col-md-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-md-push-11{left:45.83333333%}.ant-col-md-pull-11{right:45.83333333%}.ant-col-md-offset-11{margin-left:45.83333333%}.ant-col-md-order-11{order:11}.ant-col-md-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-md-push-10{left:41.66666667%}.ant-col-md-pull-10{right:41.66666667%}.ant-col-md-offset-10{margin-left:41.66666667%}.ant-col-md-order-10{order:10}.ant-col-md-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-md-push-9{left:37.5%}.ant-col-md-pull-9{right:37.5%}.ant-col-md-offset-9{margin-left:37.5%}.ant-col-md-order-9{order:9}.ant-col-md-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-md-push-8{left:33.33333333%}.ant-col-md-pull-8{right:33.33333333%}.ant-col-md-offset-8{margin-left:33.33333333%}.ant-col-md-order-8{order:8}.ant-col-md-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-md-push-7{left:29.16666667%}.ant-col-md-pull-7{right:29.16666667%}.ant-col-md-offset-7{margin-left:29.16666667%}.ant-col-md-order-7{order:7}.ant-col-md-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-md-push-6{left:25%}.ant-col-md-pull-6{right:25%}.ant-col-md-offset-6{margin-left:25%}.ant-col-md-order-6{order:6}.ant-col-md-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-md-push-5{left:20.83333333%}.ant-col-md-pull-5{right:20.83333333%}.ant-col-md-offset-5{margin-left:20.83333333%}.ant-col-md-order-5{order:5}.ant-col-md-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-md-push-4{left:16.66666667%}.ant-col-md-pull-4{right:16.66666667%}.ant-col-md-offset-4{margin-left:16.66666667%}.ant-col-md-order-4{order:4}.ant-col-md-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-md-push-3{left:12.5%}.ant-col-md-pull-3{right:12.5%}.ant-col-md-offset-3{margin-left:12.5%}.ant-col-md-order-3{order:3}.ant-col-md-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-md-push-2{left:8.33333333%}.ant-col-md-pull-2{right:8.33333333%}.ant-col-md-offset-2{margin-left:8.33333333%}.ant-col-md-order-2{order:2}.ant-col-md-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-md-push-1{left:4.16666667%}.ant-col-md-pull-1{right:4.16666667%}.ant-col-md-offset-1{margin-left:4.16666667%}.ant-col-md-order-1{order:1}.ant-col-md-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-md-push-0{left:auto}.ant-col-md-pull-0{right:auto}.ant-col-md-offset-0{margin-left:0}.ant-col-md-order-0{order:0}.ant-col-push-0.ant-col-rtl{right:auto}.ant-col-pull-0.ant-col-rtl{left:auto}.ant-col-md-push-0.ant-col-rtl{right:auto}.ant-col-md-pull-0.ant-col-rtl{left:auto}.ant-col-md-offset-0.ant-col-rtl{margin-right:0}.ant-col-md-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-md-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-md-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-md-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-md-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-md-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-md-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-md-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-md-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-md-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-md-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-md-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-md-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-md-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-md-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-md-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-md-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-md-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-md-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-md-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-md-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-md-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-md-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-md-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-md-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-md-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-md-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-md-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-md-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-md-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-md-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-md-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-md-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-md-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-md-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-md-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-md-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-md-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-md-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-md-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-md-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-md-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-md-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-md-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-md-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-md-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-md-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-md-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-md-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-md-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-md-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-md-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-md-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-md-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-md-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-md-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-md-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-md-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-md-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-md-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-md-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-md-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-md-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-md-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-md-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-md-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-md-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-md-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-md-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-md-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-md-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-md-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}}@media (min-width: 992px){.ant-col-lg-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-lg-push-24{left:100%}.ant-col-lg-pull-24{right:100%}.ant-col-lg-offset-24{margin-left:100%}.ant-col-lg-order-24{order:24}.ant-col-lg-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-lg-push-23{left:95.83333333%}.ant-col-lg-pull-23{right:95.83333333%}.ant-col-lg-offset-23{margin-left:95.83333333%}.ant-col-lg-order-23{order:23}.ant-col-lg-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-lg-push-22{left:91.66666667%}.ant-col-lg-pull-22{right:91.66666667%}.ant-col-lg-offset-22{margin-left:91.66666667%}.ant-col-lg-order-22{order:22}.ant-col-lg-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-lg-push-21{left:87.5%}.ant-col-lg-pull-21{right:87.5%}.ant-col-lg-offset-21{margin-left:87.5%}.ant-col-lg-order-21{order:21}.ant-col-lg-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-lg-push-20{left:83.33333333%}.ant-col-lg-pull-20{right:83.33333333%}.ant-col-lg-offset-20{margin-left:83.33333333%}.ant-col-lg-order-20{order:20}.ant-col-lg-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-lg-push-19{left:79.16666667%}.ant-col-lg-pull-19{right:79.16666667%}.ant-col-lg-offset-19{margin-left:79.16666667%}.ant-col-lg-order-19{order:19}.ant-col-lg-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-lg-push-18{left:75%}.ant-col-lg-pull-18{right:75%}.ant-col-lg-offset-18{margin-left:75%}.ant-col-lg-order-18{order:18}.ant-col-lg-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-lg-push-17{left:70.83333333%}.ant-col-lg-pull-17{right:70.83333333%}.ant-col-lg-offset-17{margin-left:70.83333333%}.ant-col-lg-order-17{order:17}.ant-col-lg-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-lg-push-16{left:66.66666667%}.ant-col-lg-pull-16{right:66.66666667%}.ant-col-lg-offset-16{margin-left:66.66666667%}.ant-col-lg-order-16{order:16}.ant-col-lg-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-lg-push-15{left:62.5%}.ant-col-lg-pull-15{right:62.5%}.ant-col-lg-offset-15{margin-left:62.5%}.ant-col-lg-order-15{order:15}.ant-col-lg-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-lg-push-14{left:58.33333333%}.ant-col-lg-pull-14{right:58.33333333%}.ant-col-lg-offset-14{margin-left:58.33333333%}.ant-col-lg-order-14{order:14}.ant-col-lg-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-lg-push-13{left:54.16666667%}.ant-col-lg-pull-13{right:54.16666667%}.ant-col-lg-offset-13{margin-left:54.16666667%}.ant-col-lg-order-13{order:13}.ant-col-lg-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-lg-push-12{left:50%}.ant-col-lg-pull-12{right:50%}.ant-col-lg-offset-12{margin-left:50%}.ant-col-lg-order-12{order:12}.ant-col-lg-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-lg-push-11{left:45.83333333%}.ant-col-lg-pull-11{right:45.83333333%}.ant-col-lg-offset-11{margin-left:45.83333333%}.ant-col-lg-order-11{order:11}.ant-col-lg-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-lg-push-10{left:41.66666667%}.ant-col-lg-pull-10{right:41.66666667%}.ant-col-lg-offset-10{margin-left:41.66666667%}.ant-col-lg-order-10{order:10}.ant-col-lg-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-lg-push-9{left:37.5%}.ant-col-lg-pull-9{right:37.5%}.ant-col-lg-offset-9{margin-left:37.5%}.ant-col-lg-order-9{order:9}.ant-col-lg-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-lg-push-8{left:33.33333333%}.ant-col-lg-pull-8{right:33.33333333%}.ant-col-lg-offset-8{margin-left:33.33333333%}.ant-col-lg-order-8{order:8}.ant-col-lg-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-lg-push-7{left:29.16666667%}.ant-col-lg-pull-7{right:29.16666667%}.ant-col-lg-offset-7{margin-left:29.16666667%}.ant-col-lg-order-7{order:7}.ant-col-lg-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-lg-push-6{left:25%}.ant-col-lg-pull-6{right:25%}.ant-col-lg-offset-6{margin-left:25%}.ant-col-lg-order-6{order:6}.ant-col-lg-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-lg-push-5{left:20.83333333%}.ant-col-lg-pull-5{right:20.83333333%}.ant-col-lg-offset-5{margin-left:20.83333333%}.ant-col-lg-order-5{order:5}.ant-col-lg-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-lg-push-4{left:16.66666667%}.ant-col-lg-pull-4{right:16.66666667%}.ant-col-lg-offset-4{margin-left:16.66666667%}.ant-col-lg-order-4{order:4}.ant-col-lg-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-lg-push-3{left:12.5%}.ant-col-lg-pull-3{right:12.5%}.ant-col-lg-offset-3{margin-left:12.5%}.ant-col-lg-order-3{order:3}.ant-col-lg-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-lg-push-2{left:8.33333333%}.ant-col-lg-pull-2{right:8.33333333%}.ant-col-lg-offset-2{margin-left:8.33333333%}.ant-col-lg-order-2{order:2}.ant-col-lg-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-lg-push-1{left:4.16666667%}.ant-col-lg-pull-1{right:4.16666667%}.ant-col-lg-offset-1{margin-left:4.16666667%}.ant-col-lg-order-1{order:1}.ant-col-lg-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-lg-push-0{left:auto}.ant-col-lg-pull-0{right:auto}.ant-col-lg-offset-0{margin-left:0}.ant-col-lg-order-0{order:0}.ant-col-push-0.ant-col-rtl{right:auto}.ant-col-pull-0.ant-col-rtl{left:auto}.ant-col-lg-push-0.ant-col-rtl{right:auto}.ant-col-lg-pull-0.ant-col-rtl{left:auto}.ant-col-lg-offset-0.ant-col-rtl{margin-right:0}.ant-col-lg-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-lg-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-lg-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-lg-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-lg-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-lg-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-lg-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-lg-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-lg-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-lg-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-lg-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-lg-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-lg-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-lg-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-lg-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-lg-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-lg-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-lg-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-lg-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-lg-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-lg-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-lg-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-lg-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-lg-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-lg-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-lg-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-lg-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-lg-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-lg-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-lg-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-lg-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-lg-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-lg-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-lg-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-lg-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-lg-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-lg-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-lg-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-lg-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-lg-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-lg-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-lg-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-lg-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-lg-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-lg-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-lg-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-lg-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-lg-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-lg-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-lg-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-lg-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-lg-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-lg-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-lg-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-lg-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-lg-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-lg-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-lg-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-lg-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-lg-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-lg-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-lg-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-lg-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-lg-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-lg-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-lg-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-lg-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-lg-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-lg-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-lg-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-lg-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-lg-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}}@media (min-width: 1200px){.ant-col-xl-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-xl-push-24{left:100%}.ant-col-xl-pull-24{right:100%}.ant-col-xl-offset-24{margin-left:100%}.ant-col-xl-order-24{order:24}.ant-col-xl-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-xl-push-23{left:95.83333333%}.ant-col-xl-pull-23{right:95.83333333%}.ant-col-xl-offset-23{margin-left:95.83333333%}.ant-col-xl-order-23{order:23}.ant-col-xl-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-xl-push-22{left:91.66666667%}.ant-col-xl-pull-22{right:91.66666667%}.ant-col-xl-offset-22{margin-left:91.66666667%}.ant-col-xl-order-22{order:22}.ant-col-xl-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-xl-push-21{left:87.5%}.ant-col-xl-pull-21{right:87.5%}.ant-col-xl-offset-21{margin-left:87.5%}.ant-col-xl-order-21{order:21}.ant-col-xl-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-xl-push-20{left:83.33333333%}.ant-col-xl-pull-20{right:83.33333333%}.ant-col-xl-offset-20{margin-left:83.33333333%}.ant-col-xl-order-20{order:20}.ant-col-xl-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-xl-push-19{left:79.16666667%}.ant-col-xl-pull-19{right:79.16666667%}.ant-col-xl-offset-19{margin-left:79.16666667%}.ant-col-xl-order-19{order:19}.ant-col-xl-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-xl-push-18{left:75%}.ant-col-xl-pull-18{right:75%}.ant-col-xl-offset-18{margin-left:75%}.ant-col-xl-order-18{order:18}.ant-col-xl-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-xl-push-17{left:70.83333333%}.ant-col-xl-pull-17{right:70.83333333%}.ant-col-xl-offset-17{margin-left:70.83333333%}.ant-col-xl-order-17{order:17}.ant-col-xl-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-xl-push-16{left:66.66666667%}.ant-col-xl-pull-16{right:66.66666667%}.ant-col-xl-offset-16{margin-left:66.66666667%}.ant-col-xl-order-16{order:16}.ant-col-xl-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-xl-push-15{left:62.5%}.ant-col-xl-pull-15{right:62.5%}.ant-col-xl-offset-15{margin-left:62.5%}.ant-col-xl-order-15{order:15}.ant-col-xl-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-xl-push-14{left:58.33333333%}.ant-col-xl-pull-14{right:58.33333333%}.ant-col-xl-offset-14{margin-left:58.33333333%}.ant-col-xl-order-14{order:14}.ant-col-xl-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-xl-push-13{left:54.16666667%}.ant-col-xl-pull-13{right:54.16666667%}.ant-col-xl-offset-13{margin-left:54.16666667%}.ant-col-xl-order-13{order:13}.ant-col-xl-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-xl-push-12{left:50%}.ant-col-xl-pull-12{right:50%}.ant-col-xl-offset-12{margin-left:50%}.ant-col-xl-order-12{order:12}.ant-col-xl-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-xl-push-11{left:45.83333333%}.ant-col-xl-pull-11{right:45.83333333%}.ant-col-xl-offset-11{margin-left:45.83333333%}.ant-col-xl-order-11{order:11}.ant-col-xl-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-xl-push-10{left:41.66666667%}.ant-col-xl-pull-10{right:41.66666667%}.ant-col-xl-offset-10{margin-left:41.66666667%}.ant-col-xl-order-10{order:10}.ant-col-xl-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-xl-push-9{left:37.5%}.ant-col-xl-pull-9{right:37.5%}.ant-col-xl-offset-9{margin-left:37.5%}.ant-col-xl-order-9{order:9}.ant-col-xl-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-xl-push-8{left:33.33333333%}.ant-col-xl-pull-8{right:33.33333333%}.ant-col-xl-offset-8{margin-left:33.33333333%}.ant-col-xl-order-8{order:8}.ant-col-xl-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-xl-push-7{left:29.16666667%}.ant-col-xl-pull-7{right:29.16666667%}.ant-col-xl-offset-7{margin-left:29.16666667%}.ant-col-xl-order-7{order:7}.ant-col-xl-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-xl-push-6{left:25%}.ant-col-xl-pull-6{right:25%}.ant-col-xl-offset-6{margin-left:25%}.ant-col-xl-order-6{order:6}.ant-col-xl-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-xl-push-5{left:20.83333333%}.ant-col-xl-pull-5{right:20.83333333%}.ant-col-xl-offset-5{margin-left:20.83333333%}.ant-col-xl-order-5{order:5}.ant-col-xl-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-xl-push-4{left:16.66666667%}.ant-col-xl-pull-4{right:16.66666667%}.ant-col-xl-offset-4{margin-left:16.66666667%}.ant-col-xl-order-4{order:4}.ant-col-xl-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-xl-push-3{left:12.5%}.ant-col-xl-pull-3{right:12.5%}.ant-col-xl-offset-3{margin-left:12.5%}.ant-col-xl-order-3{order:3}.ant-col-xl-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-xl-push-2{left:8.33333333%}.ant-col-xl-pull-2{right:8.33333333%}.ant-col-xl-offset-2{margin-left:8.33333333%}.ant-col-xl-order-2{order:2}.ant-col-xl-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-xl-push-1{left:4.16666667%}.ant-col-xl-pull-1{right:4.16666667%}.ant-col-xl-offset-1{margin-left:4.16666667%}.ant-col-xl-order-1{order:1}.ant-col-xl-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-xl-push-0{left:auto}.ant-col-xl-pull-0{right:auto}.ant-col-xl-offset-0{margin-left:0}.ant-col-xl-order-0{order:0}.ant-col-push-0.ant-col-rtl{right:auto}.ant-col-pull-0.ant-col-rtl{left:auto}.ant-col-xl-push-0.ant-col-rtl{right:auto}.ant-col-xl-pull-0.ant-col-rtl{left:auto}.ant-col-xl-offset-0.ant-col-rtl{margin-right:0}.ant-col-xl-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-xl-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-xl-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-xl-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-xl-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-xl-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-xl-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-xl-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-xl-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-xl-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-xl-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-xl-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-xl-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-xl-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-xl-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-xl-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-xl-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-xl-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-xl-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-xl-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-xl-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-xl-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-xl-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-xl-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-xl-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-xl-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-xl-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-xl-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-xl-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-xl-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-xl-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-xl-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-xl-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-xl-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-xl-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-xl-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-xl-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-xl-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-xl-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-xl-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-xl-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-xl-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-xl-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-xl-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-xl-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-xl-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-xl-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-xl-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-xl-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-xl-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-xl-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-xl-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-xl-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-xl-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-xl-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-xl-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-xl-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-xl-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-xl-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-xl-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-xl-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-xl-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-xl-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-xl-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-xl-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-xl-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-xl-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-xl-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-xl-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-xl-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-xl-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-xl-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}}@media (min-width: 1600px){.ant-col-xxl-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-xxl-push-24{left:100%}.ant-col-xxl-pull-24{right:100%}.ant-col-xxl-offset-24{margin-left:100%}.ant-col-xxl-order-24{order:24}.ant-col-xxl-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-xxl-push-23{left:95.83333333%}.ant-col-xxl-pull-23{right:95.83333333%}.ant-col-xxl-offset-23{margin-left:95.83333333%}.ant-col-xxl-order-23{order:23}.ant-col-xxl-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-xxl-push-22{left:91.66666667%}.ant-col-xxl-pull-22{right:91.66666667%}.ant-col-xxl-offset-22{margin-left:91.66666667%}.ant-col-xxl-order-22{order:22}.ant-col-xxl-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-xxl-push-21{left:87.5%}.ant-col-xxl-pull-21{right:87.5%}.ant-col-xxl-offset-21{margin-left:87.5%}.ant-col-xxl-order-21{order:21}.ant-col-xxl-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-xxl-push-20{left:83.33333333%}.ant-col-xxl-pull-20{right:83.33333333%}.ant-col-xxl-offset-20{margin-left:83.33333333%}.ant-col-xxl-order-20{order:20}.ant-col-xxl-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-xxl-push-19{left:79.16666667%}.ant-col-xxl-pull-19{right:79.16666667%}.ant-col-xxl-offset-19{margin-left:79.16666667%}.ant-col-xxl-order-19{order:19}.ant-col-xxl-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-xxl-push-18{left:75%}.ant-col-xxl-pull-18{right:75%}.ant-col-xxl-offset-18{margin-left:75%}.ant-col-xxl-order-18{order:18}.ant-col-xxl-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-xxl-push-17{left:70.83333333%}.ant-col-xxl-pull-17{right:70.83333333%}.ant-col-xxl-offset-17{margin-left:70.83333333%}.ant-col-xxl-order-17{order:17}.ant-col-xxl-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-xxl-push-16{left:66.66666667%}.ant-col-xxl-pull-16{right:66.66666667%}.ant-col-xxl-offset-16{margin-left:66.66666667%}.ant-col-xxl-order-16{order:16}.ant-col-xxl-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-xxl-push-15{left:62.5%}.ant-col-xxl-pull-15{right:62.5%}.ant-col-xxl-offset-15{margin-left:62.5%}.ant-col-xxl-order-15{order:15}.ant-col-xxl-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-xxl-push-14{left:58.33333333%}.ant-col-xxl-pull-14{right:58.33333333%}.ant-col-xxl-offset-14{margin-left:58.33333333%}.ant-col-xxl-order-14{order:14}.ant-col-xxl-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-xxl-push-13{left:54.16666667%}.ant-col-xxl-pull-13{right:54.16666667%}.ant-col-xxl-offset-13{margin-left:54.16666667%}.ant-col-xxl-order-13{order:13}.ant-col-xxl-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-xxl-push-12{left:50%}.ant-col-xxl-pull-12{right:50%}.ant-col-xxl-offset-12{margin-left:50%}.ant-col-xxl-order-12{order:12}.ant-col-xxl-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-xxl-push-11{left:45.83333333%}.ant-col-xxl-pull-11{right:45.83333333%}.ant-col-xxl-offset-11{margin-left:45.83333333%}.ant-col-xxl-order-11{order:11}.ant-col-xxl-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-xxl-push-10{left:41.66666667%}.ant-col-xxl-pull-10{right:41.66666667%}.ant-col-xxl-offset-10{margin-left:41.66666667%}.ant-col-xxl-order-10{order:10}.ant-col-xxl-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-xxl-push-9{left:37.5%}.ant-col-xxl-pull-9{right:37.5%}.ant-col-xxl-offset-9{margin-left:37.5%}.ant-col-xxl-order-9{order:9}.ant-col-xxl-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-xxl-push-8{left:33.33333333%}.ant-col-xxl-pull-8{right:33.33333333%}.ant-col-xxl-offset-8{margin-left:33.33333333%}.ant-col-xxl-order-8{order:8}.ant-col-xxl-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-xxl-push-7{left:29.16666667%}.ant-col-xxl-pull-7{right:29.16666667%}.ant-col-xxl-offset-7{margin-left:29.16666667%}.ant-col-xxl-order-7{order:7}.ant-col-xxl-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-xxl-push-6{left:25%}.ant-col-xxl-pull-6{right:25%}.ant-col-xxl-offset-6{margin-left:25%}.ant-col-xxl-order-6{order:6}.ant-col-xxl-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-xxl-push-5{left:20.83333333%}.ant-col-xxl-pull-5{right:20.83333333%}.ant-col-xxl-offset-5{margin-left:20.83333333%}.ant-col-xxl-order-5{order:5}.ant-col-xxl-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-xxl-push-4{left:16.66666667%}.ant-col-xxl-pull-4{right:16.66666667%}.ant-col-xxl-offset-4{margin-left:16.66666667%}.ant-col-xxl-order-4{order:4}.ant-col-xxl-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-xxl-push-3{left:12.5%}.ant-col-xxl-pull-3{right:12.5%}.ant-col-xxl-offset-3{margin-left:12.5%}.ant-col-xxl-order-3{order:3}.ant-col-xxl-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-xxl-push-2{left:8.33333333%}.ant-col-xxl-pull-2{right:8.33333333%}.ant-col-xxl-offset-2{margin-left:8.33333333%}.ant-col-xxl-order-2{order:2}.ant-col-xxl-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-xxl-push-1{left:4.16666667%}.ant-col-xxl-pull-1{right:4.16666667%}.ant-col-xxl-offset-1{margin-left:4.16666667%}.ant-col-xxl-order-1{order:1}.ant-col-xxl-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-xxl-push-0{left:auto}.ant-col-xxl-pull-0{right:auto}.ant-col-xxl-offset-0{margin-left:0}.ant-col-xxl-order-0{order:0}.ant-col-push-0.ant-col-rtl{right:auto}.ant-col-pull-0.ant-col-rtl{left:auto}.ant-col-xxl-push-0.ant-col-rtl{right:auto}.ant-col-xxl-pull-0.ant-col-rtl{left:auto}.ant-col-xxl-offset-0.ant-col-rtl{margin-right:0}.ant-col-xxl-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-xxl-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-xxl-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-xxl-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-xxl-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-xxl-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-xxl-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-xxl-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-xxl-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-xxl-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-xxl-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-xxl-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-xxl-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-xxl-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-xxl-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-xxl-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-xxl-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-xxl-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-xxl-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-xxl-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-xxl-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-xxl-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-xxl-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-xxl-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-xxl-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-xxl-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-xxl-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-xxl-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-xxl-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-xxl-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-xxl-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-xxl-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-xxl-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-xxl-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-xxl-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-xxl-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-xxl-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-xxl-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-xxl-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-xxl-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-xxl-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-xxl-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-xxl-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-xxl-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-xxl-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-xxl-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-xxl-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-xxl-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-xxl-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-xxl-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-xxl-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-xxl-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-xxl-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-xxl-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-xxl-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-xxl-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-xxl-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-xxl-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-xxl-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-xxl-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-xxl-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-xxl-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-xxl-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-xxl-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-xxl-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-xxl-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-xxl-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-xxl-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-xxl-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-xxl-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-xxl-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-xxl-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}}.ant-row-rtl{direction:rtl}.ant-input-affix-wrapper{position:relative;display:inline-block;width:100%;min-width:0;padding:4px 11px;color:#000000d9;font-size:14px;line-height:1.5715;background-color:#fff;background-image:none;border:1px solid #d9d9d9;border-radius:4px;transition:all .3s;display:inline-flex}.ant-input-affix-wrapper::placeholder{color:#bfbfbf;-webkit-user-select:none;user-select:none}.ant-input-affix-wrapper:placeholder-shown{text-overflow:ellipsis}.ant-input-affix-wrapper:hover{border-color:#40a9ff;border-right-width:1px}.ant-input-rtl .ant-input-affix-wrapper:hover{border-right-width:0;border-left-width:1px!important}.ant-input-affix-wrapper:focus,.ant-input-affix-wrapper-focused{border-color:#40a9ff;box-shadow:0 0 0 2px #1890ff33;border-right-width:1px;outline:0}.ant-input-rtl .ant-input-affix-wrapper:focus,.ant-input-rtl .ant-input-affix-wrapper-focused{border-right-width:0;border-left-width:1px!important}.ant-input-affix-wrapper-disabled{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-affix-wrapper-disabled:hover{border-color:#d9d9d9;border-right-width:1px}.ant-input-affix-wrapper[disabled]{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-affix-wrapper[disabled]:hover{border-color:#d9d9d9;border-right-width:1px}.ant-input-affix-wrapper-borderless,.ant-input-affix-wrapper-borderless:hover,.ant-input-affix-wrapper-borderless:focus,.ant-input-affix-wrapper-borderless-focused,.ant-input-affix-wrapper-borderless-disabled,.ant-input-affix-wrapper-borderless[disabled]{background-color:transparent;border:none;box-shadow:none}textarea.ant-input-affix-wrapper{max-width:100%;height:auto;min-height:32px;line-height:1.5715;vertical-align:bottom;transition:all .3s,height 0s}.ant-input-affix-wrapper-lg{padding:6.5px 11px;font-size:16px}.ant-input-affix-wrapper-sm{padding:0 7px}.ant-input-affix-wrapper-rtl{direction:rtl}.ant-input-affix-wrapper:not(.ant-input-affix-wrapper-disabled):hover{border-color:#40a9ff;border-right-width:1px;z-index:1}.ant-input-rtl .ant-input-affix-wrapper:not(.ant-input-affix-wrapper-disabled):hover{border-right-width:0;border-left-width:1px!important}.ant-input-search-with-button .ant-input-affix-wrapper:not(.ant-input-affix-wrapper-disabled):hover{z-index:0}.ant-input-affix-wrapper-focused,.ant-input-affix-wrapper:focus{z-index:1}.ant-input-affix-wrapper-disabled .ant-input[disabled]{background:transparent}.ant-input-affix-wrapper>input.ant-input{padding:0;border:none;outline:none}.ant-input-affix-wrapper>input.ant-input:focus{box-shadow:none!important}.ant-input-affix-wrapper:before{width:0;visibility:hidden;content:"\a0"}.ant-input-prefix,.ant-input-suffix{display:flex;flex:none;align-items:center}.ant-input-prefix>*:not(:last-child),.ant-input-suffix>*:not(:last-child){margin-right:8px}.ant-input-show-count-suffix{color:#00000073}.ant-input-show-count-has-suffix{margin-right:2px}.ant-input-prefix{margin-right:4px}.ant-input-suffix{margin-left:4px}.anticon.ant-input-clear-icon,.ant-input-clear-icon{margin:0;color:#00000040;font-size:12px;vertical-align:-1px;cursor:pointer;transition:color .3s}.anticon.ant-input-clear-icon:hover,.ant-input-clear-icon:hover{color:#00000073}.anticon.ant-input-clear-icon:active,.ant-input-clear-icon:active{color:#000000d9}.anticon.ant-input-clear-icon-hidden,.ant-input-clear-icon-hidden{visibility:hidden}.anticon.ant-input-clear-icon-has-suffix,.ant-input-clear-icon-has-suffix{margin:0 4px}.ant-input-affix-wrapper-textarea-with-clear-btn{padding:0!important;border:0!important}.ant-input-affix-wrapper-textarea-with-clear-btn .ant-input-clear-icon{position:absolute;top:8px;right:8px;z-index:1}.ant-input-status-error:not(.ant-input-disabled):not(.ant-input-borderless).ant-input,.ant-input-status-error:not(.ant-input-disabled):not(.ant-input-borderless).ant-input:hover{background:#fff;border-color:#ff4d4f}.ant-input-status-error:not(.ant-input-disabled):not(.ant-input-borderless).ant-input:focus,.ant-input-status-error:not(.ant-input-disabled):not(.ant-input-borderless).ant-input-focused{border-color:#ff7875;box-shadow:0 0 0 2px #ff4d4f33;border-right-width:1px;outline:0}.ant-input-status-error .ant-input-prefix{color:#ff4d4f}.ant-input-status-warning:not(.ant-input-disabled):not(.ant-input-borderless).ant-input,.ant-input-status-warning:not(.ant-input-disabled):not(.ant-input-borderless).ant-input:hover{background:#fff;border-color:#faad14}.ant-input-status-warning:not(.ant-input-disabled):not(.ant-input-borderless).ant-input:focus,.ant-input-status-warning:not(.ant-input-disabled):not(.ant-input-borderless).ant-input-focused{border-color:#ffc53d;box-shadow:0 0 0 2px #faad1433;border-right-width:1px;outline:0}.ant-input-status-warning .ant-input-prefix{color:#faad14}.ant-input-affix-wrapper-status-error:not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper,.ant-input-affix-wrapper-status-error:not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper:hover{background:#fff;border-color:#ff4d4f}.ant-input-affix-wrapper-status-error:not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper:focus,.ant-input-affix-wrapper-status-error:not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper-focused{border-color:#ff7875;box-shadow:0 0 0 2px #ff4d4f33;border-right-width:1px;outline:0}.ant-input-affix-wrapper-status-error .ant-input-prefix{color:#ff4d4f}.ant-input-affix-wrapper-status-warning:not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper,.ant-input-affix-wrapper-status-warning:not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper:hover{background:#fff;border-color:#faad14}.ant-input-affix-wrapper-status-warning:not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper:focus,.ant-input-affix-wrapper-status-warning:not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper-focused{border-color:#ffc53d;box-shadow:0 0 0 2px #faad1433;border-right-width:1px;outline:0}.ant-input-affix-wrapper-status-warning .ant-input-prefix{color:#faad14}.ant-input-textarea-status-error.ant-input-textarea-has-feedback .ant-input,.ant-input-textarea-status-warning.ant-input-textarea-has-feedback .ant-input,.ant-input-textarea-status-success.ant-input-textarea-has-feedback .ant-input,.ant-input-textarea-status-validating.ant-input-textarea-has-feedback .ant-input{padding-right:24px}.ant-input-group-wrapper-status-error .ant-input-group-addon{color:#ff4d4f;border-color:#ff4d4f}.ant-input-group-wrapper-status-warning .ant-input-group-addon{color:#faad14;border-color:#faad14}.ant-input{box-sizing:border-box;margin:0;font-variant:tabular-nums;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;width:100%;min-width:0;padding:4px 11px;color:#000000d9;font-size:14px;line-height:1.5715;background-color:#fff;background-image:none;border:1px solid #d9d9d9;border-radius:4px;transition:all .3s}.ant-input::placeholder{color:#bfbfbf;-webkit-user-select:none;user-select:none}.ant-input:placeholder-shown{text-overflow:ellipsis}.ant-input:hover{border-color:#40a9ff;border-right-width:1px}.ant-input-rtl .ant-input:hover{border-right-width:0;border-left-width:1px!important}.ant-input:focus,.ant-input-focused{border-color:#40a9ff;box-shadow:0 0 0 2px #1890ff33;border-right-width:1px;outline:0}.ant-input-rtl .ant-input:focus,.ant-input-rtl .ant-input-focused{border-right-width:0;border-left-width:1px!important}.ant-input-disabled{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-disabled:hover{border-color:#d9d9d9;border-right-width:1px}.ant-input[disabled]{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input[disabled]:hover{border-color:#d9d9d9;border-right-width:1px}.ant-input-borderless,.ant-input-borderless:hover,.ant-input-borderless:focus,.ant-input-borderless-focused,.ant-input-borderless-disabled,.ant-input-borderless[disabled]{background-color:transparent;border:none;box-shadow:none}textarea.ant-input{max-width:100%;height:auto;min-height:32px;line-height:1.5715;vertical-align:bottom;transition:all .3s,height 0s}.ant-input-lg{padding:6.5px 11px;font-size:16px}.ant-input-sm{padding:0 7px}.ant-input-rtl{direction:rtl}.ant-input-group{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:table;width:100%;border-collapse:separate;border-spacing:0}.ant-input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.ant-input-group>[class*=col-]{padding-right:8px}.ant-input-group>[class*=col-]:last-child{padding-right:0}.ant-input-group-addon,.ant-input-group-wrap,.ant-input-group>.ant-input{display:table-cell}.ant-input-group-addon:not(:first-child):not(:last-child),.ant-input-group-wrap:not(:first-child):not(:last-child),.ant-input-group>.ant-input:not(:first-child):not(:last-child){border-radius:0}.ant-input-group-addon,.ant-input-group-wrap{width:1px;white-space:nowrap;vertical-align:middle}.ant-input-group-wrap>*{display:block!important}.ant-input-group .ant-input{float:left;width:100%;margin-bottom:0;text-align:inherit}.ant-input-group .ant-input:focus{z-index:1;border-right-width:1px}.ant-input-group .ant-input:hover{z-index:1;border-right-width:1px}.ant-input-search-with-button .ant-input-group .ant-input:hover{z-index:0}.ant-input-group-addon{position:relative;padding:0 11px;color:#000000d9;font-weight:400;font-size:14px;text-align:center;background-color:#fafafa;border:1px solid #d9d9d9;border-radius:4px;transition:all .3s}.ant-input-group-addon .ant-select{margin:-5px -11px}.ant-input-group-addon .ant-select.ant-select-single:not(.ant-select-customize-input) .ant-select-selector{background-color:inherit;border:1px solid transparent;box-shadow:none}.ant-input-group-addon .ant-select-open .ant-select-selector,.ant-input-group-addon .ant-select-focused .ant-select-selector{color:#1890ff}.ant-input-group-addon .ant-cascader-picker{margin:-9px -12px;background-color:transparent}.ant-input-group-addon .ant-cascader-picker .ant-cascader-input{text-align:left;border:0;box-shadow:none}.ant-input-group>.ant-input:first-child,.ant-input-group-addon:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-group>.ant-input:first-child .ant-select .ant-select-selector,.ant-input-group-addon:first-child .ant-select .ant-select-selector{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-group>.ant-input-affix-wrapper:not(:first-child) .ant-input{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-group>.ant-input-affix-wrapper:not(:last-child) .ant-input{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-group-addon:first-child{border-right:0}.ant-input-group-addon:last-child{border-left:0}.ant-input-group>.ant-input:last-child,.ant-input-group-addon:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-group>.ant-input:last-child .ant-select .ant-select-selector,.ant-input-group-addon:last-child .ant-select .ant-select-selector{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-group-lg .ant-input,.ant-input-group-lg>.ant-input-group-addon{padding:6.5px 11px;font-size:16px}.ant-input-group-sm .ant-input,.ant-input-group-sm>.ant-input-group-addon{padding:0 7px}.ant-input-group-lg .ant-select-single .ant-select-selector{height:40px}.ant-input-group-sm .ant-select-single .ant-select-selector{height:24px}.ant-input-group .ant-input-affix-wrapper:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-search .ant-input-group .ant-input-affix-wrapper:not(:last-child){border-top-left-radius:4px;border-bottom-left-radius:4px}.ant-input-group .ant-input-affix-wrapper:not(:first-child),.ant-input-search .ant-input-group .ant-input-affix-wrapper:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-group.ant-input-group-compact{display:block}.ant-input-group.ant-input-group-compact:before{display:table;content:""}.ant-input-group.ant-input-group-compact:after{display:table;clear:both;content:""}.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(:last-child),.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(:last-child),.ant-input-group.ant-input-group-compact>.ant-input:not(:first-child):not(:last-child){border-right-width:1px}.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(:last-child):hover,.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(:last-child):hover,.ant-input-group.ant-input-group-compact>.ant-input:not(:first-child):not(:last-child):hover{z-index:1}.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(:last-child):focus,.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(:last-child):focus,.ant-input-group.ant-input-group-compact>.ant-input:not(:first-child):not(:last-child):focus{z-index:1}.ant-input-group.ant-input-group-compact>*{display:inline-block;float:none;vertical-align:top;border-radius:0}.ant-input-group.ant-input-group-compact>.ant-input-affix-wrapper{display:inline-flex}.ant-input-group.ant-input-group-compact>.ant-picker-range{display:inline-flex}.ant-input-group.ant-input-group-compact>*:not(:last-child){margin-right:-1px;border-right-width:1px}.ant-input-group.ant-input-group-compact .ant-input{float:none}.ant-input-group.ant-input-group-compact>.ant-select>.ant-select-selector,.ant-input-group.ant-input-group-compact>.ant-select-auto-complete .ant-input,.ant-input-group.ant-input-group-compact>.ant-cascader-picker .ant-input,.ant-input-group.ant-input-group-compact>.ant-input-group-wrapper .ant-input{border-right-width:1px;border-radius:0}.ant-input-group.ant-input-group-compact>.ant-select>.ant-select-selector:hover,.ant-input-group.ant-input-group-compact>.ant-select-auto-complete .ant-input:hover,.ant-input-group.ant-input-group-compact>.ant-cascader-picker .ant-input:hover,.ant-input-group.ant-input-group-compact>.ant-input-group-wrapper .ant-input:hover{z-index:1}.ant-input-group.ant-input-group-compact>.ant-select>.ant-select-selector:focus,.ant-input-group.ant-input-group-compact>.ant-select-auto-complete .ant-input:focus,.ant-input-group.ant-input-group-compact>.ant-cascader-picker .ant-input:focus,.ant-input-group.ant-input-group-compact>.ant-input-group-wrapper .ant-input:focus{z-index:1}.ant-input-group.ant-input-group-compact>.ant-select-focused{z-index:1}.ant-input-group.ant-input-group-compact>.ant-select>.ant-select-arrow{z-index:1}.ant-input-group.ant-input-group-compact>*:first-child,.ant-input-group.ant-input-group-compact>.ant-select:first-child>.ant-select-selector,.ant-input-group.ant-input-group-compact>.ant-select-auto-complete:first-child .ant-input,.ant-input-group.ant-input-group-compact>.ant-cascader-picker:first-child .ant-input{border-top-left-radius:4px;border-bottom-left-radius:4px}.ant-input-group.ant-input-group-compact>*:last-child,.ant-input-group.ant-input-group-compact>.ant-select:last-child>.ant-select-selector,.ant-input-group.ant-input-group-compact>.ant-cascader-picker:last-child .ant-input,.ant-input-group.ant-input-group-compact>.ant-cascader-picker-focused:last-child .ant-input{border-right-width:1px;border-top-right-radius:4px;border-bottom-right-radius:4px}.ant-input-group.ant-input-group-compact>.ant-select-auto-complete .ant-input{vertical-align:top}.ant-input-group.ant-input-group-compact .ant-input-group-wrapper+.ant-input-group-wrapper{margin-left:-1px}.ant-input-group.ant-input-group-compact .ant-input-group-wrapper+.ant-input-group-wrapper .ant-input-affix-wrapper{border-radius:0}.ant-input-group.ant-input-group-compact .ant-input-group-wrapper:not(:last-child).ant-input-search>.ant-input-group>.ant-input-group-addon>.ant-input-search-button{border-radius:0}.ant-input-group.ant-input-group-compact .ant-input-group-wrapper:not(:last-child).ant-input-search>.ant-input-group>.ant-input{border-radius:4px 0 0 4px}.ant-input-group>.ant-input-rtl:first-child,.ant-input-group-rtl .ant-input-group-addon:first-child{border-radius:0 4px 4px 0}.ant-input-group-rtl .ant-input-group-addon:first-child{border-right:1px solid #d9d9d9;border-left:0}.ant-input-group-rtl .ant-input-group-addon:last-child{border-right:0;border-left:1px solid #d9d9d9}.ant-input-group-rtl.ant-input-group>.ant-input:last-child,.ant-input-group-rtl.ant-input-group-addon:last-child{border-radius:4px 0 0 4px}.ant-input-group-rtl.ant-input-group .ant-input-affix-wrapper:not(:first-child){border-radius:4px 0 0 4px}.ant-input-group-rtl.ant-input-group .ant-input-affix-wrapper:not(:last-child){border-radius:0 4px 4px 0}.ant-input-group-rtl.ant-input-group.ant-input-group-compact>*:not(:last-child){margin-right:0;margin-left:-1px;border-left-width:1px}.ant-input-group-rtl.ant-input-group.ant-input-group-compact>*:first-child,.ant-input-group-rtl.ant-input-group.ant-input-group-compact>.ant-select:first-child>.ant-select-selector,.ant-input-group-rtl.ant-input-group.ant-input-group-compact>.ant-select-auto-complete:first-child .ant-input,.ant-input-group-rtl.ant-input-group.ant-input-group-compact>.ant-cascader-picker:first-child .ant-input{border-radius:0 4px 4px 0}.ant-input-group-rtl.ant-input-group.ant-input-group-compact>*:last-child,.ant-input-group-rtl.ant-input-group.ant-input-group-compact>.ant-select:last-child>.ant-select-selector,.ant-input-group-rtl.ant-input-group.ant-input-group-compact>.ant-select-auto-complete:last-child .ant-input,.ant-input-group-rtl.ant-input-group.ant-input-group-compact>.ant-cascader-picker:last-child .ant-input,.ant-input-group-rtl.ant-input-group.ant-input-group-compact>.ant-cascader-picker-focused:last-child .ant-input{border-left-width:1px;border-radius:4px 0 0 4px}.ant-input-group.ant-input-group-compact .ant-input-group-wrapper-rtl+.ant-input-group-wrapper-rtl{margin-right:-1px;margin-left:0}.ant-input-group.ant-input-group-compact .ant-input-group-wrapper-rtl:not(:last-child).ant-input-search>.ant-input-group>.ant-input{border-radius:0 4px 4px 0}.ant-input-group-wrapper{display:inline-block;width:100%;text-align:start;vertical-align:top}.ant-input-password-icon.anticon{color:#00000073;cursor:pointer;transition:all .3s}.ant-input-password-icon.anticon:hover{color:#000000d9}.ant-input[type=color]{height:32px}.ant-input[type=color].ant-input-lg{height:40px}.ant-input[type=color].ant-input-sm{height:24px;padding-top:3px;padding-bottom:3px}.ant-input-textarea-show-count>.ant-input{height:100%}.ant-input-textarea-show-count:after{float:right;color:#00000073;white-space:nowrap;content:attr(data-count);pointer-events:none}.ant-input-textarea-show-count.ant-input-textarea-in-form-item:after{margin-bottom:-22px}.ant-input-textarea-suffix{position:absolute;top:0;right:11px;bottom:0;z-index:1;display:inline-flex;align-items:center;margin:auto}.ant-input-search .ant-input:hover,.ant-input-search .ant-input:focus{border-color:#40a9ff}.ant-input-search .ant-input:hover+.ant-input-group-addon .ant-input-search-button:not(.ant-btn-primary),.ant-input-search .ant-input:focus+.ant-input-group-addon .ant-input-search-button:not(.ant-btn-primary){border-left-color:#40a9ff}.ant-input-search .ant-input-affix-wrapper{border-radius:0}.ant-input-search .ant-input-lg{line-height:1.5713}.ant-input-search>.ant-input-group>.ant-input-group-addon:last-child{left:-1px;padding:0;border:0}.ant-input-search>.ant-input-group>.ant-input-group-addon:last-child .ant-input-search-button{padding-top:0;padding-bottom:0;border-radius:0 4px 4px 0}.ant-input-search>.ant-input-group>.ant-input-group-addon:last-child .ant-input-search-button:not(.ant-btn-primary){color:#00000073}.ant-input-search>.ant-input-group>.ant-input-group-addon:last-child .ant-input-search-button:not(.ant-btn-primary).ant-btn-loading:before{inset:0}.ant-input-search-button{height:32px}.ant-input-search-button:hover,.ant-input-search-button:focus{z-index:1}.ant-input-search-large .ant-input-search-button{height:40px}.ant-input-search-small .ant-input-search-button{height:24px}.ant-input-group-wrapper-rtl,.ant-input-group-rtl{direction:rtl}.ant-input-affix-wrapper.ant-input-affix-wrapper-rtl>input.ant-input{border:none;outline:none}.ant-input-affix-wrapper-rtl .ant-input-prefix{margin:0 0 0 4px}.ant-input-affix-wrapper-rtl .ant-input-suffix{margin:0 4px 0 0}.ant-input-textarea-rtl{direction:rtl}.ant-input-textarea-rtl.ant-input-textarea-show-count:after{text-align:left}.ant-input-affix-wrapper-rtl .ant-input-clear-icon-has-suffix{margin-right:0;margin-left:4px}.ant-input-affix-wrapper-rtl .ant-input-clear-icon{right:auto;left:8px}.ant-input-search-rtl{direction:rtl}.ant-input-search-rtl .ant-input:hover+.ant-input-group-addon .ant-input-search-button:not(.ant-btn-primary),.ant-input-search-rtl .ant-input:focus+.ant-input-group-addon .ant-input-search-button:not(.ant-btn-primary){border-right-color:#40a9ff;border-left-color:#d9d9d9}.ant-input-search-rtl>.ant-input-group>.ant-input-affix-wrapper:hover,.ant-input-search-rtl>.ant-input-group>.ant-input-affix-wrapper-focused{border-right-color:#40a9ff}.ant-input-search-rtl>.ant-input-group>.ant-input-group-addon{right:-1px;left:auto}.ant-input-search-rtl>.ant-input-group>.ant-input-group-addon .ant-input-search-button{border-radius:4px 0 0 4px}@media screen and (-ms-high-contrast: active),(-ms-high-contrast: none){.ant-input{height:32px}.ant-input-lg{height:40px}.ant-input-sm{height:24px}.ant-input-affix-wrapper>input.ant-input{height:auto}}textarea.nz-textarea-autosize-measuring{height:auto!important;overflow:hidden!important;padding:2px 0!important}.ant-input-search-rtl.ant-input-search-enter-button+.ant-input-group-addon .ant-input-search-button.ant-btn-icon-only,.ant-input-search-rtl.ant-input-search-enter-button input+.ant-input-group-addon .ant-input-search-button.ant-btn-icon-only{width:32px;height:32px}.ant-input-search-rtl.ant-input-search-enter-button+.ant-input-group-addon .ant-input-search-button.ant-btn-icon-only.ant-btn-sm,.ant-input-search-rtl.ant-input-search-enter-button input+.ant-input-group-addon .ant-input-search-button.ant-btn-icon-only.ant-btn-sm{width:24px;height:24px}.ant-input-search-rtl.ant-input-search-enter-button+.ant-input-group-addon .ant-input-search-button.ant-btn-icon-only.ant-btn-lg,.ant-input-search-rtl.ant-input-search-enter-button input+.ant-input-group-addon .ant-input-search-button.ant-btn-icon-only.ant-btn-lg{width:40px;height:40px}.ant-input-affix-wrapper-textarea-with-clear-btn .ant-input-suffix{margin-left:0}nz-form-item-feedback-icon.ant-input-suffix{display:flex;flex:none;align-items:center;pointer-events:none}nz-form-item-feedback-icon.ant-input-suffix{position:absolute;top:0;right:0;z-index:1;height:100%;margin-right:12px;margin-left:4px}.ant-input-status-error.ant-input-has-feedback,.ant-input-status-warning.ant-input-has-feedback,.ant-input-status-validating.ant-input-has-feedback,.ant-input-status-success.ant-input-has-feedback{padding-right:28px}.ant-input-textarea-show-count{display:block;position:relative}.ant-input-number-affix-wrapper{display:inline-block;width:100%;min-width:0;color:#000000d9;font-size:14px;line-height:1.5715;background-color:#fff;background-image:none;border:1px solid #d9d9d9;border-radius:4px;transition:all .3s;position:relative;display:inline-flex;width:90px;padding:0;padding-inline-start:11px}.ant-input-number-affix-wrapper::placeholder{color:#bfbfbf;-webkit-user-select:none;user-select:none}.ant-input-number-affix-wrapper:placeholder-shown{text-overflow:ellipsis}.ant-input-number-affix-wrapper:hover{border-color:#40a9ff;border-right-width:1px}.ant-input-rtl .ant-input-number-affix-wrapper:hover{border-right-width:0;border-left-width:1px!important}.ant-input-number-affix-wrapper:focus,.ant-input-number-affix-wrapper-focused{border-color:#40a9ff;box-shadow:0 0 0 2px #1890ff33;border-right-width:1px;outline:0}.ant-input-rtl .ant-input-number-affix-wrapper:focus,.ant-input-rtl .ant-input-number-affix-wrapper-focused{border-right-width:0;border-left-width:1px!important}.ant-input-number-affix-wrapper-disabled{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-number-affix-wrapper-disabled:hover{border-color:#d9d9d9;border-right-width:1px}.ant-input-number-affix-wrapper[disabled]{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-number-affix-wrapper[disabled]:hover{border-color:#d9d9d9;border-right-width:1px}.ant-input-number-affix-wrapper-borderless,.ant-input-number-affix-wrapper-borderless:hover,.ant-input-number-affix-wrapper-borderless:focus,.ant-input-number-affix-wrapper-borderless-focused,.ant-input-number-affix-wrapper-borderless-disabled,.ant-input-number-affix-wrapper-borderless[disabled]{background-color:transparent;border:none;box-shadow:none}textarea.ant-input-number-affix-wrapper{max-width:100%;height:auto;min-height:32px;line-height:1.5715;vertical-align:bottom;transition:all .3s,height 0s}.ant-input-number-affix-wrapper-lg{padding:6.5px 11px;font-size:16px}.ant-input-number-affix-wrapper-sm{padding:0 7px}.ant-input-number-affix-wrapper-rtl{direction:rtl}.ant-input-number-affix-wrapper:not(.ant-input-number-affix-wrapper-disabled):hover{border-color:#40a9ff;border-right-width:1px;z-index:1}.ant-input-rtl .ant-input-number-affix-wrapper:not(.ant-input-number-affix-wrapper-disabled):hover{border-right-width:0;border-left-width:1px!important}.ant-input-number-affix-wrapper-focused,.ant-input-number-affix-wrapper:focus{z-index:1}.ant-input-number-affix-wrapper-disabled .ant-input-number[disabled]{background:transparent}.ant-input-number-affix-wrapper>div.ant-input-number{width:100%;border:none;outline:none}.ant-input-number-affix-wrapper>div.ant-input-number.ant-input-number-focused{box-shadow:none!important}.ant-input-number-affix-wrapper input.ant-input-number-input{padding:0}.ant-input-number-affix-wrapper:before{width:0;visibility:hidden;content:"\a0"}.ant-input-number-affix-wrapper .ant-input-number-handler-wrap{z-index:2}.ant-input-number-prefix,.ant-input-number-suffix{display:flex;flex:none;align-items:center;pointer-events:none}.ant-input-number-prefix{margin-inline-end:4px}.ant-input-number-suffix{position:absolute;top:0;right:0;z-index:1;height:100%;margin-right:11px;margin-left:4px}.ant-input-number-group-wrapper .ant-input-number-affix-wrapper{width:100%}.ant-input-number-status-error:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number,.ant-input-number-status-error:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number:hover{background:#fff;border-color:#ff4d4f}.ant-input-number-status-error:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number:focus,.ant-input-number-status-error:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number-focused{border-color:#ff7875;box-shadow:0 0 0 2px #ff4d4f33;border-right-width:1px;outline:0}.ant-input-number-status-error .ant-input-number-prefix{color:#ff4d4f}.ant-input-number-status-warning:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number,.ant-input-number-status-warning:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number:hover{background:#fff;border-color:#faad14}.ant-input-number-status-warning:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number:focus,.ant-input-number-status-warning:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number-focused{border-color:#ffc53d;box-shadow:0 0 0 2px #faad1433;border-right-width:1px;outline:0}.ant-input-number-status-warning .ant-input-number-prefix{color:#faad14}.ant-input-number-affix-wrapper-status-error:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper,.ant-input-number-affix-wrapper-status-error:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper:hover{background:#fff;border-color:#ff4d4f}.ant-input-number-affix-wrapper-status-error:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper:focus,.ant-input-number-affix-wrapper-status-error:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper-focused{border-color:#ff7875;box-shadow:0 0 0 2px #ff4d4f33;border-right-width:1px;outline:0}.ant-input-number-affix-wrapper-status-error .ant-input-number-prefix{color:#ff4d4f}.ant-input-number-affix-wrapper-status-warning:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper,.ant-input-number-affix-wrapper-status-warning:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper:hover{background:#fff;border-color:#faad14}.ant-input-number-affix-wrapper-status-warning:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper:focus,.ant-input-number-affix-wrapper-status-warning:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper-focused{border-color:#ffc53d;box-shadow:0 0 0 2px #faad1433;border-right-width:1px;outline:0}.ant-input-number-affix-wrapper-status-warning .ant-input-number-prefix{color:#faad14}.ant-input-number-group-wrapper-status-error .ant-input-number-group-addon{color:#ff4d4f;border-color:#ff4d4f}.ant-input-number-group-wrapper-status-warning .ant-input-number-group-addon{color:#faad14;border-color:#faad14}.ant-input-number{box-sizing:border-box;font-variant:tabular-nums;list-style:none;font-feature-settings:"tnum";position:relative;width:100%;min-width:0;color:#000000d9;font-size:14px;line-height:1.5715;background-color:#fff;background-image:none;transition:all .3s;display:inline-block;width:90px;margin:0;padding:0;border:1px solid #d9d9d9;border-radius:4px}.ant-input-number::placeholder{color:#bfbfbf;-webkit-user-select:none;user-select:none}.ant-input-number:placeholder-shown{text-overflow:ellipsis}.ant-input-rtl .ant-input-number:hover{border-right-width:0;border-left-width:1px!important}.ant-input-number:focus,.ant-input-number-focused{border-color:#40a9ff;box-shadow:0 0 0 2px #1890ff33;border-right-width:1px;outline:0}.ant-input-rtl .ant-input-number:focus,.ant-input-rtl .ant-input-number-focused{border-right-width:0;border-left-width:1px!important}.ant-input-number[disabled]{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-number[disabled]:hover{border-color:#d9d9d9;border-right-width:1px}.ant-input-number-borderless,.ant-input-number-borderless:hover,.ant-input-number-borderless:focus,.ant-input-number-borderless-focused,.ant-input-number-borderless-disabled,.ant-input-number-borderless[disabled]{background-color:transparent;border:none;box-shadow:none}textarea.ant-input-number{max-width:100%;height:auto;min-height:32px;line-height:1.5715;vertical-align:bottom;transition:all .3s,height 0s}.ant-input-number-lg{padding:6.5px 11px;font-size:16px}.ant-input-number-sm{padding:0 7px}.ant-input-number-group{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:table;width:100%;border-collapse:separate;border-spacing:0}.ant-input-number-group[class*=col-]{float:none;padding-right:0;padding-left:0}.ant-input-number-group>[class*=col-]{padding-right:8px}.ant-input-number-group>[class*=col-]:last-child{padding-right:0}.ant-input-number-group-addon,.ant-input-number-group-wrap,.ant-input-number-group>.ant-input-number{display:table-cell}.ant-input-number-group-addon:not(:first-child):not(:last-child),.ant-input-number-group-wrap:not(:first-child):not(:last-child),.ant-input-number-group>.ant-input-number:not(:first-child):not(:last-child){border-radius:0}.ant-input-number-group-addon,.ant-input-number-group-wrap{width:1px;white-space:nowrap;vertical-align:middle}.ant-input-number-group-wrap>*{display:block!important}.ant-input-number-group .ant-input-number{float:left;width:100%;margin-bottom:0;text-align:inherit}.ant-input-number-group .ant-input-number:focus{z-index:1;border-right-width:1px}.ant-input-number-group .ant-input-number:hover{z-index:1;border-right-width:1px}.ant-input-search-with-button .ant-input-number-group .ant-input-number:hover{z-index:0}.ant-input-number-group-addon{position:relative;padding:0 11px;color:#000000d9;font-weight:400;font-size:14px;text-align:center;background-color:#fafafa;border:1px solid #d9d9d9;border-radius:4px;transition:all .3s}.ant-input-number-group-addon .ant-select{margin:-5px -11px}.ant-input-number-group-addon .ant-select.ant-select-single:not(.ant-select-customize-input) .ant-select-selector{background-color:inherit;border:1px solid transparent;box-shadow:none}.ant-input-number-group-addon .ant-select-open .ant-select-selector,.ant-input-number-group-addon .ant-select-focused .ant-select-selector{color:#1890ff}.ant-input-number-group-addon .ant-cascader-picker{margin:-9px -12px;background-color:transparent}.ant-input-number-group-addon .ant-cascader-picker .ant-cascader-input{text-align:left;border:0;box-shadow:none}.ant-input-number-group>.ant-input-number:first-child,.ant-input-number-group-addon:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-number-group>.ant-input-number:first-child .ant-select .ant-select-selector,.ant-input-number-group-addon:first-child .ant-select .ant-select-selector{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-number-group>.ant-input-number-affix-wrapper:not(:first-child) .ant-input-number{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-number-group>.ant-input-number-affix-wrapper:not(:last-child) .ant-input-number{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-number-group-addon:first-child{border-right:0}.ant-input-number-group-addon:last-child{border-left:0}.ant-input-number-group>.ant-input-number:last-child,.ant-input-number-group-addon:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-number-group>.ant-input-number:last-child .ant-select .ant-select-selector,.ant-input-number-group-addon:last-child .ant-select .ant-select-selector{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-number-group-lg .ant-input-number,.ant-input-number-group-lg>.ant-input-number-group-addon{padding:6.5px 11px;font-size:16px}.ant-input-number-group-sm .ant-input-number,.ant-input-number-group-sm>.ant-input-number-group-addon{padding:0 7px}.ant-input-number-group-lg .ant-select-single .ant-select-selector{height:40px}.ant-input-number-group-sm .ant-select-single .ant-select-selector{height:24px}.ant-input-number-group .ant-input-number-affix-wrapper:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-search .ant-input-number-group .ant-input-number-affix-wrapper:not(:last-child){border-top-left-radius:4px;border-bottom-left-radius:4px}.ant-input-number-group .ant-input-number-affix-wrapper:not(:first-child),.ant-input-search .ant-input-number-group .ant-input-number-affix-wrapper:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-number-group.ant-input-number-group-compact{display:block}.ant-input-number-group.ant-input-number-group-compact:before{display:table;content:""}.ant-input-number-group.ant-input-number-group-compact:after{display:table;clear:both;content:""}.ant-input-number-group.ant-input-number-group-compact-addon:not(:first-child):not(:last-child),.ant-input-number-group.ant-input-number-group-compact-wrap:not(:first-child):not(:last-child),.ant-input-number-group.ant-input-number-group-compact>.ant-input-number:not(:first-child):not(:last-child){border-right-width:1px}.ant-input-number-group.ant-input-number-group-compact-addon:not(:first-child):not(:last-child):hover,.ant-input-number-group.ant-input-number-group-compact-wrap:not(:first-child):not(:last-child):hover,.ant-input-number-group.ant-input-number-group-compact>.ant-input-number:not(:first-child):not(:last-child):hover{z-index:1}.ant-input-number-group.ant-input-number-group-compact-addon:not(:first-child):not(:last-child):focus,.ant-input-number-group.ant-input-number-group-compact-wrap:not(:first-child):not(:last-child):focus,.ant-input-number-group.ant-input-number-group-compact>.ant-input-number:not(:first-child):not(:last-child):focus{z-index:1}.ant-input-number-group.ant-input-number-group-compact>*{display:inline-block;float:none;vertical-align:top;border-radius:0}.ant-input-number-group.ant-input-number-group-compact>.ant-input-number-affix-wrapper{display:inline-flex}.ant-input-number-group.ant-input-number-group-compact>.ant-picker-range{display:inline-flex}.ant-input-number-group.ant-input-number-group-compact>*:not(:last-child){margin-right:-1px;border-right-width:1px}.ant-input-number-group.ant-input-number-group-compact .ant-input-number{float:none}.ant-input-number-group.ant-input-number-group-compact>.ant-select>.ant-select-selector,.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete .ant-input,.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker .ant-input,.ant-input-number-group.ant-input-number-group-compact>.ant-input-group-wrapper .ant-input{border-right-width:1px;border-radius:0}.ant-input-number-group.ant-input-number-group-compact>.ant-select>.ant-select-selector:hover,.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete .ant-input:hover,.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker .ant-input:hover,.ant-input-number-group.ant-input-number-group-compact>.ant-input-group-wrapper .ant-input:hover{z-index:1}.ant-input-number-group.ant-input-number-group-compact>.ant-select>.ant-select-selector:focus,.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete .ant-input:focus,.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker .ant-input:focus,.ant-input-number-group.ant-input-number-group-compact>.ant-input-group-wrapper .ant-input:focus{z-index:1}.ant-input-number-group.ant-input-number-group-compact>.ant-select-focused{z-index:1}.ant-input-number-group.ant-input-number-group-compact>.ant-select>.ant-select-arrow{z-index:1}.ant-input-number-group.ant-input-number-group-compact>*:first-child,.ant-input-number-group.ant-input-number-group-compact>.ant-select:first-child>.ant-select-selector,.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete:first-child .ant-input,.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker:first-child .ant-input{border-top-left-radius:4px;border-bottom-left-radius:4px}.ant-input-number-group.ant-input-number-group-compact>*:last-child,.ant-input-number-group.ant-input-number-group-compact>.ant-select:last-child>.ant-select-selector,.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker:last-child .ant-input,.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker-focused:last-child .ant-input{border-right-width:1px;border-top-right-radius:4px;border-bottom-right-radius:4px}.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete .ant-input{vertical-align:top}.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper+.ant-input-group-wrapper{margin-left:-1px}.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper+.ant-input-group-wrapper .ant-input-affix-wrapper{border-radius:0}.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper:not(:last-child).ant-input-search>.ant-input-group>.ant-input-group-addon>.ant-input-search-button{border-radius:0}.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper:not(:last-child).ant-input-search>.ant-input-group>.ant-input{border-radius:4px 0 0 4px}.ant-input-number-group>.ant-input-number-rtl:first-child,.ant-input-number-group-rtl .ant-input-number-group-addon:first-child{border-radius:0 4px 4px 0}.ant-input-number-group-rtl .ant-input-number-group-addon:first-child{border-right:1px solid #d9d9d9;border-left:0}.ant-input-number-group-rtl .ant-input-number-group-addon:last-child{border-right:0;border-left:1px solid #d9d9d9}.ant-input-number-group-rtl.ant-input-number-group>.ant-input-number:last-child,.ant-input-number-group-rtl.ant-input-number-group-addon:last-child{border-radius:4px 0 0 4px}.ant-input-number-group-rtl.ant-input-number-group .ant-input-number-affix-wrapper:not(:first-child){border-radius:4px 0 0 4px}.ant-input-number-group-rtl.ant-input-number-group .ant-input-number-affix-wrapper:not(:last-child){border-radius:0 4px 4px 0}.ant-input-number-group-rtl.ant-input-number-group.ant-input-number-group-compact>*:not(:last-child){margin-right:0;margin-left:-1px;border-left-width:1px}.ant-input-number-group-rtl.ant-input-number-group.ant-input-number-group-compact>*:first-child,.ant-input-number-group-rtl.ant-input-number-group.ant-input-number-group-compact>.ant-select:first-child>.ant-select-selector,.ant-input-number-group-rtl.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete:first-child .ant-input,.ant-input-number-group-rtl.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker:first-child .ant-input{border-radius:0 4px 4px 0}.ant-input-number-group-rtl.ant-input-number-group.ant-input-number-group-compact>*:last-child,.ant-input-number-group-rtl.ant-input-number-group.ant-input-number-group-compact>.ant-select:last-child>.ant-select-selector,.ant-input-number-group-rtl.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete:last-child .ant-input,.ant-input-number-group-rtl.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker:last-child .ant-input,.ant-input-number-group-rtl.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker-focused:last-child .ant-input{border-left-width:1px;border-radius:4px 0 0 4px}.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper-rtl+.ant-input-group-wrapper-rtl{margin-right:-1px;margin-left:0}.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper-rtl:not(:last-child).ant-input-search>.ant-input-group>.ant-input{border-radius:0 4px 4px 0}.ant-input-number-group-wrapper{display:inline-block;text-align:start;vertical-align:top}.ant-input-number-handler{position:relative;display:block;width:100%;height:50%;overflow:hidden;color:#00000073;font-weight:700;line-height:0;text-align:center;border-left:1px solid #d9d9d9;transition:all .1s linear}.ant-input-number-handler:active{background:#f4f4f4}.ant-input-number-handler:hover .ant-input-number-handler-up-inner,.ant-input-number-handler:hover .ant-input-number-handler-down-inner{color:#40a9ff}.ant-input-number-handler-up-inner,.ant-input-number-handler-down-inner{display:inline-block;color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizelegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;right:4px;width:12px;height:12px;color:#00000073;line-height:12px;transition:all .1s linear;-webkit-user-select:none;user-select:none}.ant-input-number-handler-up-inner>*,.ant-input-number-handler-down-inner>*{line-height:1}.ant-input-number-handler-up-inner svg,.ant-input-number-handler-down-inner svg{display:inline-block}.ant-input-number-handler-up-inner:before,.ant-input-number-handler-down-inner:before{display:none}.ant-input-number-handler-up-inner .ant-input-number-handler-up-inner-icon,.ant-input-number-handler-up-inner .ant-input-number-handler-down-inner-icon,.ant-input-number-handler-down-inner .ant-input-number-handler-up-inner-icon,.ant-input-number-handler-down-inner .ant-input-number-handler-down-inner-icon{display:block}.ant-input-number:hover{border-color:#40a9ff;border-right-width:1px}.ant-input-number:hover+.ant-form-item-children-icon{opacity:0;transition:opacity .24s linear .24s}.ant-input-number-focused{border-color:#40a9ff;box-shadow:0 0 0 2px #1890ff33;border-right-width:1px;outline:0}.ant-input-rtl .ant-input-number-focused{border-right-width:0;border-left-width:1px!important}.ant-input-number-disabled{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-number-disabled:hover{border-color:#d9d9d9;border-right-width:1px}.ant-input-number-disabled .ant-input-number-input{cursor:not-allowed}.ant-input-number-disabled .ant-input-number-handler-wrap,.ant-input-number-readonly .ant-input-number-handler-wrap{display:none}.ant-input-number-input{width:100%;height:30px;padding:0 11px;text-align:left;background-color:transparent;border:0;border-radius:4px;outline:0;transition:all .3s linear;-webkit-appearance:textfield!important;appearance:textfield!important}.ant-input-number-input::placeholder{color:#bfbfbf;-webkit-user-select:none;user-select:none}.ant-input-number-input:placeholder-shown{text-overflow:ellipsis}.ant-input-number-input[type=number]::-webkit-inner-spin-button,.ant-input-number-input[type=number]::-webkit-outer-spin-button{margin:0;-webkit-appearance:none;appearance:none}.ant-input-number-lg{padding:0;font-size:16px}.ant-input-number-lg input{height:38px}.ant-input-number-sm{padding:0}.ant-input-number-sm input{height:22px;padding:0 7px}.ant-input-number-handler-wrap{position:absolute;top:0;right:0;width:22px;height:100%;background:#fff;border-radius:0 4px 4px 0;opacity:0;transition:opacity .24s linear .1s}.ant-input-number-handler-wrap .ant-input-number-handler .ant-input-number-handler-up-inner,.ant-input-number-handler-wrap .ant-input-number-handler .ant-input-number-handler-down-inner{display:flex;align-items:center;justify-content:center;min-width:auto;margin-right:0;font-size:7px}.ant-input-number-borderless .ant-input-number-handler-wrap{border-left-width:0}.ant-input-number-handler-wrap:hover .ant-input-number-handler{height:40%}.ant-input-number:hover .ant-input-number-handler-wrap,.ant-input-number-focused .ant-input-number-handler-wrap{opacity:1}.ant-input-number-handler-up{border-top-right-radius:4px;cursor:pointer}.ant-input-number-handler-up-inner{top:50%;margin-top:-5px;text-align:center}.ant-input-number-handler-up:hover{height:60%!important}.ant-input-number-handler-down{top:0;border-top:1px solid #d9d9d9;border-bottom-right-radius:4px;cursor:pointer}.ant-input-number-handler-down-inner{top:50%;text-align:center;transform:translateY(-50%)}.ant-input-number-handler-down:hover{height:60%!important}.ant-input-number-borderless .ant-input-number-handler-down{border-top-width:0}.ant-input-number-handler-up-disabled,.ant-input-number-handler-down-disabled{cursor:not-allowed}.ant-input-number-handler-up-disabled:hover .ant-input-number-handler-up-inner,.ant-input-number-handler-down-disabled:hover .ant-input-number-handler-down-inner{color:#00000040}.ant-input-number-borderless{box-shadow:none}.ant-input-number-out-of-range input{color:#ff4d4f}.ant-input-number-rtl{direction:rtl}.ant-input-number-rtl .ant-input-number-handler{border-right:1px solid #d9d9d9;border-left:0}.ant-input-number-rtl .ant-input-number-handler-wrap{right:auto;left:0}.ant-input-number-rtl.ant-input-number-borderless .ant-input-number-handler-wrap{border-right-width:0}.ant-input-number-rtl .ant-input-number-handler-up{border-top-right-radius:0}.ant-input-number-rtl .ant-input-number-handler-down{border-bottom-right-radius:0}.ant-input-number-rtl .ant-input-number-input{direction:ltr;text-align:right}.ant-input-number-affix-wrapper>nz-input-number.ant-input-number{width:100%;border:none;outline:none}.ant-input-number-affix-wrapper>nz-input-number.ant-input-number.ant-input-number-focused{box-shadow:none!important}.ant-input-number.ant-input-number-has-feedback .ant-input-number-handler-wrap{z-index:2}.ant-layout{display:flex;flex:auto;flex-direction:column;min-height:0;background:#f0f2f5}.ant-layout,.ant-layout *{box-sizing:border-box}.ant-layout.ant-layout-has-sider{flex-direction:row}.ant-layout.ant-layout-has-sider>.ant-layout,.ant-layout.ant-layout-has-sider>.ant-layout-content{width:0}.ant-layout-header,.ant-layout-footer{flex:0 0 auto}.ant-layout-header{height:64px;padding:0 50px;color:#000000d9;line-height:64px;background:#001529}.ant-layout-footer{padding:24px 50px;color:#000000d9;font-size:14px;background:#f0f2f5}.ant-layout-content{flex:auto;min-height:0}.ant-layout-sider{position:relative;min-width:0;background:#001529;transition:all .2s}.ant-layout-sider-children{height:100%;margin-top:-.1px;padding-top:.1px}.ant-layout-sider-children .ant-menu.ant-menu-inline-collapsed{width:auto}.ant-layout-sider-has-trigger{padding-bottom:48px}.ant-layout-sider-right{order:1}.ant-layout-sider-trigger{position:fixed;bottom:0;z-index:1;height:48px;color:#fff;line-height:48px;text-align:center;background:#002140;cursor:pointer;transition:all .2s}.ant-layout-sider-zero-width>*{overflow:hidden}.ant-layout-sider-zero-width-trigger{position:absolute;top:64px;right:-36px;z-index:1;width:36px;height:42px;color:#fff;font-size:18px;line-height:42px;text-align:center;background:#001529;border-radius:0 4px 4px 0;cursor:pointer;transition:background .3s ease}.ant-layout-sider-zero-width-trigger:after{position:absolute;inset:0;background:transparent;transition:all .3s;content:""}.ant-layout-sider-zero-width-trigger:hover:after{background:rgba(255,255,255,.1)}.ant-layout-sider-zero-width-trigger-right{left:-36px;border-radius:4px 0 0 4px}.ant-layout-sider-light{background:#fff}.ant-layout-sider-light .ant-layout-sider-trigger,.ant-layout-sider-light .ant-layout-sider-zero-width-trigger{color:#000000d9;background:#fff}.ant-layout-rtl{direction:rtl}nz-content{display:block}nz-footer{display:block}nz-header{display:block}.ant-form-item .ant-input-number+.ant-form-text{margin-left:8px}.ant-form-inline{display:flex;flex-wrap:wrap}.ant-form-inline .ant-form-item{flex:none;flex-wrap:nowrap;margin-right:16px;margin-bottom:0}.ant-form-inline .ant-form-item-with-help{margin-bottom:24px}.ant-form-inline .ant-form-item>.ant-form-item-label,.ant-form-inline .ant-form-item>.ant-form-item-control{display:inline-block;vertical-align:top}.ant-form-inline .ant-form-item>.ant-form-item-label{flex:none}.ant-form-inline .ant-form-item .ant-form-text,.ant-form-inline .ant-form-item .ant-form-item-has-feedback{display:inline-block}.ant-form-horizontal .ant-form-item-label{flex-grow:0}.ant-form-horizontal .ant-form-item-control{flex:1 1 0;min-width:0}.ant-form-horizontal .ant-form-item-label[class$="-24"]+.ant-form-item-control,.ant-form-horizontal .ant-form-item-label[class*="-24 "]+.ant-form-item-control{min-width:unset}.ant-form-vertical .ant-form-item{flex-direction:column}.ant-form-vertical .ant-form-item-label>label{height:auto}.ant-form-vertical .ant-form-item-label,.ant-col-24.ant-form-item-label,.ant-col-xl-24.ant-form-item-label{padding:0 0 8px;line-height:1.5715;white-space:initial;text-align:left}.ant-form-vertical .ant-form-item-label>label,.ant-col-24.ant-form-item-label>label,.ant-col-xl-24.ant-form-item-label>label{margin:0}.ant-form-vertical .ant-form-item-label>label:after,.ant-col-24.ant-form-item-label>label:after,.ant-col-xl-24.ant-form-item-label>label:after{display:none}.ant-form-rtl.ant-form-vertical .ant-form-item-label,.ant-form-rtl.ant-col-24.ant-form-item-label,.ant-form-rtl.ant-col-xl-24.ant-form-item-label{text-align:right}@media (max-width: 575px){.ant-form-item .ant-form-item-label{padding:0 0 8px;line-height:1.5715;white-space:initial;text-align:left}.ant-form-item .ant-form-item-label>label{margin:0}.ant-form-item .ant-form-item-label>label:after{display:none}.ant-form-rtl.ant-form-item .ant-form-item-label{text-align:right}.ant-form .ant-form-item{flex-wrap:wrap}.ant-form .ant-form-item .ant-form-item-label,.ant-form .ant-form-item .ant-form-item-control{flex:0 0 100%;max-width:100%}.ant-col-xs-24.ant-form-item-label{padding:0 0 8px;line-height:1.5715;white-space:initial;text-align:left}.ant-col-xs-24.ant-form-item-label>label{margin:0}.ant-col-xs-24.ant-form-item-label>label:after{display:none}.ant-form-rtl.ant-col-xs-24.ant-form-item-label{text-align:right}}@media (max-width: 767px){.ant-col-sm-24.ant-form-item-label{padding:0 0 8px;line-height:1.5715;white-space:initial;text-align:left}.ant-col-sm-24.ant-form-item-label>label{margin:0}.ant-col-sm-24.ant-form-item-label>label:after{display:none}.ant-form-rtl.ant-col-sm-24.ant-form-item-label{text-align:right}}@media (max-width: 991px){.ant-col-md-24.ant-form-item-label{padding:0 0 8px;line-height:1.5715;white-space:initial;text-align:left}.ant-col-md-24.ant-form-item-label>label{margin:0}.ant-col-md-24.ant-form-item-label>label:after{display:none}.ant-form-rtl.ant-col-md-24.ant-form-item-label{text-align:right}}@media (max-width: 1199px){.ant-col-lg-24.ant-form-item-label{padding:0 0 8px;line-height:1.5715;white-space:initial;text-align:left}.ant-col-lg-24.ant-form-item-label>label{margin:0}.ant-col-lg-24.ant-form-item-label>label:after{display:none}.ant-form-rtl.ant-col-lg-24.ant-form-item-label{text-align:right}}@media (max-width: 1599px){.ant-col-xl-24.ant-form-item-label{padding:0 0 8px;line-height:1.5715;white-space:initial;text-align:left}.ant-col-xl-24.ant-form-item-label>label{margin:0}.ant-col-xl-24.ant-form-item-label>label:after{display:none}.ant-form-rtl.ant-col-xl-24.ant-form-item-label{text-align:right}}.ant-form-item-explain-error{color:#ff4d4f}.ant-form-item-explain-warning{color:#faad14}.ant-form-item-has-feedback .ant-switch{margin:2px 0 4px}.ant-form-item-has-warning .ant-form-item-split{color:#faad14}.ant-form-item-has-error .ant-form-item-split{color:#ff4d4f}.ant-form{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum"}.ant-form legend{display:block;width:100%;margin-bottom:20px;padding:0;color:#00000073;font-size:16px;line-height:inherit;border:0;border-bottom:1px solid #d9d9d9}.ant-form label{font-size:14px}.ant-form input[type=search]{box-sizing:border-box}.ant-form input[type=radio],.ant-form input[type=checkbox]{line-height:normal}.ant-form input[type=file]{display:block}.ant-form input[type=range]{display:block;width:100%}.ant-form select[multiple],.ant-form select[size]{height:auto}.ant-form input[type=file]:focus,.ant-form input[type=radio]:focus,.ant-form input[type=checkbox]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.ant-form output{display:block;padding-top:15px;color:#000000d9;font-size:14px;line-height:1.5715}.ant-form .ant-form-text{display:inline-block;padding-right:8px}.ant-form-small .ant-form-item-label>label{height:24px}.ant-form-small .ant-form-item-control-input{min-height:24px}.ant-form-large .ant-form-item-label>label{height:40px}.ant-form-large .ant-form-item-control-input{min-height:40px}.ant-form-item{box-sizing:border-box;margin:0 0 24px;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";vertical-align:top;transition:margin-bottom .3s 17ms linear}.ant-form-item-with-help{margin-bottom:0;transition:none}.ant-form-item-hidden,.ant-form-item-hidden.ant-row{display:none}.ant-form-item-label{display:inline-block;flex-grow:0;overflow:hidden;white-space:nowrap;text-align:right;vertical-align:middle}.ant-form-item-label-left{text-align:left}.ant-form-item-label-wrap{overflow:unset;line-height:1.3215em;white-space:unset}.ant-form-item-label>label{position:relative;display:inline-flex;align-items:center;max-width:100%;height:32px;color:#000000d9;font-size:14px}.ant-form-item-label>label>.anticon{font-size:14px;vertical-align:top}.ant-form-hide-required-mark .ant-form-item-label>label.ant-form-item-required:not(.ant-form-item-required-mark-optional):before{display:none}.ant-form-item-label>label .ant-form-item-optional{display:inline-block;margin-left:4px;color:#00000073}.ant-form-hide-required-mark .ant-form-item-label>label .ant-form-item-optional{display:none}.ant-form-item-label>label .ant-form-item-tooltip{color:#00000073;cursor:help;writing-mode:horizontal-tb;margin-inline-start:4px}.ant-form-item-label>label:after{content:":";position:relative;top:-.5px;margin:0 8px 0 2px}.ant-form-item-label>label.ant-form-item-no-colon:after{content:" "}.ant-form-item-control{display:flex;flex-direction:column;flex-grow:1}.ant-form-item-control:first-child:not([class^="ant-col-"]):not([class*=" ant-col-"]){width:100%}.ant-form-item-control-input{position:relative;display:flex;align-items:center;min-height:32px}.ant-form-item-control-input-content{flex:auto;max-width:100%}.ant-form-item-explain,.ant-form-item-extra{clear:both;color:#00000073;font-size:14px;line-height:1.5715;transition:color .3s cubic-bezier(.215,.61,.355,1)}.ant-form-item-explain-connected{height:0;min-height:0;opacity:0}.ant-form-item-extra{min-height:24px}.ant-form-item-with-help .ant-form-item-explain{height:auto;min-height:24px;opacity:1}.ant-form-item-feedback-icon{font-size:14px;text-align:center;visibility:visible;animation:zoomIn .3s cubic-bezier(.12,.4,.29,1.46);pointer-events:none}.ant-form-item-feedback-icon-success{color:#52c41a}.ant-form-item-feedback-icon-error{color:#ff4d4f}.ant-form-item-feedback-icon-warning{color:#faad14}.ant-form-item-feedback-icon-validating{color:#1890ff}.ant-show-help{transition:height .3s linear,min-height .3s linear,margin-bottom .3s cubic-bezier(.645,.045,.355,1),opacity .3s cubic-bezier(.645,.045,.355,1)}.ant-show-help-leave{min-height:24px}.ant-show-help-leave-active{min-height:0}.ant-show-help-item{overflow:hidden;transition:height .3s cubic-bezier(.645,.045,.355,1),opacity .3s cubic-bezier(.645,.045,.355,1),transform .3s cubic-bezier(.645,.045,.355,1)!important}.ant-show-help-item-appear,.ant-show-help-item-enter{transform:translateY(-5px);opacity:0}.ant-show-help-item-appear-active,.ant-show-help-item-enter-active{transform:translateY(0);opacity:1}.ant-show-help-item-leave-active{transform:translateY(-5px)}@keyframes diffZoomIn1{0%{transform:scale(0);opacity:0}to{transform:scale(1);opacity:1}}@keyframes diffZoomIn2{0%{transform:scale(0);opacity:0}to{transform:scale(1);opacity:1}}@keyframes diffZoomIn3{0%{transform:scale(0);opacity:0}to{transform:scale(1);opacity:1}}.ant-form-rtl{direction:rtl}.ant-form-rtl .ant-form-item-label{text-align:left}.ant-form-rtl .ant-form-item-label>label.ant-form-item-required:before{margin-right:0;margin-left:4px}.ant-form-rtl .ant-form-item-label>label:after{margin:0 2px 0 8px}.ant-form-rtl .ant-form-item-label>label .ant-form-item-optional{margin-right:4px;margin-left:0}.ant-col-rtl .ant-form-item-control:first-child{width:100%}.ant-form-rtl .ant-form-item-has-feedback .ant-input{padding-right:11px;padding-left:24px}.ant-form-rtl .ant-form-item-has-feedback .ant-input-affix-wrapper .ant-input-suffix{padding-right:11px;padding-left:18px}.ant-form-rtl .ant-form-item-has-feedback .ant-input-affix-wrapper .ant-input,.ant-form-rtl .ant-form-item-has-feedback .ant-input-number-affix-wrapper .ant-input-number{padding:0}.ant-form-rtl .ant-form-item-has-feedback .ant-input-search:not(.ant-input-search-enter-button) .ant-input-suffix{right:auto;left:28px}.ant-form-rtl .ant-form-item-has-feedback .ant-input-number{padding-left:18px}.ant-form-rtl .ant-form-item-has-feedback>.ant-select .ant-select-arrow,.ant-form-rtl .ant-form-item-has-feedback>.ant-select .ant-select-clear,.ant-form-rtl .ant-form-item-has-feedback :not(.ant-input-group-addon)>.ant-select .ant-select-arrow,.ant-form-rtl .ant-form-item-has-feedback :not(.ant-input-group-addon)>.ant-select .ant-select-clear,.ant-form-rtl .ant-form-item-has-feedback :not(.ant-input-number-group-addon)>.ant-select .ant-select-arrow,.ant-form-rtl .ant-form-item-has-feedback :not(.ant-input-number-group-addon)>.ant-select .ant-select-clear{right:auto;left:32px}.ant-form-rtl .ant-form-item-has-feedback>.ant-select .ant-select-selection-selected-value,.ant-form-rtl .ant-form-item-has-feedback :not(.ant-input-group-addon)>.ant-select .ant-select-selection-selected-value,.ant-form-rtl .ant-form-item-has-feedback :not(.ant-input-number-group-addon)>.ant-select .ant-select-selection-selected-value{padding-right:0;padding-left:42px}.ant-form-rtl .ant-form-item-has-feedback .ant-cascader-picker-arrow{margin-right:0;margin-left:19px}.ant-form-rtl .ant-form-item-has-feedback .ant-cascader-picker-clear{right:auto;left:32px}.ant-form-rtl .ant-form-item-has-feedback .ant-picker,.ant-form-rtl .ant-form-item-has-feedback .ant-picker-large{padding-right:11px;padding-left:29.2px}.ant-form-rtl .ant-form-item-has-feedback .ant-picker-small{padding-right:7px;padding-left:25.2px}.ant-form-rtl .ant-form-item-has-feedback.ant-form-item-has-success .ant-form-item-children-icon,.ant-form-rtl .ant-form-item-has-feedback.ant-form-item-has-warning .ant-form-item-children-icon,.ant-form-rtl .ant-form-item-has-feedback.ant-form-item-has-error .ant-form-item-children-icon,.ant-form-rtl .ant-form-item-has-feedback.ant-form-item-is-validating .ant-form-item-children-icon{right:auto;left:0}.ant-form-rtl.ant-form-inline .ant-form-item{margin-right:0;margin-left:16px}nz-form-item.ant-form-item{transition:none}.ant-list{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative}.ant-list *{outline:none}.ant-list-pagination{margin-top:24px;text-align:right}.ant-list-pagination .ant-pagination-options{text-align:left}.ant-list-more{margin-top:12px;text-align:center}.ant-list-more button{padding-right:32px;padding-left:32px}.ant-list-spin{min-height:40px;text-align:center}.ant-list-empty-text{padding:16px;color:#00000040;font-size:14px;text-align:center}.ant-list-items{margin:0;padding:0;list-style:none}.ant-list-item{display:flex;align-items:center;justify-content:space-between;padding:12px 0;color:#000000d9}.ant-list-item-meta{display:flex;flex:1;align-items:flex-start;max-width:100%}.ant-list-item-meta-avatar{margin-right:16px}.ant-list-item-meta-content{flex:1 0;width:0;color:#000000d9}.ant-list-item-meta-title{margin-bottom:4px;color:#000000d9;font-size:14px;line-height:1.5715}.ant-list-item-meta-title>a{color:#000000d9;transition:all .3s}.ant-list-item-meta-title>a:hover{color:#1890ff}.ant-list-item-meta-description{color:#00000073;font-size:14px;line-height:1.5715}.ant-list-item-action{flex:0 0 auto;margin-left:48px;padding:0;font-size:0;list-style:none}.ant-list-item-action>li{position:relative;display:inline-block;padding:0 8px;color:#00000073;font-size:14px;line-height:1.5715;text-align:center}.ant-list-item-action>li:first-child{padding-left:0}.ant-list-item-action-split{position:absolute;top:50%;right:0;width:1px;height:14px;margin-top:-7px;background-color:#f0f0f0}.ant-list-header,.ant-list-footer{background:transparent}.ant-list-header,.ant-list-footer{padding-top:12px;padding-bottom:12px}.ant-list-empty{padding:16px 0;color:#00000073;font-size:12px;text-align:center}.ant-list-split .ant-list-item{border-bottom:1px solid #f0f0f0}.ant-list-split .ant-list-item:last-child{border-bottom:none}.ant-list-split .ant-list-header{border-bottom:1px solid #f0f0f0}.ant-list-split.ant-list-empty .ant-list-footer{border-top:1px solid #f0f0f0}.ant-list-loading .ant-list-spin-nested-loading{min-height:32px}.ant-list-split.ant-list-something-after-last-item .ant-spin-container>.ant-list-items>.ant-list-item:last-child{border-bottom:1px solid #f0f0f0}.ant-list-lg .ant-list-item{padding:16px 24px}.ant-list-sm .ant-list-item{padding:8px 16px}.ant-list-vertical .ant-list-item{align-items:initial}.ant-list-vertical .ant-list-item-main{display:block;flex:1}.ant-list-vertical .ant-list-item-extra{margin-left:40px}.ant-list-vertical .ant-list-item-meta{margin-bottom:16px}.ant-list-vertical .ant-list-item-meta-title{margin-bottom:12px;color:#000000d9;font-size:16px;line-height:24px}.ant-list-vertical .ant-list-item-action{margin-top:16px;margin-left:auto}.ant-list-vertical .ant-list-item-action>li{padding:0 16px}.ant-list-vertical .ant-list-item-action>li:first-child{padding-left:0}.ant-list-grid .ant-col>.ant-list-item{display:block;max-width:100%;margin-bottom:16px;padding-top:0;padding-bottom:0;border-bottom:none}.ant-list-item-no-flex{display:block}.ant-list:not(.ant-list-vertical) .ant-list-item-no-flex .ant-list-item-action{float:right}.ant-list-bordered{border:1px solid #d9d9d9;border-radius:4px}.ant-list-bordered .ant-list-header,.ant-list-bordered .ant-list-footer,.ant-list-bordered .ant-list-item{padding-right:24px;padding-left:24px}.ant-list-bordered .ant-list-pagination{margin:16px 24px}.ant-list-bordered.ant-list-sm .ant-list-item,.ant-list-bordered.ant-list-sm .ant-list-header,.ant-list-bordered.ant-list-sm .ant-list-footer{padding:8px 16px}.ant-list-bordered.ant-list-lg .ant-list-item,.ant-list-bordered.ant-list-lg .ant-list-header,.ant-list-bordered.ant-list-lg .ant-list-footer{padding:16px 24px}@media screen and (max-width: 768px){.ant-list-item-action,.ant-list-vertical .ant-list-item-extra{margin-left:24px}}@media screen and (max-width: 576px){.ant-list-item{flex-wrap:wrap}.ant-list-item-action{margin-left:12px}.ant-list-vertical .ant-list-item{flex-wrap:wrap-reverse}.ant-list-vertical .ant-list-item-main{min-width:220px}.ant-list-vertical .ant-list-item-extra{margin:auto auto 16px}}.ant-list-rtl{direction:rtl;text-align:right}.ant-list-rtl .ReactVirtualized__List .ant-list-item{direction:rtl}.ant-list-rtl .ant-list-pagination{text-align:left}.ant-list-rtl .ant-list-item-meta-avatar{margin-right:0;margin-left:16px}.ant-list-rtl .ant-list-item-action{margin-right:48px;margin-left:0}.ant-list.ant-list-rtl .ant-list-item-action>li:first-child{padding-right:0;padding-left:16px}.ant-list-rtl .ant-list-item-action-split{right:auto;left:0}.ant-list-rtl.ant-list-vertical .ant-list-item-extra{margin-right:40px;margin-left:0}.ant-list-rtl.ant-list-vertical .ant-list-item-action{margin-right:auto}.ant-list-rtl .ant-list-vertical .ant-list-item-action>li:first-child{padding-right:0;padding-left:16px}.ant-list-rtl .ant-list:not(.ant-list-vertical) .ant-list-item-no-flex .ant-list-item-action{float:left}@media screen and (max-width: 768px){.ant-list-rtl .ant-list-item-action,.ant-list-rtl .ant-list-vertical .ant-list-item-extra{margin-right:24px;margin-left:0}}@media screen and (max-width: 576px){.ant-list-rtl .ant-list-item-action{margin-right:22px;margin-left:0}.ant-list-rtl.ant-list-vertical .ant-list-item-extra{margin:auto auto 16px}}nz-list,nz-list nz-spin,nz-list-header,nz-list-footer,nz-list-pagination,nz-list-empty,nz-list-item-extra{display:block}.ant-menu-item-danger.ant-menu-item,.ant-menu-item-danger.ant-menu-item:hover,.ant-menu-item-danger.ant-menu-item-active{color:#ff4d4f}.ant-menu-item-danger.ant-menu-item:active{background:#fff1f0}.ant-menu-item-danger.ant-menu-item-selected{color:#ff4d4f}.ant-menu-item-danger.ant-menu-item-selected>a,.ant-menu-item-danger.ant-menu-item-selected>a:hover{color:#ff4d4f}.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-danger.ant-menu-item-selected{background-color:#fff1f0}.ant-menu-inline .ant-menu-item-danger.ant-menu-item:after{border-right-color:#ff4d4f}.ant-menu-dark .ant-menu-item-danger.ant-menu-item,.ant-menu-dark .ant-menu-item-danger.ant-menu-item:hover,.ant-menu-dark .ant-menu-item-danger.ant-menu-item>a{color:#ff4d4f}.ant-menu-dark.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-danger.ant-menu-item-selected{color:#fff;background-color:#ff4d4f}.ant-menu{box-sizing:border-box;margin:0;font-variant:tabular-nums;line-height:1.5715;font-feature-settings:"tnum";padding:0;color:#000000d9;font-size:14px;line-height:0;text-align:left;list-style:none;background:#fff;outline:none;box-shadow:0 3px 6px -4px #0000001f,0 6px 16px #00000014,0 9px 28px 8px #0000000d;transition:background .3s,width .3s cubic-bezier(.2,0,0,1) 0s}.ant-menu:before{display:table;content:""}.ant-menu:after{display:table;clear:both;content:""}.ant-menu.ant-menu-root:focus-visible{box-shadow:0 0 0 2px #bae7ff}.ant-menu ul,.ant-menu ol{margin:0;padding:0;list-style:none}.ant-menu-overflow{display:flex}.ant-menu-overflow-item{flex:none}.ant-menu-hidden,.ant-menu-submenu-hidden{display:none}.ant-menu-item-group-title{height:1.5715;padding:8px 16px;color:#00000073;font-size:14px;line-height:1.5715;transition:all .3s}.ant-menu-horizontal .ant-menu-submenu{transition:border-color .3s cubic-bezier(.645,.045,.355,1),background .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-submenu,.ant-menu-submenu-inline{transition:border-color .3s cubic-bezier(.645,.045,.355,1),background .3s cubic-bezier(.645,.045,.355,1),padding .15s cubic-bezier(.645,.045,.355,1)}.ant-menu-submenu-selected{color:#1890ff}.ant-menu-item:active,.ant-menu-submenu-title:active{background:#e6f7ff}.ant-menu-submenu .ant-menu-sub{cursor:initial;transition:background .3s cubic-bezier(.645,.045,.355,1),padding .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-title-content{transition:color .3s}.ant-menu-item a{color:#000000d9}.ant-menu-item a:hover{color:#1890ff}.ant-menu-item a:before{position:absolute;inset:0;background-color:transparent;content:""}.ant-menu-item>.ant-badge a{color:#000000d9}.ant-menu-item>.ant-badge a:hover{color:#1890ff}.ant-menu-item-divider{overflow:hidden;line-height:0;border-color:#f0f0f0;border-style:solid;border-width:1px 0 0}.ant-menu-item-divider-dashed{border-style:dashed}.ant-menu-horizontal .ant-menu-item,.ant-menu-horizontal .ant-menu-submenu{margin-top:-1px}.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-submenu .ant-menu-submenu-title:hover{background-color:transparent}.ant-menu-item-selected,.ant-menu-item-selected a,.ant-menu-item-selected a:hover{color:#1890ff}.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected{background-color:#e6f7ff}.ant-menu-inline,.ant-menu-vertical,.ant-menu-vertical-left{border-right:1px solid #f0f0f0}.ant-menu-vertical-right{border-left:1px solid #f0f0f0}.ant-menu-vertical.ant-menu-sub,.ant-menu-vertical-left.ant-menu-sub,.ant-menu-vertical-right.ant-menu-sub{min-width:160px;max-height:calc(100vh - 100px);padding:0;overflow:hidden;border-right:0}.ant-menu-vertical.ant-menu-sub:not([class*="-active"]),.ant-menu-vertical-left.ant-menu-sub:not([class*="-active"]),.ant-menu-vertical-right.ant-menu-sub:not([class*="-active"]){overflow-x:hidden;overflow-y:auto}.ant-menu-vertical.ant-menu-sub .ant-menu-item,.ant-menu-vertical-left.ant-menu-sub .ant-menu-item,.ant-menu-vertical-right.ant-menu-sub .ant-menu-item{left:0;margin-left:0;border-right:0}.ant-menu-vertical.ant-menu-sub .ant-menu-item:after,.ant-menu-vertical-left.ant-menu-sub .ant-menu-item:after,.ant-menu-vertical-right.ant-menu-sub .ant-menu-item:after{border-right:0}.ant-menu-vertical.ant-menu-sub>.ant-menu-item,.ant-menu-vertical-left.ant-menu-sub>.ant-menu-item,.ant-menu-vertical-right.ant-menu-sub>.ant-menu-item,.ant-menu-vertical.ant-menu-sub>.ant-menu-submenu,.ant-menu-vertical-left.ant-menu-sub>.ant-menu-submenu,.ant-menu-vertical-right.ant-menu-sub>.ant-menu-submenu{transform-origin:0 0}.ant-menu-horizontal.ant-menu-sub{min-width:114px}.ant-menu-horizontal .ant-menu-item,.ant-menu-horizontal .ant-menu-submenu-title{transition:border-color .3s,background .3s}.ant-menu-item,.ant-menu-submenu-title{position:relative;display:block;margin:0;padding:0 20px;white-space:nowrap;cursor:pointer;transition:border-color .3s,background .3s,padding .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-item .ant-menu-item-icon,.ant-menu-submenu-title .ant-menu-item-icon,.ant-menu-item .anticon,.ant-menu-submenu-title .anticon{min-width:14px;font-size:14px;transition:font-size .15s cubic-bezier(.215,.61,.355,1),margin .3s cubic-bezier(.645,.045,.355,1),color .3s}.ant-menu-item .ant-menu-item-icon+span,.ant-menu-submenu-title .ant-menu-item-icon+span,.ant-menu-item .anticon+span,.ant-menu-submenu-title .anticon+span{margin-left:10px;opacity:1;transition:opacity .3s cubic-bezier(.645,.045,.355,1),margin .3s,color .3s}.ant-menu-item .ant-menu-item-icon.svg,.ant-menu-submenu-title .ant-menu-item-icon.svg{vertical-align:-.125em}.ant-menu-item.ant-menu-item-only-child>.anticon,.ant-menu-submenu-title.ant-menu-item-only-child>.anticon,.ant-menu-item.ant-menu-item-only-child>.ant-menu-item-icon,.ant-menu-submenu-title.ant-menu-item-only-child>.ant-menu-item-icon{margin-right:0}.ant-menu-item:focus-visible,.ant-menu-submenu-title:focus-visible{box-shadow:0 0 0 2px #bae7ff}.ant-menu>.ant-menu-item-divider{margin:1px 0;padding:0}.ant-menu-submenu-popup{position:absolute;z-index:1050;background:transparent;border-radius:4px;box-shadow:none;transform-origin:0 0}.ant-menu-submenu-popup:before{position:absolute;inset:-7px 0 0;z-index:-1;width:100%;height:100%;opacity:.0001;content:" "}.ant-menu-submenu-placement-rightTop:before{top:0;left:-7px}.ant-menu-submenu>.ant-menu{background-color:#fff;border-radius:4px}.ant-menu-submenu>.ant-menu-submenu-title:after{transition:transform .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-submenu-popup>.ant-menu{background-color:#fff}.ant-menu-submenu-expand-icon,.ant-menu-submenu-arrow{position:absolute;top:50%;right:16px;width:10px;color:#000000d9;transform:translateY(-50%);transition:transform .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-submenu-arrow:before,.ant-menu-submenu-arrow:after{position:absolute;width:6px;height:1.5px;background-color:currentcolor;border-radius:2px;transition:background .3s cubic-bezier(.645,.045,.355,1),transform .3s cubic-bezier(.645,.045,.355,1),top .3s cubic-bezier(.645,.045,.355,1),color .3s cubic-bezier(.645,.045,.355,1);content:""}.ant-menu-submenu-arrow:before{transform:rotate(45deg) translateY(-2.5px)}.ant-menu-submenu-arrow:after{transform:rotate(-45deg) translateY(2.5px)}.ant-menu-submenu:hover>.ant-menu-submenu-title>.ant-menu-submenu-expand-icon,.ant-menu-submenu:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow{color:#1890ff}.ant-menu-inline-collapsed .ant-menu-submenu-arrow:before,.ant-menu-submenu-inline .ant-menu-submenu-arrow:before{transform:rotate(-45deg) translate(2.5px)}.ant-menu-inline-collapsed .ant-menu-submenu-arrow:after,.ant-menu-submenu-inline .ant-menu-submenu-arrow:after{transform:rotate(45deg) translate(-2.5px)}.ant-menu-submenu-horizontal .ant-menu-submenu-arrow{display:none}.ant-menu-submenu-open.ant-menu-submenu-inline>.ant-menu-submenu-title>.ant-menu-submenu-arrow{transform:translateY(-2px)}.ant-menu-submenu-open.ant-menu-submenu-inline>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after{transform:rotate(-45deg) translate(-2.5px)}.ant-menu-submenu-open.ant-menu-submenu-inline>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before{transform:rotate(45deg) translate(2.5px)}.ant-menu-vertical .ant-menu-submenu-selected,.ant-menu-vertical-left .ant-menu-submenu-selected,.ant-menu-vertical-right .ant-menu-submenu-selected{color:#1890ff}.ant-menu-horizontal{line-height:46px;border:0;border-bottom:1px solid #f0f0f0;box-shadow:none}.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu{margin-top:-1px;margin-bottom:0;padding:0 20px}.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item:hover,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu:hover,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item-active,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu-active,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item-open,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu-open,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item-selected,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu-selected{color:#1890ff}.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item:hover:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu:hover:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item-active:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu-active:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item-open:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu-open:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item-selected:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu-selected:after{border-bottom:2px solid #1890ff}.ant-menu-horizontal>.ant-menu-item,.ant-menu-horizontal>.ant-menu-submenu{position:relative;top:1px;display:inline-block;vertical-align:bottom}.ant-menu-horizontal>.ant-menu-item:after,.ant-menu-horizontal>.ant-menu-submenu:after{position:absolute;right:20px;bottom:0;left:20px;border-bottom:2px solid transparent;transition:border-color .3s cubic-bezier(.645,.045,.355,1);content:""}.ant-menu-horizontal>.ant-menu-submenu>.ant-menu-submenu-title{padding:0}.ant-menu-horizontal>.ant-menu-item a{color:#000000d9}.ant-menu-horizontal>.ant-menu-item a:hover{color:#1890ff}.ant-menu-horizontal>.ant-menu-item a:before{bottom:-2px}.ant-menu-horizontal>.ant-menu-item-selected a{color:#1890ff}.ant-menu-horizontal:after{display:block;clear:both;height:0;content:" "}.ant-menu-vertical .ant-menu-item,.ant-menu-vertical-left .ant-menu-item,.ant-menu-vertical-right .ant-menu-item,.ant-menu-inline .ant-menu-item{position:relative}.ant-menu-vertical .ant-menu-item:after,.ant-menu-vertical-left .ant-menu-item:after,.ant-menu-vertical-right .ant-menu-item:after,.ant-menu-inline .ant-menu-item:after{position:absolute;top:0;right:0;bottom:0;border-right:3px solid #1890ff;transform:scaleY(.0001);opacity:0;transition:transform .15s cubic-bezier(.215,.61,.355,1),opacity .15s cubic-bezier(.215,.61,.355,1);content:""}.ant-menu-vertical .ant-menu-item,.ant-menu-vertical-left .ant-menu-item,.ant-menu-vertical-right .ant-menu-item,.ant-menu-inline .ant-menu-item,.ant-menu-vertical .ant-menu-submenu-title,.ant-menu-vertical-left .ant-menu-submenu-title,.ant-menu-vertical-right .ant-menu-submenu-title,.ant-menu-inline .ant-menu-submenu-title{height:40px;margin-top:4px;margin-bottom:4px;padding:0 16px;overflow:hidden;line-height:40px;text-overflow:ellipsis}.ant-menu-vertical .ant-menu-submenu,.ant-menu-vertical-left .ant-menu-submenu,.ant-menu-vertical-right .ant-menu-submenu,.ant-menu-inline .ant-menu-submenu{padding-bottom:.02px}.ant-menu-vertical .ant-menu-item:not(:last-child),.ant-menu-vertical-left .ant-menu-item:not(:last-child),.ant-menu-vertical-right .ant-menu-item:not(:last-child),.ant-menu-inline .ant-menu-item:not(:last-child){margin-bottom:8px}.ant-menu-vertical>.ant-menu-item,.ant-menu-vertical-left>.ant-menu-item,.ant-menu-vertical-right>.ant-menu-item,.ant-menu-inline>.ant-menu-item,.ant-menu-vertical>.ant-menu-submenu>.ant-menu-submenu-title,.ant-menu-vertical-left>.ant-menu-submenu>.ant-menu-submenu-title,.ant-menu-vertical-right>.ant-menu-submenu>.ant-menu-submenu-title,.ant-menu-inline>.ant-menu-submenu>.ant-menu-submenu-title{height:40px;line-height:40px}.ant-menu-vertical .ant-menu-item-group-list .ant-menu-submenu-title,.ant-menu-vertical .ant-menu-submenu-title{padding-right:34px}.ant-menu-inline{width:100%}.ant-menu-inline .ant-menu-selected:after,.ant-menu-inline .ant-menu-item-selected:after{transform:scaleY(1);opacity:1;transition:transform .15s cubic-bezier(.645,.045,.355,1),opacity .15s cubic-bezier(.645,.045,.355,1)}.ant-menu-inline .ant-menu-item,.ant-menu-inline .ant-menu-submenu-title{width:calc(100% + 1px)}.ant-menu-inline .ant-menu-item-group-list .ant-menu-submenu-title,.ant-menu-inline .ant-menu-submenu-title{padding-right:34px}.ant-menu-inline.ant-menu-root .ant-menu-item,.ant-menu-inline.ant-menu-root .ant-menu-submenu-title{display:flex;align-items:center;transition:border-color .3s,background .3s,padding .1s cubic-bezier(.215,.61,.355,1)}.ant-menu-inline.ant-menu-root .ant-menu-item>.ant-menu-title-content,.ant-menu-inline.ant-menu-root .ant-menu-submenu-title>.ant-menu-title-content{flex:auto;min-width:0;overflow:hidden;text-overflow:ellipsis}.ant-menu-inline.ant-menu-root .ant-menu-item>*,.ant-menu-inline.ant-menu-root .ant-menu-submenu-title>*{flex:none}.ant-menu.ant-menu-inline-collapsed{width:80px}.ant-menu.ant-menu-inline-collapsed>.ant-menu-item,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title,.ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title{left:0;padding:0 calc(50% - 8px);text-overflow:clip}.ant-menu.ant-menu-inline-collapsed>.ant-menu-item .ant-menu-submenu-arrow,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .ant-menu-submenu-arrow,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-submenu-arrow{opacity:0}.ant-menu.ant-menu-inline-collapsed>.ant-menu-item .ant-menu-item-icon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .ant-menu-item-icon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-item-icon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-item-icon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item .anticon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .anticon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .anticon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .anticon{margin:0;font-size:16px;line-height:40px}.ant-menu.ant-menu-inline-collapsed>.ant-menu-item .ant-menu-item-icon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .ant-menu-item-icon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-item-icon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-item-icon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item .anticon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .anticon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .anticon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .anticon+span{display:inline-block;opacity:0}.ant-menu.ant-menu-inline-collapsed .ant-menu-item-icon,.ant-menu.ant-menu-inline-collapsed .anticon{display:inline-block}.ant-menu.ant-menu-inline-collapsed-tooltip{pointer-events:none}.ant-menu.ant-menu-inline-collapsed-tooltip .ant-menu-item-icon,.ant-menu.ant-menu-inline-collapsed-tooltip .anticon{display:none}.ant-menu.ant-menu-inline-collapsed-tooltip a{color:#ffffffd9}.ant-menu.ant-menu-inline-collapsed .ant-menu-item-group-title{padding-right:4px;padding-left:4px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-menu-item-group-list{margin:0;padding:0}.ant-menu-item-group-list .ant-menu-item,.ant-menu-item-group-list .ant-menu-submenu-title{padding:0 16px 0 28px}.ant-menu-root.ant-menu-vertical,.ant-menu-root.ant-menu-vertical-left,.ant-menu-root.ant-menu-vertical-right,.ant-menu-root.ant-menu-inline{box-shadow:none}.ant-menu-root.ant-menu-inline-collapsed .ant-menu-item>.ant-menu-inline-collapsed-noicon,.ant-menu-root.ant-menu-inline-collapsed .ant-menu-submenu .ant-menu-submenu-title>.ant-menu-inline-collapsed-noicon{font-size:16px;text-align:center}.ant-menu-sub.ant-menu-inline{padding:0;background:#fafafa;border:0;border-radius:0;box-shadow:none}.ant-menu-sub.ant-menu-inline>.ant-menu-item,.ant-menu-sub.ant-menu-inline>.ant-menu-submenu>.ant-menu-submenu-title{height:40px;line-height:40px;list-style-position:inside;list-style-type:disc}.ant-menu-sub.ant-menu-inline .ant-menu-item-group-title{padding-left:32px}.ant-menu-item-disabled,.ant-menu-submenu-disabled{color:#00000040!important;background:none;cursor:not-allowed}.ant-menu-item-disabled:after,.ant-menu-submenu-disabled:after{border-color:transparent!important}.ant-menu-item-disabled a,.ant-menu-submenu-disabled a{color:#00000040!important;pointer-events:none}.ant-menu-item-disabled>.ant-menu-submenu-title,.ant-menu-submenu-disabled>.ant-menu-submenu-title{color:#00000040!important;cursor:not-allowed}.ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after{background:rgba(0,0,0,.25)!important}.ant-layout-header .ant-menu{line-height:inherit}.ant-menu-inline-collapsed-tooltip a,.ant-menu-inline-collapsed-tooltip a:hover{color:#fff}.ant-menu-light .ant-menu-item:hover,.ant-menu-light .ant-menu-item-active,.ant-menu-light .ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open,.ant-menu-light .ant-menu-submenu-active,.ant-menu-light .ant-menu-submenu-title:hover{color:#1890ff}.ant-menu.ant-menu-root:focus-visible{box-shadow:0 0 0 2px #096dd9}.ant-menu-dark .ant-menu-item:focus-visible,.ant-menu-dark .ant-menu-submenu-title:focus-visible{box-shadow:0 0 0 2px #096dd9}.ant-menu.ant-menu-dark,.ant-menu-dark .ant-menu-sub,.ant-menu.ant-menu-dark .ant-menu-sub{color:#ffffffa6;background:#001529}.ant-menu.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow{opacity:.45;transition:all .3s}.ant-menu.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow:before{background:#fff}.ant-menu-dark.ant-menu-submenu-popup{background:transparent}.ant-menu-dark .ant-menu-inline.ant-menu-sub{background:#000c17}.ant-menu-dark.ant-menu-horizontal{border-bottom:0}.ant-menu-dark.ant-menu-horizontal>.ant-menu-item,.ant-menu-dark.ant-menu-horizontal>.ant-menu-submenu{top:0;margin-top:0;padding:0 20px;border-color:#001529;border-bottom:0}.ant-menu-dark.ant-menu-horizontal>.ant-menu-item:hover{background-color:#1890ff}.ant-menu-dark.ant-menu-horizontal>.ant-menu-item>a:before{bottom:0}.ant-menu-dark .ant-menu-item,.ant-menu-dark .ant-menu-item-group-title,.ant-menu-dark .ant-menu-item>a,.ant-menu-dark .ant-menu-item>span>a{color:#ffffffa6}.ant-menu-dark.ant-menu-inline,.ant-menu-dark.ant-menu-vertical,.ant-menu-dark.ant-menu-vertical-left,.ant-menu-dark.ant-menu-vertical-right{border-right:0}.ant-menu-dark.ant-menu-inline .ant-menu-item,.ant-menu-dark.ant-menu-vertical .ant-menu-item,.ant-menu-dark.ant-menu-vertical-left .ant-menu-item,.ant-menu-dark.ant-menu-vertical-right .ant-menu-item{left:0;margin-left:0;border-right:0}.ant-menu-dark.ant-menu-inline .ant-menu-item:after,.ant-menu-dark.ant-menu-vertical .ant-menu-item:after,.ant-menu-dark.ant-menu-vertical-left .ant-menu-item:after,.ant-menu-dark.ant-menu-vertical-right .ant-menu-item:after{border-right:0}.ant-menu-dark.ant-menu-inline .ant-menu-item,.ant-menu-dark.ant-menu-inline .ant-menu-submenu-title{width:100%}.ant-menu-dark .ant-menu-item:hover,.ant-menu-dark .ant-menu-item-active,.ant-menu-dark .ant-menu-submenu-active,.ant-menu-dark .ant-menu-submenu-open,.ant-menu-dark .ant-menu-submenu-selected,.ant-menu-dark .ant-menu-submenu-title:hover{color:#fff;background-color:transparent}.ant-menu-dark .ant-menu-item:hover>a,.ant-menu-dark .ant-menu-item-active>a,.ant-menu-dark .ant-menu-submenu-active>a,.ant-menu-dark .ant-menu-submenu-open>a,.ant-menu-dark .ant-menu-submenu-selected>a,.ant-menu-dark .ant-menu-submenu-title:hover>a,.ant-menu-dark .ant-menu-item:hover>span>a,.ant-menu-dark .ant-menu-item-active>span>a,.ant-menu-dark .ant-menu-submenu-active>span>a,.ant-menu-dark .ant-menu-submenu-open>span>a,.ant-menu-dark .ant-menu-submenu-selected>span>a,.ant-menu-dark .ant-menu-submenu-title:hover>span>a{color:#fff}.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow{opacity:1}.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before{background:#fff}.ant-menu-dark .ant-menu-item:hover{background-color:transparent}.ant-menu-dark.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected{background-color:#1890ff}.ant-menu-dark .ant-menu-item-selected{color:#fff;border-right:0}.ant-menu-dark .ant-menu-item-selected:after{border-right:0}.ant-menu-dark .ant-menu-item-selected>a,.ant-menu-dark .ant-menu-item-selected>span>a,.ant-menu-dark .ant-menu-item-selected>a:hover,.ant-menu-dark .ant-menu-item-selected>span>a:hover{color:#fff}.ant-menu-dark .ant-menu-item-selected .ant-menu-item-icon,.ant-menu-dark .ant-menu-item-selected .anticon{color:#fff}.ant-menu-dark .ant-menu-item-selected .ant-menu-item-icon+span,.ant-menu-dark .ant-menu-item-selected .anticon+span{color:#fff}.ant-menu.ant-menu-dark .ant-menu-item-selected,.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected{background-color:#1890ff}.ant-menu-dark .ant-menu-item-disabled,.ant-menu-dark .ant-menu-submenu-disabled,.ant-menu-dark .ant-menu-item-disabled>a,.ant-menu-dark .ant-menu-submenu-disabled>a,.ant-menu-dark .ant-menu-item-disabled>span>a,.ant-menu-dark .ant-menu-submenu-disabled>span>a{color:#ffffff59!important;opacity:.8}.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title{color:#ffffff59!important}.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after{background:rgba(255,255,255,.35)!important}.ant-menu.ant-menu-rtl{direction:rtl;text-align:right}.ant-menu-rtl .ant-menu-item-group-title{text-align:right}.ant-menu-rtl.ant-menu-inline,.ant-menu-rtl.ant-menu-vertical{border-right:none;border-left:1px solid #f0f0f0}.ant-menu-rtl.ant-menu-dark.ant-menu-inline,.ant-menu-rtl.ant-menu-dark.ant-menu-vertical{border-left:none}.ant-menu-rtl.ant-menu-vertical.ant-menu-sub>.ant-menu-item,.ant-menu-rtl.ant-menu-vertical-left.ant-menu-sub>.ant-menu-item,.ant-menu-rtl.ant-menu-vertical-right.ant-menu-sub>.ant-menu-item,.ant-menu-rtl.ant-menu-vertical.ant-menu-sub>.ant-menu-submenu,.ant-menu-rtl.ant-menu-vertical-left.ant-menu-sub>.ant-menu-submenu,.ant-menu-rtl.ant-menu-vertical-right.ant-menu-sub>.ant-menu-submenu{transform-origin:top right}.ant-menu-rtl .ant-menu-item .ant-menu-item-icon,.ant-menu-rtl .ant-menu-submenu-title .ant-menu-item-icon,.ant-menu-rtl .ant-menu-item .anticon,.ant-menu-rtl .ant-menu-submenu-title .anticon{margin-right:auto;margin-left:10px}.ant-menu-rtl .ant-menu-item.ant-menu-item-only-child>.ant-menu-item-icon,.ant-menu-rtl .ant-menu-submenu-title.ant-menu-item-only-child>.ant-menu-item-icon,.ant-menu-rtl .ant-menu-item.ant-menu-item-only-child>.anticon,.ant-menu-rtl .ant-menu-submenu-title.ant-menu-item-only-child>.anticon{margin-left:0}.ant-menu-submenu-rtl.ant-menu-submenu-popup{transform-origin:100% 0}.ant-menu-rtl .ant-menu-submenu-vertical>.ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-rtl .ant-menu-submenu-vertical-left>.ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-rtl .ant-menu-submenu-vertical-right>.ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-rtl .ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow{right:auto;left:16px}.ant-menu-rtl .ant-menu-submenu-vertical>.ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-rtl .ant-menu-submenu-vertical-left>.ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-rtl .ant-menu-submenu-vertical-right>.ant-menu-submenu-title .ant-menu-submenu-arrow:before{transform:rotate(-45deg) translateY(-2px)}.ant-menu-rtl .ant-menu-submenu-vertical>.ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-rtl .ant-menu-submenu-vertical-left>.ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-rtl .ant-menu-submenu-vertical-right>.ant-menu-submenu-title .ant-menu-submenu-arrow:after{transform:rotate(45deg) translateY(2px)}.ant-menu-rtl.ant-menu-vertical .ant-menu-item:after,.ant-menu-rtl.ant-menu-vertical-left .ant-menu-item:after,.ant-menu-rtl.ant-menu-vertical-right .ant-menu-item:after,.ant-menu-rtl.ant-menu-inline .ant-menu-item:after{right:auto;left:0}.ant-menu-rtl.ant-menu-vertical .ant-menu-item,.ant-menu-rtl.ant-menu-vertical-left .ant-menu-item,.ant-menu-rtl.ant-menu-vertical-right .ant-menu-item,.ant-menu-rtl.ant-menu-inline .ant-menu-item,.ant-menu-rtl.ant-menu-vertical .ant-menu-submenu-title,.ant-menu-rtl.ant-menu-vertical-left .ant-menu-submenu-title,.ant-menu-rtl.ant-menu-vertical-right .ant-menu-submenu-title,.ant-menu-rtl.ant-menu-inline .ant-menu-submenu-title{text-align:right}.ant-menu-rtl.ant-menu-inline .ant-menu-submenu-title{padding-right:0;padding-left:34px}.ant-menu-rtl.ant-menu-vertical .ant-menu-submenu-title{padding-right:16px;padding-left:34px}.ant-menu-rtl.ant-menu-inline-collapsed.ant-menu-vertical .ant-menu-submenu-title{padding:0 calc(50% - 8px)}.ant-menu-rtl .ant-menu-item-group-list .ant-menu-item,.ant-menu-rtl .ant-menu-item-group-list .ant-menu-submenu-title{padding:0 28px 0 16px}.ant-menu-sub.ant-menu-inline{border:0}.ant-menu-rtl.ant-menu-sub.ant-menu-inline .ant-menu-item-group-title{padding-right:32px;padding-left:0}.ant-menu-submenu.ant-menu-submenu-placement-bottom{top:6px;position:relative}.ant-menu-submenu.ant-menu-submenu-placement-right{left:4px;position:relative}.ant-menu-submenu.ant-menu-submenu-placement-right.ant-menu-submenu-rtl{left:auto;right:4px}.ant-menu-submenu.ant-menu-submenu-placement-left{right:4px;position:relative}.ant-menu-submenu.ant-menu-submenu-placement-left.ant-menu-submenu-rtl{right:auto;left:4px}.ant-mentions-status-error:not(.ant-mentions-disabled):not(.ant-mentions-borderless).ant-mentions,.ant-mentions-status-error:not(.ant-mentions-disabled):not(.ant-mentions-borderless).ant-mentions:hover{background:#fff;border-color:#ff4d4f}.ant-mentions-status-error:not(.ant-mentions-disabled):not(.ant-mentions-borderless).ant-mentions:focus,.ant-mentions-status-error:not(.ant-mentions-disabled):not(.ant-mentions-borderless).ant-mentions-focused{border-color:#ff7875;box-shadow:0 0 0 2px #ff4d4f33;border-right-width:1px;outline:0}.ant-mentions-status-error .ant-input-prefix{color:#ff4d4f}.ant-mentions-status-warning:not(.ant-mentions-disabled):not(.ant-mentions-borderless).ant-mentions,.ant-mentions-status-warning:not(.ant-mentions-disabled):not(.ant-mentions-borderless).ant-mentions:hover{background:#fff;border-color:#faad14}.ant-mentions-status-warning:not(.ant-mentions-disabled):not(.ant-mentions-borderless).ant-mentions:focus,.ant-mentions-status-warning:not(.ant-mentions-disabled):not(.ant-mentions-borderless).ant-mentions-focused{border-color:#ffc53d;box-shadow:0 0 0 2px #faad1433;border-right-width:1px;outline:0}.ant-mentions-status-warning .ant-input-prefix{color:#faad14}.ant-mentions{box-sizing:border-box;margin:0;font-variant:tabular-nums;list-style:none;font-feature-settings:"tnum";width:100%;min-width:0;color:#000000d9;font-size:14px;background-color:#fff;background-image:none;border:1px solid #d9d9d9;border-radius:4px;transition:all .3s;position:relative;display:inline-block;height:auto;padding:0;overflow:hidden;line-height:1.5715;white-space:pre-wrap;vertical-align:bottom}.ant-mentions::placeholder{color:#bfbfbf;-webkit-user-select:none;user-select:none}.ant-mentions:placeholder-shown{text-overflow:ellipsis}.ant-mentions:hover{border-color:#40a9ff;border-right-width:1px}.ant-input-rtl .ant-mentions:hover{border-right-width:0;border-left-width:1px!important}.ant-mentions:focus,.ant-mentions-focused{border-color:#40a9ff;box-shadow:0 0 0 2px #1890ff33;border-right-width:1px;outline:0}.ant-input-rtl .ant-mentions:focus,.ant-input-rtl .ant-mentions-focused{border-right-width:0;border-left-width:1px!important}.ant-mentions-disabled{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;box-shadow:none;cursor:not-allowed;opacity:1}.ant-mentions-disabled:hover{border-color:#d9d9d9;border-right-width:1px}.ant-mentions[disabled]{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;box-shadow:none;cursor:not-allowed;opacity:1}.ant-mentions[disabled]:hover{border-color:#d9d9d9;border-right-width:1px}.ant-mentions-borderless,.ant-mentions-borderless:hover,.ant-mentions-borderless:focus,.ant-mentions-borderless-focused,.ant-mentions-borderless-disabled,.ant-mentions-borderless[disabled]{background-color:transparent;border:none;box-shadow:none}textarea.ant-mentions{max-width:100%;height:auto;min-height:32px;line-height:1.5715;vertical-align:bottom;transition:all .3s,height 0s}.ant-mentions-lg{padding:6.5px 11px;font-size:16px}.ant-mentions-sm{padding:0 7px}.ant-mentions-disabled>textarea{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;box-shadow:none;cursor:not-allowed;opacity:1}.ant-mentions-disabled>textarea:hover{border-color:#d9d9d9;border-right-width:1px}.ant-mentions-focused{border-color:#40a9ff;box-shadow:0 0 0 2px #1890ff33;border-right-width:1px;outline:0}.ant-input-rtl .ant-mentions-focused{border-right-width:0;border-left-width:1px!important}.ant-mentions>textarea,.ant-mentions-measure{min-height:30px;margin:0;padding:4px 11px;overflow:inherit;overflow-x:hidden;overflow-y:auto;font-weight:inherit;font-size:inherit;font-family:inherit;font-style:inherit;font-variant:inherit;font-size-adjust:inherit;font-stretch:inherit;line-height:inherit;direction:inherit;letter-spacing:inherit;white-space:inherit;text-align:inherit;vertical-align:top;word-wrap:break-word;word-break:inherit;tab-size:inherit}.ant-mentions>textarea{width:100%;border:none;outline:none;resize:none}.ant-mentions>textarea::placeholder{color:#bfbfbf;-webkit-user-select:none;user-select:none}.ant-mentions>textarea:placeholder-shown{text-overflow:ellipsis}.ant-mentions-measure{position:absolute;inset:0;z-index:-1;color:transparent;pointer-events:none}.ant-mentions-measure>span{display:inline-block;min-height:1em}.ant-mentions-dropdown{margin:0;padding:0;color:#000000d9;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;top:-9999px;left:-9999px;z-index:1050;box-sizing:border-box;font-size:14px;font-variant:initial;background-color:#fff;border-radius:4px;outline:none;box-shadow:0 3px 6px -4px #0000001f,0 6px 16px #00000014,0 9px 28px 8px #0000000d}.ant-mentions-dropdown-hidden{display:none}.ant-mentions-dropdown-menu{max-height:250px;margin-bottom:0;padding-left:0;overflow:auto;list-style:none;outline:none}.ant-mentions-dropdown-menu-item{position:relative;display:block;min-width:100px;padding:5px 12px;overflow:hidden;color:#000000d9;font-weight:400;line-height:1.5715;white-space:nowrap;text-overflow:ellipsis;cursor:pointer;transition:background .3s ease}.ant-mentions-dropdown-menu-item:hover{background-color:#f5f5f5}.ant-mentions-dropdown-menu-item:first-child{border-radius:4px 4px 0 0}.ant-mentions-dropdown-menu-item:last-child{border-radius:0 0 4px 4px}.ant-mentions-dropdown-menu-item-disabled{color:#00000040;cursor:not-allowed}.ant-mentions-dropdown-menu-item-disabled:hover{color:#00000040;background-color:#fff;cursor:not-allowed}.ant-mentions-dropdown-menu-item-selected{color:#000000d9;font-weight:600;background-color:#fafafa}.ant-mentions-dropdown-menu-item-active{background-color:#f5f5f5}.ant-mentions-suffix{position:absolute;top:0;right:11px;bottom:0;z-index:1;display:inline-flex;align-items:center;margin:auto}.ant-mentions-rtl{direction:rtl}.ant-mentions-dropdown{top:100%;left:12px;position:relative;width:100%;margin-top:8px;margin-bottom:4px}.ant-mentions:focus-within{border-color:#40a9ff;box-shadow:0 0 0 2px #1890ff33;border-right-width:1px;outline:0}.ant-input-rtl .ant-mentions:focus-within{border-right-width:0;border-left-width:1px!important}.ant-mentions.ant-mentions-status-error:not(.ant-mentions-disabled):not(.ant-mentions-borderless).ant-mentions:focus-within{border-color:#ff7875;box-shadow:0 0 0 2px #ff4d4f33;border-right-width:1px;outline:0}.ant-mentions.ant-mentions-status-warning:not(.ant-mentions-disabled):not(.ant-mentions-borderless).ant-mentions:focus-within{border-color:#ffc53d;box-shadow:0 0 0 2px #faad1433;border-right-width:1px;outline:0}.ant-message{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:fixed;top:8px;left:0;z-index:1010;width:100%;pointer-events:none}.ant-message-notice{padding:8px;text-align:center}.ant-message-notice-content{display:inline-block;padding:10px 16px;background:#fff;border-radius:4px;box-shadow:0 3px 6px -4px #0000001f,0 6px 16px #00000014,0 9px 28px 8px #0000000d;pointer-events:all}.ant-message-success .anticon{color:#52c41a}.ant-message-error .anticon{color:#ff4d4f}.ant-message-warning .anticon{color:#faad14}.ant-message-info .anticon,.ant-message-loading .anticon{color:#1890ff}.ant-message .anticon{position:relative;top:1px;margin-right:8px;font-size:16px}.ant-message-notice.ant-move-up-leave.ant-move-up-leave-active{animation-name:MessageMoveOut;animation-duration:.3s}@keyframes MessageMoveOut{0%{max-height:150px;padding:8px;opacity:1}to{max-height:0;padding:0;opacity:0}}.ant-message-rtl,.ant-message-rtl span{direction:rtl}.ant-message-rtl .anticon{margin-right:0;margin-left:8px}.ant-modal{box-sizing:border-box;padding:0 0 24px;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";pointer-events:none;position:relative;top:100px;width:auto;max-width:calc(100vw - 32px);margin:0 auto}.ant-modal.ant-zoom-enter,.ant-modal.ant-zoom-appear{transform:none;opacity:0;animation-duration:.3s;-webkit-user-select:none;user-select:none}.ant-modal-mask{position:fixed;inset:0;z-index:1000;height:100%;background-color:#00000073}.ant-modal-mask-hidden{display:none}.ant-modal-wrap{position:fixed;inset:0;overflow:auto;outline:0;-webkit-overflow-scrolling:touch}.ant-modal-wrap{z-index:1000}.ant-modal-title{margin:0;color:#000000d9;font-weight:500;font-size:16px;line-height:22px;word-wrap:break-word}.ant-modal-content{position:relative;background-color:#fff;background-clip:padding-box;border:0;border-radius:4px;box-shadow:0 3px 6px -4px #0000001f,0 6px 16px #00000014,0 9px 28px 8px #0000000d;pointer-events:auto}.ant-modal-close{position:absolute;top:0;right:0;z-index:10;padding:0;color:#00000073;font-weight:700;line-height:1;text-decoration:none;background:transparent;border:0;outline:0;cursor:pointer;transition:color .3s}.ant-modal-close-x{display:block;width:56px;height:56px;font-size:16px;font-style:normal;line-height:56px;text-align:center;text-transform:none;text-rendering:auto}.ant-modal-close:focus,.ant-modal-close:hover{color:#000000bf;text-decoration:none}.ant-modal-header{padding:16px 24px;color:#000000d9;background:#fff;border-bottom:1px solid #f0f0f0;border-radius:4px 4px 0 0}.ant-modal-body{padding:24px;font-size:14px;line-height:1.5715;word-wrap:break-word}.ant-modal-footer{padding:10px 16px;text-align:right;background:transparent;border-top:1px solid #f0f0f0;border-radius:0 0 4px 4px}.ant-modal-footer .ant-btn+.ant-btn:not(.ant-dropdown-trigger){margin-bottom:0;margin-left:8px}.ant-modal-open{overflow:hidden}.ant-modal-centered{text-align:center}.ant-modal-centered:before{display:inline-block;width:0;height:100%;vertical-align:middle;content:""}.ant-modal-centered .ant-modal{top:0;display:inline-block;padding-bottom:0;text-align:left;vertical-align:middle}@media (max-width: 767px){.ant-modal{max-width:calc(100vw - 16px);margin:8px auto}.ant-modal-centered .ant-modal{flex:1}}.ant-modal-confirm .ant-modal-header{display:none}.ant-modal-confirm .ant-modal-body{padding:32px 32px 24px}.ant-modal-confirm-body-wrapper:before{display:table;content:""}.ant-modal-confirm-body-wrapper:after{display:table;clear:both;content:""}.ant-modal-confirm-body .ant-modal-confirm-title{display:block;overflow:hidden;color:#000000d9;font-weight:500;font-size:16px;line-height:1.4}.ant-modal-confirm-body .ant-modal-confirm-content{margin-top:8px;color:#000000d9;font-size:14px}.ant-modal-confirm-body>.anticon{float:left;margin-right:16px;font-size:22px}.ant-modal-confirm-body>.anticon+.ant-modal-confirm-title+.ant-modal-confirm-content{margin-left:38px}.ant-modal-confirm .ant-modal-confirm-btns{float:right;margin-top:24px}.ant-modal-confirm .ant-modal-confirm-btns .ant-btn+.ant-btn{margin-bottom:0;margin-left:8px}.ant-modal-confirm-error .ant-modal-confirm-body>.anticon{color:#ff4d4f}.ant-modal-confirm-warning .ant-modal-confirm-body>.anticon,.ant-modal-confirm-confirm .ant-modal-confirm-body>.anticon{color:#faad14}.ant-modal-confirm-info .ant-modal-confirm-body>.anticon{color:#1890ff}.ant-modal-confirm-success .ant-modal-confirm-body>.anticon{color:#52c41a}.ant-modal-wrap-rtl{direction:rtl}.ant-modal-wrap-rtl .ant-modal-close{right:initial;left:0}.ant-modal-wrap-rtl .ant-modal-footer{text-align:left}.ant-modal-wrap-rtl .ant-modal-footer .ant-btn+.ant-btn{margin-right:8px;margin-left:0}.ant-modal-wrap-rtl .ant-modal-confirm-body{direction:rtl}.ant-modal-wrap-rtl .ant-modal-confirm-body>.anticon{float:right;margin-right:0;margin-left:16px}.ant-modal-wrap-rtl .ant-modal-confirm-body>.anticon+.ant-modal-confirm-title+.ant-modal-confirm-content{margin-right:38px;margin-left:0}.ant-modal-wrap-rtl .ant-modal-confirm-btns{float:left}.ant-modal-wrap-rtl .ant-modal-confirm-btns .ant-btn+.ant-btn{margin-right:8px;margin-left:0}.ant-modal-wrap-rtl.ant-modal-centered .ant-modal{text-align:right}.ant-notification{box-sizing:border-box;margin:0 24px 0 0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:fixed;z-index:1010}.ant-notification-close-icon{font-size:14px;cursor:pointer}.ant-notification-hook-holder{position:relative}.ant-notification-notice{position:relative;width:384px;max-width:calc(100vw - 48px);margin-bottom:16px;margin-left:auto;padding:16px 24px;overflow:hidden;line-height:1.5715;word-wrap:break-word;background:#fff;border-radius:4px;box-shadow:0 3px 6px -4px #0000001f,0 6px 16px #00000014,0 9px 28px 8px #0000000d}.ant-notification-top .ant-notification-notice,.ant-notification-bottom .ant-notification-notice{margin-right:auto;margin-left:auto}.ant-notification-topLeft .ant-notification-notice,.ant-notification-bottomLeft .ant-notification-notice{margin-right:auto;margin-left:0}.ant-notification-notice-message{margin-bottom:8px;color:#000000d9;font-size:16px;line-height:24px}.ant-notification-notice-message-single-line-auto-margin{display:block;width:calc(264px - 100%);max-width:4px;background-color:transparent;pointer-events:none}.ant-notification-notice-message-single-line-auto-margin:before{display:block;content:""}.ant-notification-notice-description{font-size:14px}.ant-notification-notice-closable .ant-notification-notice-message{padding-right:24px}.ant-notification-notice-with-icon .ant-notification-notice-message{margin-bottom:4px;margin-left:48px;font-size:16px}.ant-notification-notice-with-icon .ant-notification-notice-description{margin-left:48px;font-size:14px}.ant-notification-notice-icon{position:absolute;margin-left:4px;font-size:24px;line-height:24px}.anticon.ant-notification-notice-icon-success{color:#52c41a}.anticon.ant-notification-notice-icon-info{color:#1890ff}.anticon.ant-notification-notice-icon-warning{color:#faad14}.anticon.ant-notification-notice-icon-error{color:#ff4d4f}.ant-notification-notice-close{position:absolute;top:16px;right:22px;color:#00000073;outline:none}.ant-notification-notice-close:hover{color:#000000ab}.ant-notification-notice-btn{float:right;margin-top:16px}.ant-notification .notification-fade-effect{animation-duration:.24s;animation-timing-function:cubic-bezier(.645,.045,.355,1);animation-fill-mode:both}.ant-notification-fade-enter,.ant-notification-fade-appear{animation-duration:.24s;animation-timing-function:cubic-bezier(.645,.045,.355,1);animation-fill-mode:both;opacity:0;animation-play-state:paused}.ant-notification-fade-leave{animation-duration:.24s;animation-timing-function:cubic-bezier(.645,.045,.355,1);animation-fill-mode:both;animation-duration:.2s;animation-play-state:paused}.ant-notification-fade-enter.ant-notification-fade-enter-active,.ant-notification-fade-appear.ant-notification-fade-appear-active{animation-name:NotificationFadeIn;animation-play-state:running}.ant-notification-fade-leave.ant-notification-fade-leave-active{animation-name:NotificationFadeOut;animation-play-state:running}@keyframes NotificationFadeIn{0%{left:384px;opacity:0}to{left:0;opacity:1}}@keyframes NotificationFadeOut{0%{max-height:150px;margin-bottom:16px;opacity:1}to{max-height:0;margin-bottom:0;padding-top:0;padding-bottom:0;opacity:0}}.ant-notification-rtl{direction:rtl}.ant-notification-rtl .ant-notification-notice-closable .ant-notification-notice-message{padding-right:0;padding-left:24px}.ant-notification-rtl .ant-notification-notice-with-icon .ant-notification-notice-message,.ant-notification-rtl .ant-notification-notice-with-icon .ant-notification-notice-description{margin-right:48px;margin-left:0}.ant-notification-rtl .ant-notification-notice-icon{margin-right:4px;margin-left:0}.ant-notification-rtl .ant-notification-notice-close{right:auto;left:22px}.ant-notification-rtl .ant-notification-notice-btn{float:left}.ant-notification-top,.ant-notification-bottom{margin-right:0;margin-left:0}.ant-notification-top .ant-notification-fade-enter.ant-notification-fade-enter-active,.ant-notification-top .ant-notification-fade-appear.ant-notification-fade-appear-active{animation-name:NotificationTopFadeIn}.ant-notification-bottom .ant-notification-fade-enter.ant-notification-fade-enter-active,.ant-notification-bottom .ant-notification-fade-appear.ant-notification-fade-appear-active{animation-name:NotificationBottomFadeIn}.ant-notification-topLeft,.ant-notification-bottomLeft{margin-right:0;margin-left:24px}.ant-notification-topLeft .ant-notification-fade-enter.ant-notification-fade-enter-active,.ant-notification-bottomLeft .ant-notification-fade-enter.ant-notification-fade-enter-active,.ant-notification-topLeft .ant-notification-fade-appear.ant-notification-fade-appear-active,.ant-notification-bottomLeft .ant-notification-fade-appear.ant-notification-fade-appear-active{animation-name:NotificationLeftFadeIn}@keyframes NotificationTopFadeIn{0%{margin-top:-100%;opacity:0}to{margin-top:0;opacity:1}}@keyframes NotificationBottomFadeIn{0%{margin-bottom:-100%;opacity:0}to{margin-bottom:0;opacity:1}}@keyframes NotificationLeftFadeIn{0%{right:384px;opacity:0}to{right:0;opacity:1}}.ant-page-header{box-sizing:border-box;margin:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;padding:16px 24px;background-color:#fff}.ant-page-header-ghost{background-color:inherit}.ant-page-header.has-breadcrumb{padding-top:12px}.ant-page-header.has-footer{padding-bottom:0}.ant-page-header-back{margin-right:16px;font-size:16px;line-height:1}.ant-page-header-back-button{color:#1890ff;text-decoration:none;outline:none;transition:color .3s;color:#000;cursor:pointer}.ant-page-header-back-button:focus,.ant-page-header-back-button:hover{color:#40a9ff}.ant-page-header-back-button:active{color:#096dd9}.ant-page-header .ant-divider-vertical{height:14px;margin:0 12px;vertical-align:middle}.ant-breadcrumb+.ant-page-header-heading{margin-top:8px}.ant-page-header-heading{display:flex;justify-content:space-between}.ant-page-header-heading-left{display:flex;align-items:center;margin:4px 0;overflow:hidden}.ant-page-header-heading-title{margin-right:12px;margin-bottom:0;color:#000000d9;font-weight:600;font-size:20px;line-height:32px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-page-header-heading .ant-avatar{margin-right:12px}.ant-page-header-heading-sub-title{margin-right:12px;color:#00000073;font-size:14px;line-height:1.5715;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-page-header-heading-extra{margin:4px 0;white-space:nowrap}.ant-page-header-heading-extra>*{white-space:unset}.ant-page-header-content{padding-top:12px}.ant-page-header-footer{margin-top:16px}.ant-page-header-footer .ant-tabs>.ant-tabs-nav{margin:0}.ant-page-header-footer .ant-tabs>.ant-tabs-nav:before{border:none}.ant-page-header-footer .ant-tabs .ant-tabs-tab{padding-top:8px;padding-bottom:8px;font-size:16px}.ant-page-header-compact .ant-page-header-heading{flex-wrap:wrap}.ant-page-header-rtl{direction:rtl}.ant-page-header-rtl .ant-page-header-back{float:right;margin-right:0;margin-left:16px}.ant-page-header-rtl .ant-page-header-heading-title,.ant-page-header-rtl .ant-page-header-heading .ant-avatar{margin-right:0;margin-left:12px}.ant-page-header-rtl .ant-page-header-heading-sub-title{float:right;margin-right:0;margin-left:12px}.ant-page-header-rtl .ant-page-header-heading-tags{float:right}.ant-page-header-rtl .ant-page-header-heading-extra{float:left}.ant-page-header-rtl .ant-page-header-heading-extra>*{margin-right:12px;margin-left:0}.ant-page-header-rtl .ant-page-header-heading-extra>*:first-child{margin-right:0}.ant-page-header-rtl .ant-page-header-footer .ant-tabs-bar .ant-tabs-nav{float:right}.ant-page-header-back-button{border:0;background:transparent;padding:0;line-height:inherit;display:inline-block}nz-page-header,nz-page-header-content,nz-page-header-footer{display:block}.ant-pagination{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum"}.ant-pagination ul,.ant-pagination ol{margin:0;padding:0;list-style:none}.ant-pagination:after{display:block;clear:both;height:0;overflow:hidden;visibility:hidden;content:" "}.ant-pagination-total-text{display:inline-block;height:32px;margin-right:8px;line-height:30px;vertical-align:middle}.ant-pagination-item{display:inline-block;min-width:32px;height:32px;margin-right:8px;font-family:-apple-system,BlinkMacSystemFont,Inter,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:30px;text-align:center;vertical-align:middle;list-style:none;background-color:#fff;border:1px solid #d9d9d9;border-radius:4px;outline:0;cursor:pointer;-webkit-user-select:none;user-select:none}.ant-pagination-item a{display:block;padding:0 6px;color:#000000d9;transition:none}.ant-pagination-item a:hover{text-decoration:none}.ant-pagination-item:hover{border-color:#1890ff;transition:all .3s}.ant-pagination-item:hover a{color:#1890ff}.ant-pagination-item:focus-visible{border-color:#1890ff;transition:all .3s}.ant-pagination-item:focus-visible a{color:#1890ff}.ant-pagination-item-active{font-weight:500;background:#fff;border-color:#1890ff}.ant-pagination-item-active a{color:#1890ff}.ant-pagination-item-active:hover{border-color:#40a9ff}.ant-pagination-item-active:focus-visible{border-color:#40a9ff}.ant-pagination-item-active:hover a{color:#40a9ff}.ant-pagination-item-active:focus-visible a{color:#40a9ff}.ant-pagination-jump-prev,.ant-pagination-jump-next{outline:0}.ant-pagination-jump-prev .ant-pagination-item-container,.ant-pagination-jump-next .ant-pagination-item-container{position:relative}.ant-pagination-jump-prev .ant-pagination-item-container .ant-pagination-item-link-icon,.ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-link-icon{color:#1890ff;font-size:12px;letter-spacing:-1px;opacity:0;transition:all .2s}.ant-pagination-jump-prev .ant-pagination-item-container .ant-pagination-item-link-icon-svg,.ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-link-icon-svg{inset:0;margin:auto}.ant-pagination-jump-prev .ant-pagination-item-container .ant-pagination-item-ellipsis,.ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-ellipsis{position:absolute;inset:0;display:block;margin:auto;color:#00000040;font-family:Arial,Helvetica,sans-serif;letter-spacing:2px;text-align:center;text-indent:.13em;opacity:1;transition:all .2s}.ant-pagination-jump-prev:hover .ant-pagination-item-link-icon,.ant-pagination-jump-next:hover .ant-pagination-item-link-icon{opacity:1}.ant-pagination-jump-prev:hover .ant-pagination-item-ellipsis,.ant-pagination-jump-next:hover .ant-pagination-item-ellipsis{opacity:0}.ant-pagination-jump-prev:focus-visible .ant-pagination-item-link-icon,.ant-pagination-jump-next:focus-visible .ant-pagination-item-link-icon{opacity:1}.ant-pagination-jump-prev:focus-visible .ant-pagination-item-ellipsis,.ant-pagination-jump-next:focus-visible .ant-pagination-item-ellipsis{opacity:0}.ant-pagination-prev,.ant-pagination-jump-prev,.ant-pagination-jump-next{margin-right:8px}.ant-pagination-prev,.ant-pagination-next,.ant-pagination-jump-prev,.ant-pagination-jump-next{display:inline-block;min-width:32px;height:32px;color:#000000d9;font-family:-apple-system,BlinkMacSystemFont,Inter,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:32px;text-align:center;vertical-align:middle;list-style:none;border-radius:4px;cursor:pointer;transition:all .3s}.ant-pagination-prev,.ant-pagination-next{font-family:Arial,Helvetica,sans-serif;outline:0}.ant-pagination-prev button,.ant-pagination-next button{color:#000000d9;cursor:pointer;-webkit-user-select:none;user-select:none}.ant-pagination-prev:hover button,.ant-pagination-next:hover button{border-color:#40a9ff}.ant-pagination-prev .ant-pagination-item-link,.ant-pagination-next .ant-pagination-item-link{display:block;width:100%;height:100%;padding:0;font-size:12px;text-align:center;background-color:#fff;border:1px solid #d9d9d9;border-radius:4px;outline:none;transition:all .3s}.ant-pagination-prev:focus-visible .ant-pagination-item-link,.ant-pagination-next:focus-visible .ant-pagination-item-link{color:#1890ff;border-color:#1890ff}.ant-pagination-prev:hover .ant-pagination-item-link,.ant-pagination-next:hover .ant-pagination-item-link{color:#1890ff;border-color:#1890ff}.ant-pagination-disabled,.ant-pagination-disabled:hover{cursor:not-allowed}.ant-pagination-disabled .ant-pagination-item-link,.ant-pagination-disabled:hover .ant-pagination-item-link{color:#00000040;border-color:#d9d9d9;cursor:not-allowed}.ant-pagination-disabled:focus-visible{cursor:not-allowed}.ant-pagination-disabled:focus-visible .ant-pagination-item-link{color:#00000040;border-color:#d9d9d9;cursor:not-allowed}.ant-pagination-slash{margin:0 10px 0 5px}.ant-pagination-options{display:inline-block;margin-left:16px;vertical-align:middle}@media all and (-ms-high-contrast: none){.ant-pagination-options *::-ms-backdrop,.ant-pagination-options{vertical-align:top}}.ant-pagination-options-size-changer.ant-select{display:inline-block;width:auto}.ant-pagination-options-quick-jumper{display:inline-block;height:32px;margin-left:8px;line-height:32px;vertical-align:top}.ant-pagination-options-quick-jumper input{position:relative;display:inline-block;width:100%;min-width:0;padding:4px 11px;color:#000000d9;font-size:14px;line-height:1.5715;background-color:#fff;background-image:none;border:1px solid #d9d9d9;border-radius:4px;transition:all .3s;width:50px;height:32px;margin:0 8px}.ant-pagination-options-quick-jumper input::placeholder{color:#bfbfbf;-webkit-user-select:none;user-select:none}.ant-pagination-options-quick-jumper input:placeholder-shown{text-overflow:ellipsis}.ant-pagination-options-quick-jumper input:hover{border-color:#40a9ff;border-right-width:1px}.ant-input-rtl .ant-pagination-options-quick-jumper input:hover{border-right-width:0;border-left-width:1px!important}.ant-pagination-options-quick-jumper input:focus,.ant-pagination-options-quick-jumper input-focused{border-color:#40a9ff;box-shadow:0 0 0 2px #1890ff33;border-right-width:1px;outline:0}.ant-input-rtl .ant-pagination-options-quick-jumper input:focus,.ant-input-rtl .ant-pagination-options-quick-jumper input-focused{border-right-width:0;border-left-width:1px!important}.ant-pagination-options-quick-jumper input-disabled{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;box-shadow:none;cursor:not-allowed;opacity:1}.ant-pagination-options-quick-jumper input-disabled:hover{border-color:#d9d9d9;border-right-width:1px}.ant-pagination-options-quick-jumper input[disabled]{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;box-shadow:none;cursor:not-allowed;opacity:1}.ant-pagination-options-quick-jumper input[disabled]:hover{border-color:#d9d9d9;border-right-width:1px}.ant-pagination-options-quick-jumper input-borderless,.ant-pagination-options-quick-jumper input-borderless:hover,.ant-pagination-options-quick-jumper input-borderless:focus,.ant-pagination-options-quick-jumper input-borderless-focused,.ant-pagination-options-quick-jumper input-borderless-disabled,.ant-pagination-options-quick-jumper input-borderless[disabled]{background-color:transparent;border:none;box-shadow:none}textarea.ant-pagination-options-quick-jumper input{max-width:100%;height:auto;min-height:32px;line-height:1.5715;vertical-align:bottom;transition:all .3s,height 0s}.ant-pagination-options-quick-jumper input-lg{padding:6.5px 11px;font-size:16px}.ant-pagination-options-quick-jumper input-sm{padding:0 7px}.ant-pagination-options-quick-jumper input-rtl{direction:rtl}.ant-pagination-simple .ant-pagination-prev,.ant-pagination-simple .ant-pagination-next{height:24px;line-height:24px;vertical-align:top}.ant-pagination-simple .ant-pagination-prev .ant-pagination-item-link,.ant-pagination-simple .ant-pagination-next .ant-pagination-item-link{height:24px;background-color:transparent;border:0}.ant-pagination-simple .ant-pagination-prev .ant-pagination-item-link:after,.ant-pagination-simple .ant-pagination-next .ant-pagination-item-link:after{height:24px;line-height:24px}.ant-pagination-simple .ant-pagination-simple-pager{display:inline-block;height:24px;margin-right:8px}.ant-pagination-simple .ant-pagination-simple-pager input{box-sizing:border-box;height:100%;margin-right:8px;padding:0 6px;text-align:center;background-color:#fff;border:1px solid #d9d9d9;border-radius:4px;outline:none;transition:border-color .3s}.ant-pagination-simple .ant-pagination-simple-pager input:hover{border-color:#1890ff}.ant-pagination-simple .ant-pagination-simple-pager input:focus{border-color:#40a9ff;box-shadow:0 0 0 2px #1890ff33}.ant-pagination-simple .ant-pagination-simple-pager input[disabled]{color:#00000040;background:#f5f5f5;border-color:#d9d9d9;cursor:not-allowed}.ant-pagination.mini .ant-pagination-total-text,.ant-pagination.mini .ant-pagination-simple-pager{height:24px;line-height:24px}.ant-pagination.mini .ant-pagination-item{min-width:24px;height:24px;margin:0;line-height:22px}.ant-pagination.mini .ant-pagination-item:not(.ant-pagination-item-active){background:transparent;border-color:transparent}.ant-pagination.mini .ant-pagination-prev,.ant-pagination.mini .ant-pagination-next{min-width:24px;height:24px;margin:0;line-height:24px}.ant-pagination.mini .ant-pagination-prev .ant-pagination-item-link,.ant-pagination.mini .ant-pagination-next .ant-pagination-item-link{background:transparent;border-color:transparent}.ant-pagination.mini .ant-pagination-prev .ant-pagination-item-link:after,.ant-pagination.mini .ant-pagination-next .ant-pagination-item-link:after{height:24px;line-height:24px}.ant-pagination.mini .ant-pagination-jump-prev,.ant-pagination.mini .ant-pagination-jump-next{height:24px;margin-right:0;line-height:24px}.ant-pagination.mini .ant-pagination-options{margin-left:2px}.ant-pagination.mini .ant-pagination-options-size-changer{top:0}.ant-pagination.mini .ant-pagination-options-quick-jumper{height:24px;line-height:24px}.ant-pagination.mini .ant-pagination-options-quick-jumper input{padding:0 7px;width:44px;height:24px}.ant-pagination.ant-pagination-disabled{cursor:not-allowed}.ant-pagination.ant-pagination-disabled .ant-pagination-item{background:#f5f5f5;border-color:#d9d9d9;cursor:not-allowed}.ant-pagination.ant-pagination-disabled .ant-pagination-item a{color:#00000040;background:transparent;border:none;cursor:not-allowed}.ant-pagination.ant-pagination-disabled .ant-pagination-item-active{background:#e6e6e6}.ant-pagination.ant-pagination-disabled .ant-pagination-item-active a{color:#00000040}.ant-pagination.ant-pagination-disabled .ant-pagination-item-link{color:#00000040;background:#f5f5f5;border-color:#d9d9d9;cursor:not-allowed}.ant-pagination-simple.ant-pagination.ant-pagination-disabled .ant-pagination-item-link{background:transparent}.ant-pagination.ant-pagination-disabled .ant-pagination-item-link-icon{opacity:0}.ant-pagination.ant-pagination-disabled .ant-pagination-item-ellipsis{opacity:1}.ant-pagination.ant-pagination-disabled .ant-pagination-simple-pager{color:#00000040}@media only screen and (max-width: 992px){.ant-pagination-item-after-jump-prev,.ant-pagination-item-before-jump-next{display:none}}@media only screen and (max-width: 576px){.ant-pagination-options{display:none}}.ant-pagination-rtl .ant-pagination-total-text,.ant-pagination-rtl .ant-pagination-item,.ant-pagination-rtl .ant-pagination-prev,.ant-pagination-rtl .ant-pagination-jump-prev,.ant-pagination-rtl .ant-pagination-jump-next{margin-right:0;margin-left:8px}.ant-pagination-rtl .ant-pagination-slash{margin:0 5px 0 10px}.ant-pagination-rtl .ant-pagination-options{margin-right:16px;margin-left:0}.ant-pagination-rtl .ant-pagination-options .ant-pagination-options-size-changer.ant-select{margin-right:0;margin-left:8px}.ant-pagination-rtl .ant-pagination-options .ant-pagination-options-quick-jumper{margin-left:0}.ant-pagination-rtl.ant-pagination-simple .ant-pagination-simple-pager,.ant-pagination-rtl.ant-pagination-simple .ant-pagination-simple-pager input{margin-right:0;margin-left:8px}.ant-pagination-rtl.ant-pagination.mini .ant-pagination-options{margin-right:2px;margin-left:0}nz-pagination{display:block}.ant-popover{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;top:0;left:0;z-index:1030;font-weight:400;white-space:normal;text-align:left;cursor:auto;-webkit-user-select:text;user-select:text}.ant-popover:after{position:absolute;background:rgba(255,255,255,.01);content:""}.ant-popover-hidden{display:none}.ant-popover-placement-top,.ant-popover-placement-topLeft,.ant-popover-placement-topRight{padding-bottom:15.3137085px}.ant-popover-placement-right,.ant-popover-placement-rightTop,.ant-popover-placement-rightBottom{padding-left:15.3137085px}.ant-popover-placement-bottom,.ant-popover-placement-bottomLeft,.ant-popover-placement-bottomRight{padding-top:15.3137085px}.ant-popover-placement-left,.ant-popover-placement-leftTop,.ant-popover-placement-leftBottom{padding-right:15.3137085px}.ant-popover-inner{background-color:#fff;background-clip:padding-box;border-radius:4px;box-shadow:0 3px 6px -4px #0000001f,0 6px 16px #00000014,0 9px 28px 8px #0000000d;box-shadow:0 0 8px #00000026 \ }@media screen and (-ms-high-contrast: active),(-ms-high-contrast: none){.ant-popover-inner{box-shadow:0 3px 6px -4px #0000001f,0 6px 16px #00000014,0 9px 28px 8px #0000000d}}.ant-popover-title{min-width:177px;min-height:32px;margin:0;padding:5px 16px 4px;color:#000000d9;font-weight:500;border-bottom:1px solid #f0f0f0}.ant-popover-inner-content{padding:12px 16px;color:#000000d9}.ant-popover-message{position:relative;padding:4px 0 12px;color:#000000d9;font-size:14px}.ant-popover-message>.anticon{position:absolute;top:8.0005px;color:#faad14;font-size:14px}.ant-popover-message-title{padding-left:22px}.ant-popover-buttons{margin-bottom:4px;text-align:right}.ant-popover-buttons button{margin-left:8px}.ant-popover-arrow{position:absolute;display:block;width:16px;height:16px;overflow:hidden;background:transparent;pointer-events:none}.ant-popover-arrow-content{position:absolute;inset:0;display:block;width:11.3137085px;height:11.3137085px;margin:auto;background-color:#fff;content:"";pointer-events:auto;border-radius:0 0 2px;pointer-events:none}.ant-popover-arrow-content:before{position:absolute;top:-11.3137085px;left:-11.3137085px;width:33.9411255px;height:33.9411255px;background:#fff;background-repeat:no-repeat;background-position:-10px -10px;content:"";clip-path:path("M 9.849242404917499 24.091883092036785 A 5 5 0 0 1 13.384776310850237 22.627416997969522 L 20.627416997969522 22.627416997969522 A 2 2 0 0 0 22.627416997969522 20.627416997969522 L 22.627416997969522 13.384776310850237 A 5 5 0 0 1 24.091883092036785 9.849242404917499 L 23.091883092036785 9.849242404917499 L 9.849242404917499 23.091883092036785 Z")}.ant-popover-placement-top .ant-popover-arrow,.ant-popover-placement-topLeft .ant-popover-arrow,.ant-popover-placement-topRight .ant-popover-arrow{bottom:-.6862915px}.ant-popover-placement-top .ant-popover-arrow-content,.ant-popover-placement-topLeft .ant-popover-arrow-content,.ant-popover-placement-topRight .ant-popover-arrow-content{box-shadow:3px 3px 7px #00000012;transform:translateY(-8px) rotate(45deg)}.ant-popover-placement-top .ant-popover-arrow{left:50%;transform:translate(-50%)}.ant-popover-placement-topLeft .ant-popover-arrow{left:16px}.ant-popover-placement-topRight .ant-popover-arrow{right:16px}.ant-popover-placement-right .ant-popover-arrow,.ant-popover-placement-rightTop .ant-popover-arrow,.ant-popover-placement-rightBottom .ant-popover-arrow{left:-.6862915px}.ant-popover-placement-right .ant-popover-arrow-content,.ant-popover-placement-rightTop .ant-popover-arrow-content,.ant-popover-placement-rightBottom .ant-popover-arrow-content{box-shadow:3px 3px 7px #00000012;transform:translate(8px) rotate(135deg)}.ant-popover-placement-right .ant-popover-arrow{top:50%;transform:translateY(-50%)}.ant-popover-placement-rightTop .ant-popover-arrow{top:12px}.ant-popover-placement-rightBottom .ant-popover-arrow{bottom:12px}.ant-popover-placement-bottom .ant-popover-arrow,.ant-popover-placement-bottomLeft .ant-popover-arrow,.ant-popover-placement-bottomRight .ant-popover-arrow{top:-.6862915px}.ant-popover-placement-bottom .ant-popover-arrow-content,.ant-popover-placement-bottomLeft .ant-popover-arrow-content,.ant-popover-placement-bottomRight .ant-popover-arrow-content{box-shadow:2px 2px 5px #0000000f;transform:translateY(8px) rotate(-135deg)}.ant-popover-placement-bottom .ant-popover-arrow{left:50%;transform:translate(-50%)}.ant-popover-placement-bottomLeft .ant-popover-arrow{left:16px}.ant-popover-placement-bottomRight .ant-popover-arrow{right:16px}.ant-popover-placement-left .ant-popover-arrow,.ant-popover-placement-leftTop .ant-popover-arrow,.ant-popover-placement-leftBottom .ant-popover-arrow{right:-.6862915px}.ant-popover-placement-left .ant-popover-arrow-content,.ant-popover-placement-leftTop .ant-popover-arrow-content,.ant-popover-placement-leftBottom .ant-popover-arrow-content{box-shadow:3px 3px 7px #00000012;transform:translate(-8px) rotate(-45deg)}.ant-popover-placement-left .ant-popover-arrow{top:50%;transform:translateY(-50%)}.ant-popover-placement-leftTop .ant-popover-arrow{top:12px}.ant-popover-placement-leftBottom .ant-popover-arrow{bottom:12px}.ant-popover-pink .ant-popover-inner,.ant-popover-pink .ant-popover-arrow-content,.ant-popover-magenta .ant-popover-inner,.ant-popover-magenta .ant-popover-arrow-content{background-color:#eb2f96}.ant-popover-red .ant-popover-inner,.ant-popover-red .ant-popover-arrow-content{background-color:#f5222d}.ant-popover-volcano .ant-popover-inner,.ant-popover-volcano .ant-popover-arrow-content{background-color:#fa541c}.ant-popover-orange .ant-popover-inner,.ant-popover-orange .ant-popover-arrow-content{background-color:#fa8c16}.ant-popover-yellow .ant-popover-inner,.ant-popover-yellow .ant-popover-arrow-content{background-color:#fadb14}.ant-popover-gold .ant-popover-inner,.ant-popover-gold .ant-popover-arrow-content{background-color:#faad14}.ant-popover-cyan .ant-popover-inner,.ant-popover-cyan .ant-popover-arrow-content{background-color:#13c2c2}.ant-popover-lime .ant-popover-inner,.ant-popover-lime .ant-popover-arrow-content{background-color:#a0d911}.ant-popover-green .ant-popover-inner,.ant-popover-green .ant-popover-arrow-content{background-color:#52c41a}.ant-popover-blue .ant-popover-inner,.ant-popover-blue .ant-popover-arrow-content{background-color:#1890ff}.ant-popover-geekblue .ant-popover-inner,.ant-popover-geekblue .ant-popover-arrow-content{background-color:#2f54eb}.ant-popover-purple .ant-popover-inner,.ant-popover-purple .ant-popover-arrow-content{background-color:#722ed1}.ant-popover-rtl{direction:rtl;text-align:right}.ant-popover-rtl .ant-popover-message-title{padding-right:22px;padding-left:16px}.ant-popover-rtl .ant-popover-buttons{text-align:left}.ant-popover-rtl .ant-popover-buttons button{margin-right:8px;margin-left:0}.ant-popover{position:relative}.ant-progress{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-block}.ant-progress-line{position:relative;width:100%;font-size:14px}.ant-progress-steps{display:inline-block}.ant-progress-steps-outer{display:flex;flex-direction:row;align-items:center}.ant-progress-steps-item{flex-shrink:0;min-width:2px;margin-right:2px;background:#f3f3f3;transition:all .3s}.ant-progress-steps-item-active{background:#1890ff}.ant-progress-small.ant-progress-line,.ant-progress-small.ant-progress-line .ant-progress-text .anticon{font-size:12px}.ant-progress-outer{display:inline-block;width:100%;margin-right:0;padding-right:0}.ant-progress-show-info .ant-progress-outer{margin-right:calc(-2em - 8px);padding-right:calc(2em + 8px)}.ant-progress-inner{position:relative;display:inline-block;width:100%;overflow:hidden;vertical-align:middle;background-color:#f5f5f5;border-radius:100px}.ant-progress-circle-trail{stroke:#f5f5f5}.ant-progress-circle-path{animation:ant-progress-appear .3s}.ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path{stroke:#1890ff}.ant-progress-success-bg,.ant-progress-bg{position:relative;background-color:#1890ff;border-radius:100px;transition:all .4s cubic-bezier(.08,.82,.17,1) 0s}.ant-progress-success-bg{position:absolute;top:0;left:0;background-color:#52c41a}.ant-progress-text{display:inline-block;width:2em;margin-left:8px;color:#000000d9;font-size:1em;line-height:1;white-space:nowrap;text-align:left;vertical-align:middle;word-break:normal}.ant-progress-text .anticon{font-size:14px}.ant-progress-status-active .ant-progress-bg:before{position:absolute;inset:0;background:#fff;border-radius:10px;opacity:0;animation:ant-progress-active 2.4s cubic-bezier(.23,1,.32,1) infinite;content:""}.ant-progress-status-exception .ant-progress-bg{background-color:#ff4d4f}.ant-progress-status-exception .ant-progress-text{color:#ff4d4f}.ant-progress-status-exception .ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path{stroke:#ff4d4f}.ant-progress-status-success .ant-progress-bg{background-color:#52c41a}.ant-progress-status-success .ant-progress-text{color:#52c41a}.ant-progress-status-success .ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path{stroke:#52c41a}.ant-progress-circle .ant-progress-inner{position:relative;line-height:1;background-color:transparent}.ant-progress-circle .ant-progress-text{position:absolute;top:50%;left:50%;width:100%;margin:0;padding:0;color:#000000d9;font-size:1em;line-height:1;white-space:normal;text-align:center;transform:translate(-50%,-50%)}.ant-progress-circle .ant-progress-text .anticon{font-size:1.16666667em}.ant-progress-circle.ant-progress-status-exception .ant-progress-text{color:#ff4d4f}.ant-progress-circle.ant-progress-status-success .ant-progress-text{color:#52c41a}@keyframes ant-progress-active{0%{transform:translate(-100%) scaleX(0);opacity:.1}20%{transform:translate(-100%) scaleX(0);opacity:.5}to{transform:translate(0) scaleX(1);opacity:0}}.ant-progress-rtl{direction:rtl}.ant-progress-rtl.ant-progress-show-info .ant-progress-outer{margin-right:0;margin-left:calc(-2em - 8px);padding-right:0;padding-left:calc(2em + 8px)}.ant-progress-rtl .ant-progress-success-bg{right:0;left:auto}.ant-progress-rtl.ant-progress-line .ant-progress-text,.ant-progress-rtl.ant-progress-steps .ant-progress-text{margin-right:8px;margin-left:0;text-align:right}.ant-radio-group{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-block;font-size:0}.ant-radio-group .ant-badge-count{z-index:1}.ant-radio-group>.ant-badge:not(:first-child)>.ant-radio-button-wrapper{border-left:none}.ant-radio-wrapper{box-sizing:border-box;margin:0 8px 0 0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-flex;align-items:baseline;cursor:pointer}.ant-radio-wrapper-disabled{cursor:not-allowed}.ant-radio-wrapper:after{display:inline-block;width:0;overflow:hidden;content:"\a0"}.ant-radio-wrapper.ant-radio-wrapper-in-form-item input[type=radio]{width:14px;height:14px}.ant-radio{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;top:.2em;display:inline-block;outline:none;cursor:pointer}.ant-radio-wrapper:hover .ant-radio,.ant-radio:hover .ant-radio-inner,.ant-radio-input:focus+.ant-radio-inner{border-color:#1890ff}.ant-radio-input:focus+.ant-radio-inner{box-shadow:0 0 0 3px #e6f7ff}.ant-radio-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #1890ff;border-radius:50%;visibility:hidden;animation:antRadioEffect .36s ease-in-out;animation-fill-mode:both;content:""}.ant-radio:hover:after,.ant-radio-wrapper:hover .ant-radio:after{visibility:visible}.ant-radio-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;background-color:#fff;border-color:#d9d9d9;border-style:solid;border-width:1px;border-radius:50%;transition:all .3s}.ant-radio-inner:after{position:absolute;top:50%;left:50%;display:block;width:16px;height:16px;margin-top:-8px;margin-left:-8px;background-color:#1890ff;border-top:0;border-left:0;border-radius:16px;transform:scale(0);opacity:0;transition:all .3s cubic-bezier(.78,.14,.15,.86);content:" "}.ant-radio-input{position:absolute;inset:0;z-index:1;cursor:pointer;opacity:0}.ant-radio-checked .ant-radio-inner{border-color:#1890ff}.ant-radio-checked .ant-radio-inner:after{transform:scale(.5);opacity:1;transition:all .3s cubic-bezier(.78,.14,.15,.86)}.ant-radio-disabled{cursor:not-allowed}.ant-radio-disabled .ant-radio-inner{background-color:#f5f5f5;border-color:#d9d9d9!important;cursor:not-allowed}.ant-radio-disabled .ant-radio-inner:after{background-color:#0003}.ant-radio-disabled .ant-radio-input{cursor:not-allowed}.ant-radio-disabled+span{color:#00000040;cursor:not-allowed}span.ant-radio+*{padding-right:8px;padding-left:8px}.ant-radio-button-wrapper{position:relative;display:inline-block;height:32px;margin:0;padding:0 15px;color:#000000d9;font-size:14px;line-height:30px;background:#fff;border:1px solid #d9d9d9;border-top-width:1.02px;border-left-width:0;cursor:pointer;transition:color .3s,background .3s,border-color .3s,box-shadow .3s}.ant-radio-button-wrapper a{color:#000000d9}.ant-radio-button-wrapper>.ant-radio-button{position:absolute;top:0;left:0;z-index:-1;width:100%;height:100%}.ant-radio-group-large .ant-radio-button-wrapper{height:40px;font-size:16px;line-height:38px}.ant-radio-group-small .ant-radio-button-wrapper{height:24px;padding:0 7px;line-height:22px}.ant-radio-button-wrapper:not(:first-child):before{position:absolute;top:-1px;left:-1px;display:block;box-sizing:content-box;width:1px;height:100%;padding:1px 0;background-color:#d9d9d9;transition:background-color .3s;content:""}.ant-radio-button-wrapper:first-child{border-left:1px solid #d9d9d9;border-radius:4px 0 0 4px}.ant-radio-button-wrapper:last-child{border-radius:0 4px 4px 0}.ant-radio-button-wrapper:first-child:last-child{border-radius:4px}.ant-radio-button-wrapper:hover{position:relative;color:#1890ff}.ant-radio-button-wrapper:focus-within{box-shadow:0 0 0 3px #e6f7ff}.ant-radio-button-wrapper .ant-radio-inner,.ant-radio-button-wrapper input[type=checkbox],.ant-radio-button-wrapper input[type=radio]{width:0;height:0;opacity:0;pointer-events:none}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled){z-index:1;color:#1890ff;background:#fff;border-color:#1890ff}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):before{background-color:#1890ff}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):first-child{border-color:#1890ff}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{color:#40a9ff;border-color:#40a9ff}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover:before{background-color:#40a9ff}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active{color:#096dd9;border-color:#096dd9}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active:before{background-color:#096dd9}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within{box-shadow:0 0 0 3px #e6f7ff}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled){color:#fff;background:#1890ff;border-color:#1890ff}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{color:#fff;background:#40a9ff;border-color:#40a9ff}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active{color:#fff;background:#096dd9;border-color:#096dd9}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within{box-shadow:0 0 0 3px #e6f7ff}.ant-radio-button-wrapper-disabled{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;cursor:not-allowed}.ant-radio-button-wrapper-disabled:first-child,.ant-radio-button-wrapper-disabled:hover{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9}.ant-radio-button-wrapper-disabled:first-child{border-left-color:#d9d9d9}.ant-radio-button-wrapper-disabled.ant-radio-button-wrapper-checked{color:#00000040;background-color:#e6e6e6;border-color:#d9d9d9;box-shadow:none}@keyframes antRadioEffect{0%{transform:scale(1);opacity:.5}to{transform:scale(1.6);opacity:0}}.ant-radio-group.ant-radio-group-rtl{direction:rtl}.ant-radio-wrapper.ant-radio-wrapper-rtl{margin-right:0;margin-left:8px;direction:rtl}.ant-radio-button-wrapper.ant-radio-button-wrapper-rtl{border-right-width:0;border-left-width:1px}.ant-radio-button-wrapper.ant-radio-button-wrapper-rtl.ant-radio-button-wrapper:not(:first-child):before{right:-1px;left:0}.ant-radio-button-wrapper.ant-radio-button-wrapper-rtl.ant-radio-button-wrapper:first-child{border-right:1px solid #d9d9d9;border-radius:0 4px 4px 0}.ant-radio-button-wrapper-checked:not([class*=" ant-radio-button-wrapper-disabled"]).ant-radio-button-wrapper:first-child{border-right-color:#40a9ff}.ant-radio-button-wrapper.ant-radio-button-wrapper-rtl.ant-radio-button-wrapper:last-child{border-radius:4px 0 0 4px}.ant-radio-button-wrapper.ant-radio-button-wrapper-rtl.ant-radio-button-wrapper-disabled:first-child{border-right-color:#d9d9d9}.ant-radio+span:empty{display:none}.ant-rate{box-sizing:border-box;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;font-feature-settings:"tnum";display:inline-block;margin:0;padding:0;color:#fadb14;font-size:20px;line-height:unset;list-style:none;outline:none}.ant-rate-disabled .ant-rate-star{cursor:default}.ant-rate-disabled .ant-rate-star>div:hover{transform:scale(1)}.ant-rate-star{position:relative;display:inline-block;color:inherit;cursor:pointer}.ant-rate-star:not(:last-child){margin-right:8px}.ant-rate-star>div{transition:all .3s,outline 0s}.ant-rate-star>div:hover{transform:scale(1.1)}.ant-rate-star>div:focus{outline:0}.ant-rate-star>div:focus-visible{outline:1px dashed #fadb14;transform:scale(1.1)}.ant-rate-star-first,.ant-rate-star-second{color:#dedede;transition:all .3s;-webkit-user-select:none;user-select:none}.ant-rate-star-first .anticon,.ant-rate-star-second .anticon{vertical-align:middle}.ant-rate-star-first{position:absolute;top:0;left:0;width:50%;height:100%;overflow:hidden;opacity:0}.ant-rate-star-half .ant-rate-star-first,.ant-rate-star-half .ant-rate-star-second{opacity:1}.ant-rate-star-half .ant-rate-star-first,.ant-rate-star-full .ant-rate-star-second{color:inherit}.ant-rate-text{display:inline-block;margin:0 8px;font-size:14px}.ant-rate-rtl{direction:rtl}.ant-rate-rtl .ant-rate-star:not(:last-child){margin-right:0;margin-left:8px}.ant-rate-rtl .ant-rate-star-first{right:0;left:auto}.ant-select-single .ant-select-selector{display:flex}.ant-select-single .ant-select-selector .ant-select-selection-search{position:absolute;inset:0 11px}.ant-select-single .ant-select-selector .ant-select-selection-search-input{width:100%}.ant-select-single .ant-select-selector .ant-select-selection-item,.ant-select-single .ant-select-selector .ant-select-selection-placeholder{padding:0;line-height:30px;transition:all .3s}@supports (-moz-appearance: meterbar){.ant-select-single .ant-select-selector .ant-select-selection-item,.ant-select-single .ant-select-selector .ant-select-selection-placeholder{line-height:30px}}.ant-select-single .ant-select-selector .ant-select-selection-item{position:relative;-webkit-user-select:none;user-select:none}.ant-select-single .ant-select-selector .ant-select-selection-placeholder{transition:none;pointer-events:none}.ant-select-single .ant-select-selector:after,.ant-select-single .ant-select-selector .ant-select-selection-item:after,.ant-select-single .ant-select-selector .ant-select-selection-placeholder:after{display:inline-block;width:0;visibility:hidden;content:"\a0"}.ant-select-single.ant-select-show-arrow .ant-select-selection-search{right:25px}.ant-select-single.ant-select-show-arrow .ant-select-selection-item,.ant-select-single.ant-select-show-arrow .ant-select-selection-placeholder{padding-right:18px}.ant-select-single.ant-select-open .ant-select-selection-item{color:#bfbfbf}.ant-select-single:not(.ant-select-customize-input) .ant-select-selector{width:100%;height:32px;padding:0 11px}.ant-select-single:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-search-input{height:30px}.ant-select-single:not(.ant-select-customize-input) .ant-select-selector:after{line-height:30px}.ant-select-single.ant-select-customize-input .ant-select-selector:after{display:none}.ant-select-single.ant-select-customize-input .ant-select-selector .ant-select-selection-search{position:static;width:100%}.ant-select-single.ant-select-customize-input .ant-select-selector .ant-select-selection-placeholder{position:absolute;right:0;left:0;padding:0 11px}.ant-select-single.ant-select-customize-input .ant-select-selector .ant-select-selection-placeholder:after{display:none}.ant-select-single.ant-select-lg:not(.ant-select-customize-input) .ant-select-selector{height:40px}.ant-select-single.ant-select-lg:not(.ant-select-customize-input) .ant-select-selector:after,.ant-select-single.ant-select-lg:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-item,.ant-select-single.ant-select-lg:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-placeholder{line-height:38px}.ant-select-single.ant-select-lg:not(.ant-select-customize-input):not(.ant-select-customize-input) .ant-select-selection-search-input{height:38px}.ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector{height:24px}.ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector:after,.ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-item,.ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-placeholder{line-height:22px}.ant-select-single.ant-select-sm:not(.ant-select-customize-input):not(.ant-select-customize-input) .ant-select-selection-search-input{height:22px}.ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selection-search{right:7px;left:7px}.ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector{padding:0 7px}.ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-search{right:28px}.ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-item,.ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-placeholder{padding-right:21px}.ant-select-single.ant-select-lg:not(.ant-select-customize-input) .ant-select-selector{padding:0 11px}.ant-select-selection-overflow{position:relative;display:flex;flex:auto;flex-wrap:wrap;max-width:100%}.ant-select-selection-overflow-item{flex:none;align-self:center;max-width:100%}.ant-select-multiple .ant-select-selector{display:flex;flex-wrap:wrap;align-items:center;padding:1px 4px}.ant-select-show-search.ant-select-multiple .ant-select-selector{cursor:text}.ant-select-disabled.ant-select-multiple .ant-select-selector{background:#f5f5f5;cursor:not-allowed}.ant-select-multiple .ant-select-selector:after{display:inline-block;width:0;margin:2px 0;line-height:24px;content:"\a0"}.ant-select-multiple.ant-select-show-arrow .ant-select-selector,.ant-select-multiple.ant-select-allow-clear .ant-select-selector{padding-right:24px}.ant-select-multiple .ant-select-selection-item{position:relative;display:flex;flex:none;box-sizing:border-box;max-width:100%;height:24px;margin-top:2px;margin-bottom:2px;line-height:22px;background:#f5f5f5;border:1px solid #f0f0f0;border-radius:4px;cursor:default;transition:font-size .3s,line-height .3s,height .3s;-webkit-user-select:none;user-select:none;margin-inline-end:4px;padding-inline-start:8px;padding-inline-end:4px}.ant-select-disabled.ant-select-multiple .ant-select-selection-item{color:#bfbfbf;border-color:#d9d9d9;cursor:not-allowed}.ant-select-multiple .ant-select-selection-item-content{display:inline-block;margin-right:4px;overflow:hidden;white-space:pre;text-overflow:ellipsis}.ant-select-multiple .ant-select-selection-item-remove{color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizelegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:inline-block;color:#00000073;font-weight:700;font-size:10px;line-height:inherit;cursor:pointer}.ant-select-multiple .ant-select-selection-item-remove>*{line-height:1}.ant-select-multiple .ant-select-selection-item-remove svg{display:inline-block}.ant-select-multiple .ant-select-selection-item-remove:before{display:none}.ant-select-multiple .ant-select-selection-item-remove .ant-select-multiple .ant-select-selection-item-remove-icon{display:block}.ant-select-multiple .ant-select-selection-item-remove>.anticon{vertical-align:middle}.ant-select-multiple .ant-select-selection-item-remove:hover{color:#000000bf}.ant-select-multiple .ant-select-selection-overflow-item+.ant-select-selection-overflow-item .ant-select-selection-search{margin-inline-start:0}.ant-select-multiple .ant-select-selection-search{position:relative;max-width:100%;margin-inline-start:7px}.ant-select-multiple .ant-select-selection-search-input,.ant-select-multiple .ant-select-selection-search-mirror{height:24px;font-family:-apple-system,BlinkMacSystemFont,Inter,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:24px;transition:all .3s}.ant-select-multiple .ant-select-selection-search-input{width:100%;min-width:4.1px}.ant-select-multiple .ant-select-selection-search-mirror{position:absolute;top:0;left:0;z-index:999;white-space:pre;visibility:hidden}.ant-select-multiple .ant-select-selection-placeholder{position:absolute;top:50%;right:11px;left:11px;transform:translateY(-50%);transition:all .3s}.ant-select-multiple.ant-select-lg .ant-select-selector:after{line-height:32px}.ant-select-multiple.ant-select-lg .ant-select-selection-item{height:32px;line-height:30px}.ant-select-multiple.ant-select-lg .ant-select-selection-search{height:32px;line-height:32px}.ant-select-multiple.ant-select-lg .ant-select-selection-search-input,.ant-select-multiple.ant-select-lg .ant-select-selection-search-mirror{height:32px;line-height:30px}.ant-select-multiple.ant-select-sm .ant-select-selector:after{line-height:16px}.ant-select-multiple.ant-select-sm .ant-select-selection-item{height:16px;line-height:14px}.ant-select-multiple.ant-select-sm .ant-select-selection-search{height:16px;line-height:16px}.ant-select-multiple.ant-select-sm .ant-select-selection-search-input,.ant-select-multiple.ant-select-sm .ant-select-selection-search-mirror{height:16px;line-height:14px}.ant-select-multiple.ant-select-sm .ant-select-selection-placeholder{left:7px}.ant-select-multiple.ant-select-sm .ant-select-selection-search{margin-inline-start:3px}.ant-select-multiple.ant-select-lg .ant-select-selection-item{height:32px;line-height:32px}.ant-select-disabled .ant-select-selection-item-remove{display:none}.ant-select-status-error.ant-select:not(.ant-select-disabled):not(.ant-select-customize-input) .ant-select-selector{background-color:#fff;border-color:#ff4d4f!important}.ant-select-status-error.ant-select:not(.ant-select-disabled):not(.ant-select-customize-input).ant-select-open .ant-select-selector,.ant-select-status-error.ant-select:not(.ant-select-disabled):not(.ant-select-customize-input).ant-select-focused .ant-select-selector{border-color:#ff7875;box-shadow:0 0 0 2px #ff4d4f33;border-right-width:1px;outline:0}.ant-select-status-warning.ant-select:not(.ant-select-disabled):not(.ant-select-customize-input) .ant-select-selector{background-color:#fff;border-color:#faad14!important}.ant-select-status-warning.ant-select:not(.ant-select-disabled):not(.ant-select-customize-input).ant-select-open .ant-select-selector,.ant-select-status-warning.ant-select:not(.ant-select-disabled):not(.ant-select-customize-input).ant-select-focused .ant-select-selector{border-color:#ffc53d;box-shadow:0 0 0 2px #faad1433;border-right-width:1px;outline:0}.ant-select-status-error.ant-select-has-feedback .ant-select-clear,.ant-select-status-warning.ant-select-has-feedback .ant-select-clear,.ant-select-status-success.ant-select-has-feedback .ant-select-clear,.ant-select-status-validating.ant-select-has-feedback .ant-select-clear{right:32px}.ant-select-status-error.ant-select-has-feedback .ant-select-selection-selected-value,.ant-select-status-warning.ant-select-has-feedback .ant-select-selection-selected-value,.ant-select-status-success.ant-select-has-feedback .ant-select-selection-selected-value,.ant-select-status-validating.ant-select-has-feedback .ant-select-selection-selected-value{padding-right:42px}.ant-select{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;cursor:pointer}.ant-select:not(.ant-select-customize-input) .ant-select-selector{position:relative;background-color:#fff;border:1px solid #d9d9d9;border-radius:4px;transition:all .3s cubic-bezier(.645,.045,.355,1)}.ant-select:not(.ant-select-customize-input) .ant-select-selector input{cursor:pointer}.ant-select-show-search.ant-select:not(.ant-select-customize-input) .ant-select-selector{cursor:text}.ant-select-show-search.ant-select:not(.ant-select-customize-input) .ant-select-selector input{cursor:auto}.ant-select-focused:not(.ant-select-disabled).ant-select:not(.ant-select-customize-input) .ant-select-selector{border-color:#40a9ff;box-shadow:0 0 0 2px #1890ff33;border-right-width:1px;outline:0}.ant-input-rtl .ant-select-focused:not(.ant-select-disabled).ant-select:not(.ant-select-customize-input) .ant-select-selector{border-right-width:0;border-left-width:1px!important}.ant-select-disabled.ant-select:not(.ant-select-customize-input) .ant-select-selector{color:#00000040;background:#f5f5f5;cursor:not-allowed}.ant-select-multiple.ant-select-disabled.ant-select:not(.ant-select-customize-input) .ant-select-selector{background:#f5f5f5}.ant-select-disabled.ant-select:not(.ant-select-customize-input) .ant-select-selector input{cursor:not-allowed}.ant-select:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-search-input{margin:0;padding:0;background:transparent;border:none;outline:none;-webkit-appearance:none;appearance:none}.ant-select:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-search-input::-webkit-search-cancel-button{display:none;-webkit-appearance:none}.ant-select:not(.ant-select-disabled):hover .ant-select-selector{border-color:#40a9ff;border-right-width:1px}.ant-input-rtl .ant-select:not(.ant-select-disabled):hover .ant-select-selector{border-right-width:0;border-left-width:1px!important}.ant-select-selection-item{flex:1;overflow:hidden;font-weight:400;white-space:nowrap;text-overflow:ellipsis}@media all and (-ms-high-contrast: none){.ant-select-selection-item *::-ms-backdrop,.ant-select-selection-item{flex:auto}}.ant-select-selection-placeholder{flex:1;overflow:hidden;color:#bfbfbf;white-space:nowrap;text-overflow:ellipsis;pointer-events:none}@media all and (-ms-high-contrast: none){.ant-select-selection-placeholder *::-ms-backdrop,.ant-select-selection-placeholder{flex:auto}}.ant-select-arrow{display:inline-block;color:inherit;font-style:normal;line-height:0;text-transform:none;vertical-align:-.125em;text-rendering:optimizelegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;top:50%;right:11px;display:flex;align-items:center;height:12px;margin-top:-6px;color:#00000040;font-size:12px;line-height:1;text-align:center;pointer-events:none}.ant-select-arrow>*{line-height:1}.ant-select-arrow svg{display:inline-block}.ant-select-arrow:before{display:none}.ant-select-arrow .ant-select-arrow-icon{display:block}.ant-select-arrow .anticon{vertical-align:top;transition:transform .3s}.ant-select-arrow .anticon>svg{vertical-align:top}.ant-select-arrow .anticon:not(.ant-select-suffix){pointer-events:auto}.ant-select-disabled .ant-select-arrow{cursor:not-allowed}.ant-select-arrow>*:not(:last-child){margin-inline-end:8px}.ant-select-clear{position:absolute;top:50%;right:11px;z-index:1;display:inline-block;width:12px;height:12px;margin-top:-6px;color:#00000040;font-size:12px;font-style:normal;line-height:1;text-align:center;text-transform:none;background:#fff;cursor:pointer;opacity:0;transition:color .3s ease,opacity .15s ease;text-rendering:auto}.ant-select-clear:before{display:block}.ant-select-clear:hover{color:#00000073}.ant-select:hover .ant-select-clear{opacity:1}.ant-select-dropdown{margin:0;color:#000000d9;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;top:-9999px;left:-9999px;z-index:1050;box-sizing:border-box;padding:4px 0;overflow:hidden;font-size:14px;font-variant:initial;background-color:#fff;border-radius:4px;outline:none;box-shadow:0 3px 6px -4px #0000001f,0 6px 16px #00000014,0 9px 28px 8px #0000000d}.ant-select-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-select-dropdown-placement-bottomLeft,.ant-select-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-select-dropdown-placement-bottomLeft{animation-name:antSlideUpIn}.ant-select-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-select-dropdown-placement-topLeft,.ant-select-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-select-dropdown-placement-topLeft{animation-name:antSlideDownIn}.ant-select-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-select-dropdown-placement-bottomLeft{animation-name:antSlideUpOut}.ant-select-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-select-dropdown-placement-topLeft{animation-name:antSlideDownOut}.ant-select-dropdown-empty{color:#00000040}.ant-select-item-empty{position:relative;display:block;min-height:32px;padding:5px 12px;color:#000000d9;font-weight:400;font-size:14px;line-height:22px;color:#00000040}.ant-select-item{position:relative;display:block;min-height:32px;padding:5px 12px;color:#000000d9;font-weight:400;font-size:14px;line-height:22px;cursor:pointer;transition:background .3s ease}.ant-select-item-group{color:#00000073;font-size:12px;cursor:default}.ant-select-item-option{display:flex}.ant-select-item-option-content{flex:auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-select-item-option-state{flex:none}.ant-select-item-option-active:not(.ant-select-item-option-disabled){background-color:#f5f5f5}.ant-select-item-option-selected:not(.ant-select-item-option-disabled){color:#000000d9;font-weight:600;background-color:#e6f7ff}.ant-select-item-option-selected:not(.ant-select-item-option-disabled) .ant-select-item-option-state{color:#1890ff}.ant-select-item-option-disabled{color:#00000040;cursor:not-allowed}.ant-select-item-option-disabled.ant-select-item-option-selected{background-color:#f5f5f5}.ant-select-item-option-grouped{padding-left:24px}.ant-select-lg{font-size:16px}.ant-select-borderless .ant-select-selector{background-color:transparent!important;border-color:transparent!important;box-shadow:none!important}.ant-select.ant-select-in-form-item{width:100%}.ant-select-rtl{direction:rtl}.ant-select-rtl .ant-select-arrow,.ant-select-rtl .ant-select-clear{right:initial;left:11px}.ant-select-dropdown-rtl{direction:rtl}.ant-select-dropdown-rtl .ant-select-item-option-grouped{padding-right:24px;padding-left:12px}.ant-select-rtl.ant-select-multiple.ant-select-show-arrow .ant-select-selector,.ant-select-rtl.ant-select-multiple.ant-select-allow-clear .ant-select-selector{padding-right:4px;padding-left:24px}.ant-select-rtl.ant-select-multiple .ant-select-selection-item{text-align:right}.ant-select-rtl.ant-select-multiple .ant-select-selection-item-content{margin-right:0;margin-left:4px;text-align:right}.ant-select-rtl.ant-select-multiple .ant-select-selection-search-mirror{right:0;left:auto}.ant-select-rtl.ant-select-multiple .ant-select-selection-placeholder{right:11px;left:auto}.ant-select-rtl.ant-select-multiple.ant-select-sm .ant-select-selection-placeholder{right:7px}.ant-select-rtl.ant-select-single .ant-select-selector .ant-select-selection-item,.ant-select-rtl.ant-select-single .ant-select-selector .ant-select-selection-placeholder{right:0;left:9px;text-align:right}.ant-select-rtl.ant-select-single.ant-select-show-arrow .ant-select-selection-search{right:11px;left:25px}.ant-select-rtl.ant-select-single.ant-select-show-arrow .ant-select-selection-item,.ant-select-rtl.ant-select-single.ant-select-show-arrow .ant-select-selection-placeholder{padding-right:0;padding-left:18px}.ant-select-rtl.ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-search{right:6px}.ant-select-rtl.ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-item,.ant-select-rtl.ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-placeholder{padding-right:0;padding-left:21px}.ant-select-dropdown{top:100%;left:0;position:relative;width:100%;margin-top:4px;margin-bottom:4px;display:block}.ant-select-dropdown .cdk-virtual-scroll-content-wrapper{right:0}.ant-select-dropdown .full-width{contain:initial}.ant-select-dropdown .full-width .cdk-virtual-scroll-content-wrapper{position:static}.ant-select-dropdown .full-width .cdk-virtual-scroll-spacer{position:absolute;top:0;width:1px}.segmented-disabled-item,.segmented-disabled-item:hover,.segmented-disabled-item:focus{color:#00000040;cursor:not-allowed}.segmented-item-selected{background-color:#fff;border-radius:4px;box-shadow:0 2px 8px -2px #0000000d,0 1px 4px -1px #00000012,0 0 1px #00000014}.segmented-text-ellipsis{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;word-break:keep-all}.ant-segmented{box-sizing:border-box;margin:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-block;padding:2px;color:#000000a6;background-color:#0000000a;border-radius:4px;transition:all .3s cubic-bezier(.645,.045,.355,1)}.ant-segmented-group{position:relative;display:flex;align-items:stretch;justify-items:flex-start;width:100%}.ant-segmented.ant-segmented-block{display:flex}.ant-segmented.ant-segmented-block .ant-segmented-item{flex:1;min-width:0}.ant-segmented:not(.ant-segmented-disabled):hover,.ant-segmented:not(.ant-segmented-disabled):focus{background-color:#0000000f}.ant-segmented-item{position:relative;text-align:center;cursor:pointer;transition:color .3s cubic-bezier(.645,.045,.355,1)}.ant-segmented-item-selected{background-color:#fff;border-radius:4px;box-shadow:0 2px 8px -2px #0000000d,0 1px 4px -1px #00000012,0 0 1px #00000014;color:#262626}.ant-segmented-item:hover,.ant-segmented-item:focus{color:#262626}.ant-segmented-item-label{min-height:28px;padding:0 11px;line-height:28px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;word-break:keep-all}.ant-segmented-item-icon+*{margin-left:12px / 2}.ant-segmented-item-input{position:absolute;top:0;left:0;width:0;height:0;opacity:0;pointer-events:none}.ant-segmented.ant-segmented-lg .ant-segmented-item-label{min-height:36px;padding:0 11px;font-size:16px;line-height:36px}.ant-segmented.ant-segmented-sm .ant-segmented-item-label{min-height:20px;padding:0 7px;line-height:20px}.ant-segmented-item-disabled,.ant-segmented-item-disabled:hover,.ant-segmented-item-disabled:focus{color:#00000040;cursor:not-allowed}.ant-segmented-thumb{background-color:#fff;border-radius:4px;box-shadow:0 2px 8px -2px #0000000d,0 1px 4px -1px #00000012,0 0 1px #00000014;position:absolute;top:0;left:0;width:0;height:100%;padding:4px 0}.ant-segmented-thumb-motion-appear-active{transition:transform .3s cubic-bezier(.645,.045,.355,1),width .3s cubic-bezier(.645,.045,.355,1);will-change:transform,width}.ant-segmented.ant-segmented-rtl{direction:rtl}.ant-segmented.ant-segmented-rtl .ant-segmented-item-icon{margin-right:0;margin-left:6px}.ant-skeleton{display:table;width:100%}.ant-skeleton-header{display:table-cell;padding-right:16px;vertical-align:top}.ant-skeleton-header .ant-skeleton-avatar{display:inline-block;vertical-align:top;background:rgba(190,190,190,.2);width:32px;height:32px;line-height:32px}.ant-skeleton-header .ant-skeleton-avatar.ant-skeleton-avatar-circle{border-radius:50%}.ant-skeleton-header .ant-skeleton-avatar-lg{width:40px;height:40px;line-height:40px}.ant-skeleton-header .ant-skeleton-avatar-lg.ant-skeleton-avatar-circle{border-radius:50%}.ant-skeleton-header .ant-skeleton-avatar-sm{width:24px;height:24px;line-height:24px}.ant-skeleton-header .ant-skeleton-avatar-sm.ant-skeleton-avatar-circle{border-radius:50%}.ant-skeleton-content{display:table-cell;width:100%;vertical-align:top}.ant-skeleton-content .ant-skeleton-title{width:100%;height:16px;margin-top:16px;background:rgba(190,190,190,.2);border-radius:4px}.ant-skeleton-content .ant-skeleton-title+.ant-skeleton-paragraph{margin-top:24px}.ant-skeleton-content .ant-skeleton-paragraph{padding:0}.ant-skeleton-content .ant-skeleton-paragraph>li{width:100%;height:16px;list-style:none;background:rgba(190,190,190,.2);border-radius:4px}.ant-skeleton-content .ant-skeleton-paragraph>li:last-child:not(:first-child):not(:nth-child(2)){width:61%}.ant-skeleton-content .ant-skeleton-paragraph>li+li{margin-top:16px}.ant-skeleton-with-avatar .ant-skeleton-content .ant-skeleton-title{margin-top:12px}.ant-skeleton-with-avatar .ant-skeleton-content .ant-skeleton-title+.ant-skeleton-paragraph{margin-top:28px}.ant-skeleton-round .ant-skeleton-content .ant-skeleton-title,.ant-skeleton-round .ant-skeleton-content .ant-skeleton-paragraph>li{border-radius:100px}.ant-skeleton.ant-skeleton-active .ant-skeleton-content .ant-skeleton-title,.ant-skeleton.ant-skeleton-active .ant-skeleton-content .ant-skeleton-paragraph>li{background:linear-gradient(90deg,rgba(190,190,190,.2) 25%,rgba(129,129,129,.24) 37%,rgba(190,190,190,.2) 63%);background-size:400% 100%;animation:ant-skeleton-loading 1.4s ease infinite}.ant-skeleton.ant-skeleton-active .ant-skeleton-avatar,.ant-skeleton.ant-skeleton-active .ant-skeleton-button,.ant-skeleton.ant-skeleton-active .ant-skeleton-input,.ant-skeleton.ant-skeleton-active .ant-skeleton-image{background:linear-gradient(90deg,rgba(190,190,190,.2) 25%,rgba(129,129,129,.24) 37%,rgba(190,190,190,.2) 63%);background-size:400% 100%;animation:ant-skeleton-loading 1.4s ease infinite}.ant-skeleton.ant-skeleton-block,.ant-skeleton.ant-skeleton-block .ant-skeleton-button,.ant-skeleton.ant-skeleton-block .ant-skeleton-input{width:100%}.ant-skeleton-element{display:inline-block;width:auto}.ant-skeleton-element .ant-skeleton-button{display:inline-block;vertical-align:top;background:rgba(190,190,190,.2);border-radius:4px;width:64px;min-width:64px;height:32px;line-height:32px}.ant-skeleton-element .ant-skeleton-button.ant-skeleton-button-circle{width:32px;min-width:32px;border-radius:50%}.ant-skeleton-element .ant-skeleton-button.ant-skeleton-button-round{border-radius:32px}.ant-skeleton-element .ant-skeleton-button-lg{width:80px;min-width:80px;height:40px;line-height:40px}.ant-skeleton-element .ant-skeleton-button-lg.ant-skeleton-button-circle{width:40px;min-width:40px;border-radius:50%}.ant-skeleton-element .ant-skeleton-button-lg.ant-skeleton-button-round{border-radius:40px}.ant-skeleton-element .ant-skeleton-button-sm{width:48px;min-width:48px;height:24px;line-height:24px}.ant-skeleton-element .ant-skeleton-button-sm.ant-skeleton-button-circle{width:24px;min-width:24px;border-radius:50%}.ant-skeleton-element .ant-skeleton-button-sm.ant-skeleton-button-round{border-radius:24px}.ant-skeleton-element .ant-skeleton-avatar{display:inline-block;vertical-align:top;background:rgba(190,190,190,.2);width:32px;height:32px;line-height:32px}.ant-skeleton-element .ant-skeleton-avatar.ant-skeleton-avatar-circle{border-radius:50%}.ant-skeleton-element .ant-skeleton-avatar-lg{width:40px;height:40px;line-height:40px}.ant-skeleton-element .ant-skeleton-avatar-lg.ant-skeleton-avatar-circle{border-radius:50%}.ant-skeleton-element .ant-skeleton-avatar-sm{width:24px;height:24px;line-height:24px}.ant-skeleton-element .ant-skeleton-avatar-sm.ant-skeleton-avatar-circle{border-radius:50%}.ant-skeleton-element .ant-skeleton-input{display:inline-block;vertical-align:top;background:rgba(190,190,190,.2);width:160px;min-width:160px;height:32px;line-height:32px}.ant-skeleton-element .ant-skeleton-input-lg{width:200px;min-width:200px;height:40px;line-height:40px}.ant-skeleton-element .ant-skeleton-input-sm{width:120px;min-width:120px;height:24px;line-height:24px}.ant-skeleton-element .ant-skeleton-image{display:flex;align-items:center;justify-content:center;vertical-align:top;background:rgba(190,190,190,.2);width:96px;height:96px;line-height:96px}.ant-skeleton-element .ant-skeleton-image.ant-skeleton-image-circle{border-radius:50%}.ant-skeleton-element .ant-skeleton-image-path{fill:#bfbfbf}.ant-skeleton-element .ant-skeleton-image-svg{width:48px;height:48px;line-height:48px;max-width:192px;max-height:192px}.ant-skeleton-element .ant-skeleton-image-svg.ant-skeleton-image-circle{border-radius:50%}@keyframes ant-skeleton-loading{0%{background-position:100% 50%}to{background-position:0 50%}}.ant-skeleton-rtl{direction:rtl}.ant-skeleton-rtl .ant-skeleton-header{padding-right:0;padding-left:16px}.ant-skeleton-rtl.ant-skeleton.ant-skeleton-active .ant-skeleton-content .ant-skeleton-title,.ant-skeleton-rtl.ant-skeleton.ant-skeleton-active .ant-skeleton-content .ant-skeleton-paragraph>li{animation-name:ant-skeleton-loading-rtl}.ant-skeleton-rtl.ant-skeleton.ant-skeleton-active .ant-skeleton-avatar{animation-name:ant-skeleton-loading-rtl}@keyframes ant-skeleton-loading-rtl{0%{background-position:0% 50%}to{background-position:100% 50%}}.ant-slider{box-sizing:border-box;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;height:12px;margin:10px 6px;padding:4px 0;cursor:pointer;touch-action:none}.ant-slider-vertical{width:12px;height:100%;margin:6px 10px;padding:0 4px}.ant-slider-vertical .ant-slider-rail{width:4px;height:100%}.ant-slider-vertical .ant-slider-track{width:4px}.ant-slider-vertical .ant-slider-handle{margin-top:-6px;margin-left:-5px}.ant-slider-vertical .ant-slider-mark{top:0;left:12px;width:18px;height:100%}.ant-slider-vertical .ant-slider-mark-text{left:4px;white-space:nowrap}.ant-slider-vertical .ant-slider-step{width:4px;height:100%}.ant-slider-vertical .ant-slider-dot{top:auto;margin-left:-2px}.ant-slider-tooltip .ant-tooltip-inner{min-width:unset}.ant-slider-rtl.ant-slider-vertical .ant-slider-handle{margin-right:-5px;margin-left:0}.ant-slider-rtl.ant-slider-vertical .ant-slider-mark{right:12px;left:auto}.ant-slider-rtl.ant-slider-vertical .ant-slider-mark-text{right:4px;left:auto}.ant-slider-rtl.ant-slider-vertical .ant-slider-dot{right:2px;left:auto}.ant-slider-with-marks{margin-bottom:28px}.ant-slider-rail{position:absolute;width:100%;height:4px;background-color:#f5f5f5;border-radius:4px;transition:background-color .3s}.ant-slider-track{position:absolute;height:4px;background-color:#91d5ff;border-radius:4px;transition:background-color .3s}.ant-slider-handle{position:absolute;width:14px;height:14px;margin-top:-5px;background-color:#fff;border:solid 2px #91d5ff;border-radius:50%;box-shadow:0;cursor:pointer;transition:border-color .3s,box-shadow .6s,transform .3s cubic-bezier(.18,.89,.32,1.28)}.ant-slider-handle-dragging{z-index:1}.ant-slider-handle:focus{border-color:#46a6ff;outline:none;box-shadow:0 0 0 5px #1890ff1f}.ant-slider-handle.ant-tooltip-open{border-color:#1890ff}.ant-slider:hover .ant-slider-rail{background-color:#e1e1e1}.ant-slider:hover .ant-slider-track{background-color:#69c0ff}.ant-slider:hover .ant-slider-handle:not(.ant-tooltip-open){border-color:#69c0ff}.ant-slider-mark{position:absolute;top:14px;left:0;width:100%;font-size:14px}.ant-slider-mark-text{position:absolute;display:inline-block;color:#00000073;text-align:center;word-break:keep-all;cursor:pointer;-webkit-user-select:none;user-select:none}.ant-slider-mark-text-active{color:#000000d9}.ant-slider-step{position:absolute;width:100%;height:4px;background:transparent;pointer-events:none}.ant-slider-dot{position:absolute;top:-2px;width:8px;height:8px;background-color:#fff;border:2px solid #f0f0f0;border-radius:50%;cursor:pointer}.ant-slider-dot-active{border-color:#8cc8ff}.ant-slider-disabled{cursor:not-allowed}.ant-slider-disabled .ant-slider-rail{background-color:#f5f5f5!important}.ant-slider-disabled .ant-slider-track{background-color:#00000040!important}.ant-slider-disabled .ant-slider-handle,.ant-slider-disabled .ant-slider-dot{background-color:#fff;border-color:#00000040!important;box-shadow:none;cursor:not-allowed}.ant-slider-disabled .ant-slider-mark-text,.ant-slider-disabled .ant-slider-dot{cursor:not-allowed!important}.ant-slider-rtl{direction:rtl}.ant-slider-rtl .ant-slider-mark{right:0;left:auto}.ant-spin{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;display:none;color:#1890ff;text-align:center;vertical-align:middle;opacity:0;transition:transform .3s cubic-bezier(.78,.14,.15,.86)}.ant-spin-spinning{position:static;display:inline-block;opacity:1}.ant-spin-nested-loading{position:relative}.ant-spin-nested-loading>div>.ant-spin{position:absolute;top:0;left:0;z-index:4;display:block;width:100%;height:100%;max-height:400px}.ant-spin-nested-loading>div>.ant-spin .ant-spin-dot{position:absolute;top:50%;left:50%;margin:-10px}.ant-spin-nested-loading>div>.ant-spin .ant-spin-text{position:absolute;top:50%;width:100%;padding-top:5px;text-shadow:0 1px 2px #fff}.ant-spin-nested-loading>div>.ant-spin.ant-spin-show-text .ant-spin-dot{margin-top:-20px}.ant-spin-nested-loading>div>.ant-spin-sm .ant-spin-dot{margin:-7px}.ant-spin-nested-loading>div>.ant-spin-sm .ant-spin-text{padding-top:2px}.ant-spin-nested-loading>div>.ant-spin-sm.ant-spin-show-text .ant-spin-dot{margin-top:-17px}.ant-spin-nested-loading>div>.ant-spin-lg .ant-spin-dot{margin:-16px}.ant-spin-nested-loading>div>.ant-spin-lg .ant-spin-text{padding-top:11px}.ant-spin-nested-loading>div>.ant-spin-lg.ant-spin-show-text .ant-spin-dot{margin-top:-26px}.ant-spin-container{position:relative;transition:opacity .3s}.ant-spin-container:after{position:absolute;inset:0;z-index:10;display:none \ ;width:100%;height:100%;background:#fff;opacity:0;transition:all .3s;content:"";pointer-events:none}.ant-spin-blur{clear:both;opacity:.5;-webkit-user-select:none;user-select:none;pointer-events:none}.ant-spin-blur:after{opacity:.4;pointer-events:auto}.ant-spin-tip{color:#00000073}.ant-spin-dot{position:relative;display:inline-block;font-size:20px;width:1em;height:1em}.ant-spin-dot-item{position:absolute;display:block;width:9px;height:9px;background-color:#1890ff;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.ant-spin-dot-item:nth-child(1){top:0;left:0}.ant-spin-dot-item:nth-child(2){top:0;right:0;animation-delay:.4s}.ant-spin-dot-item:nth-child(3){right:0;bottom:0;animation-delay:.8s}.ant-spin-dot-item:nth-child(4){bottom:0;left:0;animation-delay:1.2s}.ant-spin-dot-spin{transform:rotate(0);animation:antRotate 1.2s infinite linear}.ant-spin-sm .ant-spin-dot{font-size:14px}.ant-spin-sm .ant-spin-dot i{width:6px;height:6px}.ant-spin-lg .ant-spin-dot{font-size:32px}.ant-spin-lg .ant-spin-dot i{width:14px;height:14px}.ant-spin.ant-spin-show-text .ant-spin-text{display:block}@media all and (-ms-high-contrast: none),(-ms-high-contrast: active){.ant-spin-blur{background:#fff;opacity:.5}}@keyframes antSpinMove{to{opacity:1}}@keyframes antRotate{to{transform:rotate(360deg)}}.ant-spin-rtl{direction:rtl}.ant-spin-rtl .ant-spin-dot-spin{transform:rotate(-45deg);animation-name:antRotateRtl}@keyframes antRotateRtl{to{transform:rotate(-405deg)}}nz-spin{display:block}.ant-statistic{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum"}.ant-statistic-title{margin-bottom:4px;color:#00000073;font-size:14px}.ant-statistic-content{color:#000000d9;font-size:24px;font-family:-apple-system,BlinkMacSystemFont,Inter,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}.ant-statistic-content-value{display:inline-block;direction:ltr}.ant-statistic-content-prefix,.ant-statistic-content-suffix{display:inline-block}.ant-statistic-content-prefix{margin-right:4px}.ant-statistic-content-suffix{margin-left:4px}.ant-statistic-rtl{direction:rtl}.ant-statistic-rtl .ant-statistic-content-prefix{margin-right:0;margin-left:4px}.ant-statistic-rtl .ant-statistic-content-suffix{margin-right:4px;margin-left:0}.ant-steps{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:flex;width:100%;font-size:0;text-align:initial}.ant-steps-item{position:relative;display:inline-block;flex:1;overflow:hidden;vertical-align:top}.ant-steps-item-container{outline:none}.ant-steps-item:last-child{flex:none}.ant-steps-item:last-child>.ant-steps-item-container>.ant-steps-item-tail,.ant-steps-item:last-child>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{display:none}.ant-steps-item-icon,.ant-steps-item-content{display:inline-block;vertical-align:top}.ant-steps-item-icon{width:32px;height:32px;margin:0 8px 0 0;font-size:16px;font-family:-apple-system,BlinkMacSystemFont,Inter,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:32px;text-align:center;border:1px solid rgba(0,0,0,.25);border-radius:32px;transition:background-color .3s,border-color .3s}.ant-steps-item-icon .ant-steps-icon{position:relative;top:-.5px;color:#1890ff;line-height:1}.ant-steps-item-tail{position:absolute;top:12px;left:0;width:100%;padding:0 10px}.ant-steps-item-tail:after{display:inline-block;width:100%;height:1px;background:#f0f0f0;border-radius:1px;transition:background .3s;content:""}.ant-steps-item-title{position:relative;display:inline-block;padding-right:16px;color:#000000d9;font-size:16px;line-height:32px}.ant-steps-item-title:after{position:absolute;top:16px;left:100%;display:block;width:9999px;height:1px;background:#f0f0f0;content:""}.ant-steps-item-subtitle{display:inline;margin-left:8px;color:#00000073;font-weight:400;font-size:14px}.ant-steps-item-description{color:#00000073;font-size:14px}.ant-steps-item-wait .ant-steps-item-icon{background-color:#fff;border-color:#00000040}.ant-steps-item-wait .ant-steps-item-icon>.ant-steps-icon{color:#00000040}.ant-steps-item-wait .ant-steps-item-icon>.ant-steps-icon .ant-steps-icon-dot{background:rgba(0,0,0,.25)}.ant-steps-item-wait>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title{color:#00000073}.ant-steps-item-wait>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{background-color:#f0f0f0}.ant-steps-item-wait>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-description{color:#00000073}.ant-steps-item-wait>.ant-steps-item-container>.ant-steps-item-tail:after{background-color:#f0f0f0}.ant-steps-item-process .ant-steps-item-icon{background-color:#fff;border-color:#1890ff}.ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon{color:#1890ff}.ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon .ant-steps-icon-dot{background:#1890ff}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title{color:#000000d9}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{background-color:#f0f0f0}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-description{color:#000000d9}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-tail:after{background-color:#f0f0f0}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-icon{background:#1890ff}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-icon .ant-steps-icon{color:#fff}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-title{font-weight:500}.ant-steps-item-finish .ant-steps-item-icon{background-color:#fff;border-color:#1890ff}.ant-steps-item-finish .ant-steps-item-icon>.ant-steps-icon{color:#1890ff}.ant-steps-item-finish .ant-steps-item-icon>.ant-steps-icon .ant-steps-icon-dot{background:#1890ff}.ant-steps-item-finish>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title{color:#000000d9}.ant-steps-item-finish>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{background-color:#1890ff}.ant-steps-item-finish>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-description{color:#00000073}.ant-steps-item-finish>.ant-steps-item-container>.ant-steps-item-tail:after{background-color:#1890ff}.ant-steps-item-error .ant-steps-item-icon{background-color:#fff;border-color:#ff4d4f}.ant-steps-item-error .ant-steps-item-icon>.ant-steps-icon{color:#ff4d4f}.ant-steps-item-error .ant-steps-item-icon>.ant-steps-icon .ant-steps-icon-dot{background:#ff4d4f}.ant-steps-item-error>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title{color:#ff4d4f}.ant-steps-item-error>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{background-color:#f0f0f0}.ant-steps-item-error>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-description{color:#ff4d4f}.ant-steps-item-error>.ant-steps-item-container>.ant-steps-item-tail:after{background-color:#f0f0f0}.ant-steps-item.ant-steps-next-error .ant-steps-item-title:after{background:#ff4d4f}.ant-steps-item-disabled{cursor:not-allowed}.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button]{cursor:pointer}.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button] .ant-steps-item-title,.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button] .ant-steps-item-subtitle,.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button] .ant-steps-item-description,.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button] .ant-steps-item-icon .ant-steps-icon{transition:color .3s}.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button]:hover .ant-steps-item-title,.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button]:hover .ant-steps-item-subtitle,.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button]:hover .ant-steps-item-description{color:#1890ff}.ant-steps .ant-steps-item:not(.ant-steps-item-active):not(.ant-steps-item-process)>.ant-steps-item-container[role=button]:hover .ant-steps-item-icon{border-color:#1890ff}.ant-steps .ant-steps-item:not(.ant-steps-item-active):not(.ant-steps-item-process)>.ant-steps-item-container[role=button]:hover .ant-steps-item-icon .ant-steps-icon{color:#1890ff}.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item{padding-left:16px;white-space:nowrap}.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:first-child{padding-left:0}.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:last-child .ant-steps-item-title{padding-right:0}.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item-tail{display:none}.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item-description{max-width:140px;white-space:normal}.ant-steps-item-custom>.ant-steps-item-container>.ant-steps-item-icon{height:auto;background:none;border:0}.ant-steps-item-custom>.ant-steps-item-container>.ant-steps-item-icon>.ant-steps-icon{top:0;left:.5px;width:32px;height:32px;font-size:24px;line-height:32px}.ant-steps-item-custom.ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon{color:#1890ff}.ant-steps:not(.ant-steps-vertical) .ant-steps-item-custom .ant-steps-item-icon{width:auto;background:none}.ant-steps-small.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item{padding-left:12px}.ant-steps-small.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:first-child{padding-left:0}.ant-steps-small .ant-steps-item-icon{width:24px;height:24px;margin:0 8px 0 0;font-size:12px;line-height:24px;text-align:center;border-radius:24px}.ant-steps-small .ant-steps-item-title{padding-right:12px;font-size:14px;line-height:24px}.ant-steps-small .ant-steps-item-title:after{top:12px}.ant-steps-small .ant-steps-item-description{color:#00000073;font-size:14px}.ant-steps-small .ant-steps-item-tail{top:8px}.ant-steps-small .ant-steps-item-custom .ant-steps-item-icon{width:inherit;height:inherit;line-height:inherit;background:none;border:0;border-radius:0}.ant-steps-small .ant-steps-item-custom .ant-steps-item-icon>.ant-steps-icon{font-size:24px;line-height:24px;transform:none}.ant-steps-vertical{display:flex;flex-direction:column}.ant-steps-vertical>.ant-steps-item{display:block;flex:1 0 auto;padding-left:0;overflow:visible}.ant-steps-vertical>.ant-steps-item .ant-steps-item-icon{float:left;margin-right:16px}.ant-steps-vertical>.ant-steps-item .ant-steps-item-content{display:block;min-height:48px;overflow:hidden}.ant-steps-vertical>.ant-steps-item .ant-steps-item-title{line-height:32px}.ant-steps-vertical>.ant-steps-item .ant-steps-item-description{padding-bottom:12px}.ant-steps-vertical>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{position:absolute;top:0;left:16px;width:1px;height:100%;padding:38px 0 6px}.ant-steps-vertical>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail:after{width:1px;height:100%}.ant-steps-vertical>.ant-steps-item:not(:last-child)>.ant-steps-item-container>.ant-steps-item-tail{display:block}.ant-steps-vertical>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{display:none}.ant-steps-vertical.ant-steps-small .ant-steps-item-container .ant-steps-item-tail{position:absolute;top:0;left:12px;padding:30px 0 6px}.ant-steps-vertical.ant-steps-small .ant-steps-item-container .ant-steps-item-title{line-height:24px}.ant-steps-label-vertical .ant-steps-item{overflow:visible}.ant-steps-label-vertical .ant-steps-item-tail{margin-left:58px;padding:3.5px 24px}.ant-steps-label-vertical .ant-steps-item-content{display:block;width:116px;margin-top:8px;text-align:center}.ant-steps-label-vertical .ant-steps-item-icon{display:inline-block;margin-left:42px}.ant-steps-label-vertical .ant-steps-item-title{padding-right:0;padding-left:0}.ant-steps-label-vertical .ant-steps-item-title:after{display:none}.ant-steps-label-vertical .ant-steps-item-subtitle{display:block;margin-bottom:4px;margin-left:0;line-height:1.5715}.ant-steps-label-vertical.ant-steps-small:not(.ant-steps-dot) .ant-steps-item-icon{margin-left:46px}.ant-steps-dot .ant-steps-item-title,.ant-steps-dot.ant-steps-small .ant-steps-item-title{line-height:1.5715}.ant-steps-dot .ant-steps-item-tail,.ant-steps-dot.ant-steps-small .ant-steps-item-tail{top:2px;width:100%;margin:0 0 0 70px;padding:0}.ant-steps-dot .ant-steps-item-tail:after,.ant-steps-dot.ant-steps-small .ant-steps-item-tail:after{width:calc(100% - 20px);height:3px;margin-left:12px}.ant-steps-dot .ant-steps-item:first-child .ant-steps-icon-dot,.ant-steps-dot.ant-steps-small .ant-steps-item:first-child .ant-steps-icon-dot{left:2px}.ant-steps-dot .ant-steps-item-icon,.ant-steps-dot.ant-steps-small .ant-steps-item-icon{width:8px;height:8px;margin-left:67px;padding-right:0;line-height:8px;background:transparent;border:0}.ant-steps-dot .ant-steps-item-icon .ant-steps-icon-dot,.ant-steps-dot.ant-steps-small .ant-steps-item-icon .ant-steps-icon-dot{position:relative;float:left;width:100%;height:100%;border-radius:100px;transition:all .3s}.ant-steps-dot .ant-steps-item-icon .ant-steps-icon-dot:after,.ant-steps-dot.ant-steps-small .ant-steps-item-icon .ant-steps-icon-dot:after{position:absolute;top:-12px;left:-26px;width:60px;height:32px;background:rgba(0,0,0,.001);content:""}.ant-steps-dot .ant-steps-item-content,.ant-steps-dot.ant-steps-small .ant-steps-item-content{width:140px}.ant-steps-dot .ant-steps-item-process .ant-steps-item-icon,.ant-steps-dot.ant-steps-small .ant-steps-item-process .ant-steps-item-icon{position:relative;top:-1px;width:10px;height:10px;line-height:10px;background:none}.ant-steps-dot .ant-steps-item-process .ant-steps-icon:first-child .ant-steps-icon-dot,.ant-steps-dot.ant-steps-small .ant-steps-item-process .ant-steps-icon:first-child .ant-steps-icon-dot{left:0}.ant-steps-vertical.ant-steps-dot .ant-steps-item-icon{margin-top:13px;margin-left:0;background:none}.ant-steps-vertical.ant-steps-dot .ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{top:6.5px;left:-9px;margin:0;padding:22px 0 4px}.ant-steps-vertical.ant-steps-dot.ant-steps-small .ant-steps-item-icon{margin-top:10px}.ant-steps-vertical.ant-steps-dot.ant-steps-small .ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{top:3.5px}.ant-steps-vertical.ant-steps-dot .ant-steps-item:first-child .ant-steps-icon-dot{left:0}.ant-steps-vertical.ant-steps-dot .ant-steps-item-content{width:inherit}.ant-steps-vertical.ant-steps-dot .ant-steps-item-process .ant-steps-item-container .ant-steps-item-icon .ant-steps-icon-dot{top:-1px;left:-1px}.ant-steps-navigation{padding-top:12px}.ant-steps-navigation.ant-steps-small .ant-steps-item-container{margin-left:-12px}.ant-steps-navigation .ant-steps-item{overflow:visible;text-align:center}.ant-steps-navigation .ant-steps-item-container{display:inline-block;height:100%;margin-left:-16px;padding-bottom:12px;text-align:left;transition:opacity .3s}.ant-steps-navigation .ant-steps-item-container .ant-steps-item-content{max-width:auto}.ant-steps-navigation .ant-steps-item-container .ant-steps-item-title{max-width:100%;padding-right:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-steps-navigation .ant-steps-item-container .ant-steps-item-title:after{display:none}.ant-steps-navigation .ant-steps-item:not(.ant-steps-item-active) .ant-steps-item-container[role=button]{cursor:pointer}.ant-steps-navigation .ant-steps-item:not(.ant-steps-item-active) .ant-steps-item-container[role=button]:hover{opacity:.85}.ant-steps-navigation .ant-steps-item:last-child{flex:1}.ant-steps-navigation .ant-steps-item:last-child:after{display:none}.ant-steps-navigation .ant-steps-item:after{position:absolute;top:50%;left:100%;display:inline-block;width:12px;height:12px;margin-top:-14px;margin-left:-2px;border:1px solid rgba(0,0,0,.25);border-bottom:none;border-left:none;transform:rotate(45deg);content:""}.ant-steps-navigation .ant-steps-item:before{position:absolute;bottom:0;left:50%;display:inline-block;width:0;height:2px;background-color:#1890ff;transition:width .3s,left .3s;transition-timing-function:ease-out;content:""}.ant-steps-navigation .ant-steps-item.ant-steps-item-active:before{left:0;width:100%}.ant-steps-navigation.ant-steps-vertical>.ant-steps-item{margin-right:0!important}.ant-steps-navigation.ant-steps-vertical>.ant-steps-item:before{display:none}.ant-steps-navigation.ant-steps-vertical>.ant-steps-item.ant-steps-item-active:before{top:0;right:0;left:unset;display:block;width:3px;height:calc(100% - 24px)}.ant-steps-navigation.ant-steps-vertical>.ant-steps-item:after{position:relative;top:-2px;left:50%;display:block;width:8px;height:8px;margin-bottom:8px;text-align:center;transform:rotate(135deg)}.ant-steps-navigation.ant-steps-vertical>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{visibility:hidden}.ant-steps-navigation.ant-steps-horizontal>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{visibility:hidden}.ant-steps-rtl{direction:rtl}.ant-steps.ant-steps-rtl .ant-steps-item-icon{margin-right:0;margin-left:8px}.ant-steps-rtl .ant-steps-item-tail{right:0;left:auto}.ant-steps-rtl .ant-steps-item-title{padding-right:0;padding-left:16px}.ant-steps-rtl .ant-steps-item-title .ant-steps-item-subtitle{float:left;margin-right:8px;margin-left:0}.ant-steps-rtl .ant-steps-item-title:after{right:100%;left:auto}.ant-steps-rtl.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item{padding-right:16px;padding-left:0}.ant-steps-rtl.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:first-child{padding-right:0}.ant-steps-rtl.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:last-child .ant-steps-item-title{padding-left:0}.ant-steps-rtl .ant-steps-item-custom .ant-steps-item-icon>.ant-steps-icon{right:.5px;left:auto}.ant-steps-rtl.ant-steps-navigation.ant-steps-small .ant-steps-item-container{margin-right:-12px;margin-left:0}.ant-steps-rtl.ant-steps-navigation .ant-steps-item-container{margin-right:-16px;margin-left:0;text-align:right}.ant-steps-rtl.ant-steps-navigation .ant-steps-item-container .ant-steps-item-title{padding-left:0}.ant-steps-rtl.ant-steps-navigation .ant-steps-item:after{right:100%;left:auto;margin-right:-2px;margin-left:0;transform:rotate(225deg)}.ant-steps-rtl.ant-steps-small.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item{padding-right:12px;padding-left:0}.ant-steps-rtl.ant-steps-small.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:first-child{padding-right:0}.ant-steps-rtl.ant-steps-small .ant-steps-item-title{padding-right:0;padding-left:12px}.ant-steps-rtl.ant-steps-vertical>.ant-steps-item .ant-steps-item-icon{float:right;margin-right:0;margin-left:16px}.ant-steps-rtl.ant-steps-vertical>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{right:16px;left:auto}.ant-steps-rtl.ant-steps-vertical.ant-steps-small .ant-steps-item-container .ant-steps-item-tail{right:12px;left:auto}.ant-steps-rtl.ant-steps-label-vertical .ant-steps-item-title{padding-left:0}.ant-steps-rtl.ant-steps-dot .ant-steps-item-tail,.ant-steps-rtl.ant-steps-dot.ant-steps-small .ant-steps-item-tail{margin:0 70px 0 0}.ant-steps-rtl.ant-steps-dot .ant-steps-item-tail:after,.ant-steps-rtl.ant-steps-dot.ant-steps-small .ant-steps-item-tail:after{margin-right:12px;margin-left:0}.ant-steps-rtl.ant-steps-dot .ant-steps-item:first-child .ant-steps-icon-dot,.ant-steps-rtl.ant-steps-dot.ant-steps-small .ant-steps-item:first-child .ant-steps-icon-dot{right:2px;left:auto}.ant-steps-rtl.ant-steps-dot .ant-steps-item-icon,.ant-steps-rtl.ant-steps-dot.ant-steps-small .ant-steps-item-icon{margin-right:67px;margin-left:0}.ant-steps-rtl.ant-steps-dot .ant-steps-item-icon .ant-steps-icon-dot,.ant-steps-rtl.ant-steps-dot.ant-steps-small .ant-steps-item-icon .ant-steps-icon-dot{float:right}.ant-steps-rtl.ant-steps-dot .ant-steps-item-icon .ant-steps-icon-dot:after,.ant-steps-rtl.ant-steps-dot.ant-steps-small .ant-steps-item-icon .ant-steps-icon-dot:after{right:-26px;left:auto}.ant-steps-rtl.ant-steps-vertical.ant-steps-dot .ant-steps-item-icon{margin-right:0;margin-left:16px}.ant-steps-rtl.ant-steps-vertical.ant-steps-dot .ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{right:-9px;left:auto}.ant-steps-rtl.ant-steps-vertical.ant-steps-dot .ant-steps-item:first-child .ant-steps-icon-dot{right:0;left:auto}.ant-steps-rtl.ant-steps-vertical.ant-steps-dot .ant-steps-item-process .ant-steps-icon-dot{right:-2px;left:auto}.ant-steps-rtl.ant-steps-with-progress.ant-steps-horizontal.ant-steps-label-horizontal .ant-steps-item:first-child{padding-right:4px;padding-left:0}.ant-steps-rtl.ant-steps-with-progress.ant-steps-horizontal.ant-steps-label-horizontal .ant-steps-item:first-child.ant-steps-item-active{padding-right:4px}.ant-steps-with-progress .ant-steps-item{padding-top:4px}.ant-steps-with-progress .ant-steps-item .ant-steps-item-tail{top:4px!important}.ant-steps-with-progress.ant-steps-horizontal .ant-steps-item:first-child{padding-bottom:4px;padding-left:4px}.ant-steps-with-progress .ant-steps-item-icon{position:relative}.ant-steps-with-progress .ant-steps-item-icon .ant-progress{position:absolute;inset:-5px}.ant-switch{margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;box-sizing:border-box;min-width:44px;height:22px;line-height:22px;vertical-align:middle;background-image:linear-gradient(to right,rgba(0,0,0,.25),rgba(0,0,0,.25)),linear-gradient(to right,#fff,#fff);border:0;border-radius:100px;cursor:pointer;transition:all .2s;-webkit-user-select:none;user-select:none}.ant-switch:focus{outline:0;box-shadow:0 0 0 2px #0000001a}.ant-switch-checked:focus{box-shadow:0 0 0 2px #e6f7ff}.ant-switch:focus:hover{box-shadow:none}.ant-switch-checked{background:#1890ff}.ant-switch-loading,.ant-switch-disabled{cursor:not-allowed;opacity:.4}.ant-switch-loading *,.ant-switch-disabled *{box-shadow:none;cursor:not-allowed}.ant-switch-inner{display:block;margin:0 7px 0 25px;color:#fff;font-size:12px;transition:margin .2s}.ant-switch-checked .ant-switch-inner{margin:0 25px 0 7px}.ant-switch-handle{position:absolute;top:2px;left:2px;width:18px;height:18px;transition:all .2s ease-in-out}.ant-switch-handle:before{position:absolute;inset:0;background-color:#fff;border-radius:9px;box-shadow:0 2px 4px #00230b33;transition:all .2s ease-in-out;content:""}.ant-switch-checked .ant-switch-handle{left:calc(100% - 20px)}.ant-switch:not(.ant-switch-disabled):active .ant-switch-handle:before{right:-30%;left:0}.ant-switch:not(.ant-switch-disabled):active.ant-switch-checked .ant-switch-handle:before{right:0;left:-30%}.ant-switch-loading-icon.anticon{position:relative;top:2px;color:#000000a6;vertical-align:top}.ant-switch-checked .ant-switch-loading-icon{color:#1890ff}.ant-switch-small{min-width:28px;height:16px;line-height:16px}.ant-switch-small .ant-switch-inner{margin:0 5px 0 18px;font-size:12px}.ant-switch-small .ant-switch-handle{width:12px;height:12px}.ant-switch-small .ant-switch-loading-icon{top:1.5px;font-size:9px}.ant-switch-small.ant-switch-checked .ant-switch-inner{margin:0 18px 0 5px}.ant-switch-small.ant-switch-checked .ant-switch-handle{left:calc(100% - 14px)}.ant-switch-rtl{direction:rtl}.ant-switch-rtl .ant-switch-inner{margin:0 25px 0 7px}.ant-switch-rtl .ant-switch-handle{right:2px;left:auto}.ant-switch-rtl:not(.ant-switch-rtl-disabled):active .ant-switch-handle:before{right:0;left:-30%}.ant-switch-rtl:not(.ant-switch-rtl-disabled):active.ant-switch-checked .ant-switch-handle:before{right:-30%;left:0}.ant-switch-rtl.ant-switch-checked .ant-switch-inner{margin:0 7px 0 25px}.ant-switch-rtl.ant-switch-checked .ant-switch-handle{right:calc(100% - 20px)}.ant-switch-rtl.ant-switch-small.ant-switch-checked .ant-switch-handle{right:calc(100% - 14px)}nz-switch{display:inline-block}.ant-table.ant-table-middle{font-size:14px}.ant-table.ant-table-middle .ant-table-title,.ant-table.ant-table-middle .ant-table-footer,.ant-table.ant-table-middle .ant-table-thead>tr>th,.ant-table.ant-table-middle .ant-table-tbody>tr>td,.ant-table.ant-table-middle tfoot>tr>th,.ant-table.ant-table-middle tfoot>tr>td{padding:12px 8px}.ant-table.ant-table-middle .ant-table-filter-trigger{margin-right:-4px}.ant-table.ant-table-middle .ant-table-expanded-row-fixed{margin:-12px -8px}.ant-table.ant-table-middle .ant-table-tbody .ant-table-wrapper:only-child .ant-table{margin:-12px -8px -12px 25px}.ant-table.ant-table-middle .ant-table-selection-column{padding-inline-start:2px}.ant-table.ant-table-small{font-size:14px}.ant-table.ant-table-small .ant-table-title,.ant-table.ant-table-small .ant-table-footer,.ant-table.ant-table-small .ant-table-thead>tr>th,.ant-table.ant-table-small .ant-table-tbody>tr>td,.ant-table.ant-table-small tfoot>tr>th,.ant-table.ant-table-small tfoot>tr>td{padding:8px}.ant-table.ant-table-small .ant-table-filter-trigger{margin-right:-4px}.ant-table.ant-table-small .ant-table-expanded-row-fixed{margin:-8px}.ant-table.ant-table-small .ant-table-tbody .ant-table-wrapper:only-child .ant-table{margin:-8px -8px -8px 25px}.ant-table.ant-table-small .ant-table-selection-column{padding-inline-start:2px}.ant-table.ant-table-bordered>.ant-table-title{border:1px solid #f0f0f0;border-bottom:0}.ant-table.ant-table-bordered>.ant-table-container{border-left:1px solid #f0f0f0}.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>thead>tr>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>thead>tr>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>thead>tr>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>thead>tr>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>tbody>tr>td,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>tbody>tr>td,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>tbody>tr>td,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>tbody>tr>td,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>tfoot>tr>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>tfoot>tr>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>tfoot>tr>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>tfoot>tr>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>tfoot>tr>td,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>tfoot>tr>td,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>tfoot>tr>td,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>tfoot>tr>td{border-right:1px solid #f0f0f0}.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>thead>tr:not(:last-child)>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>thead>tr:not(:last-child)>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>thead>tr:not(:last-child)>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>thead>tr:not(:last-child)>th{border-bottom:1px solid #f0f0f0}.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>thead>tr>th:before,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>thead>tr>th:before,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>thead>tr>th:before,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>thead>tr>th:before{background-color:transparent!important}.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>thead>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>thead>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>thead>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>thead>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>tbody>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>tbody>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>tbody>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>tbody>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>tfoot>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>tfoot>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>tfoot>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>tfoot>tr>.ant-table-cell-fix-right-first:after{border-right:1px solid #f0f0f0}.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>tbody>tr>td>.ant-table-expanded-row-fixed,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>tbody>tr>td>.ant-table-expanded-row-fixed,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>tbody>tr>td>.ant-table-expanded-row-fixed,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>tbody>tr>td>.ant-table-expanded-row-fixed{margin:-16px -17px}.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>tbody>tr>td>.ant-table-expanded-row-fixed:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>tbody>tr>td>.ant-table-expanded-row-fixed:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>tbody>tr>td>.ant-table-expanded-row-fixed:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>tbody>tr>td>.ant-table-expanded-row-fixed:after{position:absolute;top:0;right:1px;bottom:0;border-right:1px solid #f0f0f0;content:""}.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table{border-top:1px solid #f0f0f0}.ant-table.ant-table-bordered.ant-table-scroll-horizontal>.ant-table-container>.ant-table-body>table>tbody>tr.ant-table-expanded-row>td,.ant-table.ant-table-bordered.ant-table-scroll-horizontal>.ant-table-container>.ant-table-body>table>tbody>tr.ant-table-placeholder>td{border-right:0}.ant-table.ant-table-bordered.ant-table-middle>.ant-table-container>.ant-table-content>table>tbody>tr>td>.ant-table-expanded-row-fixed,.ant-table.ant-table-bordered.ant-table-middle>.ant-table-container>.ant-table-body>table>tbody>tr>td>.ant-table-expanded-row-fixed{margin:-12px -9px}.ant-table.ant-table-bordered.ant-table-small>.ant-table-container>.ant-table-content>table>tbody>tr>td>.ant-table-expanded-row-fixed,.ant-table.ant-table-bordered.ant-table-small>.ant-table-container>.ant-table-body>table>tbody>tr>td>.ant-table-expanded-row-fixed{margin:-8px -9px}.ant-table.ant-table-bordered>.ant-table-footer{border:1px solid #f0f0f0;border-top:0}.ant-table-cell .ant-table-container:first-child{border-top:0}.ant-table-cell-scrollbar:not([rowspan]){box-shadow:0 1px 0 1px #fafafa}.ant-table-wrapper{clear:both;max-width:100%}.ant-table-wrapper:before{display:table;content:""}.ant-table-wrapper:after{display:table;clear:both;content:""}.ant-table{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;font-size:14px;background:#fff;border-radius:4px}.ant-table table{width:100%;text-align:left;border-radius:4px 4px 0 0;border-collapse:separate;border-spacing:0}.ant-table-thead>tr>th,.ant-table-tbody>tr>td,.ant-table tfoot>tr>th,.ant-table tfoot>tr>td{position:relative;padding:16px;overflow-wrap:break-word}.ant-table-cell-ellipsis{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;word-break:keep-all}.ant-table-cell-ellipsis.ant-table-cell-fix-left-last,.ant-table-cell-ellipsis.ant-table-cell-fix-right-first{overflow:visible}.ant-table-cell-ellipsis.ant-table-cell-fix-left-last .ant-table-cell-content,.ant-table-cell-ellipsis.ant-table-cell-fix-right-first .ant-table-cell-content{display:block;overflow:hidden;text-overflow:ellipsis}.ant-table-cell-ellipsis .ant-table-column-title{overflow:hidden;text-overflow:ellipsis;word-break:keep-all}.ant-table-title{padding:16px}.ant-table-footer{padding:16px;color:#000000d9;background:#fafafa}.ant-table-thead>tr>th{position:relative;color:#000000d9;font-weight:500;text-align:left;background:#fafafa;border-bottom:1px solid #f0f0f0;transition:background .3s ease}.ant-table-thead>tr>th[colspan]:not([colspan="1"]){text-align:center}.ant-table-thead>tr>th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan]):before{position:absolute;top:50%;right:0;width:1px;height:1.6em;background-color:#0000000f;transform:translateY(-50%);transition:background-color .3s;content:""}.ant-table-thead>tr:not(:last-child)>th[colspan]{border-bottom:0}.ant-table-tbody>tr>td{border-bottom:1px solid #f0f0f0;transition:background .3s}.ant-table-tbody>tr>td>.ant-table-wrapper:only-child .ant-table,.ant-table-tbody>tr>td>.ant-table-expanded-row-fixed>.ant-table-wrapper:only-child .ant-table{margin:-16px -16px -16px 33px}.ant-table-tbody>tr>td>.ant-table-wrapper:only-child .ant-table-tbody>tr:last-child>td,.ant-table-tbody>tr>td>.ant-table-expanded-row-fixed>.ant-table-wrapper:only-child .ant-table-tbody>tr:last-child>td{border-bottom:0}.ant-table-tbody>tr>td>.ant-table-wrapper:only-child .ant-table-tbody>tr:last-child>td:first-child,.ant-table-tbody>tr>td>.ant-table-expanded-row-fixed>.ant-table-wrapper:only-child .ant-table-tbody>tr:last-child>td:first-child,.ant-table-tbody>tr>td>.ant-table-wrapper:only-child .ant-table-tbody>tr:last-child>td:last-child,.ant-table-tbody>tr>td>.ant-table-expanded-row-fixed>.ant-table-wrapper:only-child .ant-table-tbody>tr:last-child>td:last-child{border-radius:0}.ant-table-tbody>tr.ant-table-row:hover>td,.ant-table-tbody>tr>td.ant-table-cell-row-hover{background:#fafafa}.ant-table-tbody>tr.ant-table-row-selected>td{background:#e6f7ff;border-color:#00000008}.ant-table-tbody>tr.ant-table-row-selected:hover>td{background:#dcf4ff}.ant-table-summary{position:relative;z-index:2;background:#fff}div.ant-table-summary{box-shadow:0 -1px #f0f0f0}.ant-table-summary>tr>th,.ant-table-summary>tr>td{border-bottom:1px solid #f0f0f0}.ant-table-pagination.ant-pagination{margin:16px 0}.ant-table-pagination{display:flex;flex-wrap:wrap;row-gap:8px}.ant-table-pagination>*{flex:none}.ant-table-pagination-left{justify-content:flex-start}.ant-table-pagination-center{justify-content:center}.ant-table-pagination-right{justify-content:flex-end}.ant-table-thead th.ant-table-column-has-sorters{outline:none;cursor:pointer;transition:all .3s}.ant-table-thead th.ant-table-column-has-sorters:hover{background:rgba(0,0,0,.04)}.ant-table-thead th.ant-table-column-has-sorters:hover:before{background-color:transparent!important}.ant-table-thead th.ant-table-column-has-sorters:focus-visible{color:#1890ff}.ant-table-thead th.ant-table-column-has-sorters.ant-table-cell-fix-left:hover,.ant-table-thead th.ant-table-column-has-sorters.ant-table-cell-fix-right:hover,.ant-table-thead th.ant-table-column-sort{background:#f5f5f5}.ant-table-thead th.ant-table-column-sort:before{background-color:transparent!important}td.ant-table-column-sort{background:#fafafa}.ant-table-column-title{position:relative;z-index:1;flex:1}.ant-table-column-sorters{display:flex;flex:auto;align-items:center;justify-content:space-between}.ant-table-column-sorters:after{position:absolute;inset:0;width:100%;height:100%;content:""}.ant-table-column-sorter{margin-left:4px;color:#bfbfbf;font-size:0;transition:color .3s}.ant-table-column-sorter-inner{display:inline-flex;flex-direction:column;align-items:center}.ant-table-column-sorter-up,.ant-table-column-sorter-down{font-size:11px}.ant-table-column-sorter-up.active,.ant-table-column-sorter-down.active{color:#1890ff}.ant-table-column-sorter-up+.ant-table-column-sorter-down{margin-top:-.3em}.ant-table-column-sorters:hover .ant-table-column-sorter{color:#a6a6a6}.ant-table-filter-column{display:flex;justify-content:space-between}.ant-table-filter-trigger{position:relative;display:flex;align-items:center;margin:-4px -8px -4px 4px;padding:0 4px;color:#bfbfbf;font-size:12px;border-radius:4px;cursor:pointer;transition:all .3s}.ant-table-filter-trigger:hover{color:#00000073;background:rgba(0,0,0,.04)}.ant-table-filter-trigger.active{color:#1890ff}.ant-table-filter-dropdown{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";min-width:120px;background-color:#fff;border-radius:4px;box-shadow:0 3px 6px -4px #0000001f,0 6px 16px #00000014,0 9px 28px 8px #0000000d}.ant-table-filter-dropdown .ant-dropdown-menu{max-height:264px;overflow-x:hidden;border:0;box-shadow:none}.ant-table-filter-dropdown .ant-dropdown-menu:empty:after{display:block;padding:8px 0;color:#00000040;font-size:12px;text-align:center;content:"Not Found"}.ant-table-filter-dropdown-tree{padding:8px 8px 0}.ant-table-filter-dropdown-tree .ant-tree-treenode .ant-tree-node-content-wrapper:hover{background-color:#f5f5f5}.ant-table-filter-dropdown-tree .ant-tree-treenode-checkbox-checked .ant-tree-node-content-wrapper,.ant-table-filter-dropdown-tree .ant-tree-treenode-checkbox-checked .ant-tree-node-content-wrapper:hover{background-color:#bae7ff}.ant-table-filter-dropdown-search{padding:8px;border-bottom:1px #f0f0f0 solid}.ant-table-filter-dropdown-search-input input{min-width:140px}.ant-table-filter-dropdown-search-input .anticon{color:#00000040}.ant-table-filter-dropdown-checkall{width:100%;margin-bottom:4px;margin-left:4px}.ant-table-filter-dropdown-submenu>ul{max-height:calc(100vh - 130px);overflow-x:hidden;overflow-y:auto}.ant-table-filter-dropdown .ant-checkbox-wrapper+span,.ant-table-filter-dropdown-submenu .ant-checkbox-wrapper+span{padding-left:8px}.ant-table-filter-dropdown-btns{display:flex;justify-content:space-between;padding:7px 8px;overflow:hidden;background-color:inherit;border-top:1px solid #f0f0f0}.ant-table-selection-col{width:32px}.ant-table-bordered .ant-table-selection-col{width:50px}table tr th.ant-table-selection-column,table tr td.ant-table-selection-column{padding-right:8px;padding-left:8px;text-align:center}table tr th.ant-table-selection-column .ant-radio-wrapper,table tr td.ant-table-selection-column .ant-radio-wrapper{margin-right:0}table tr th.ant-table-selection-column.ant-table-cell-fix-left{z-index:3}table tr th.ant-table-selection-column:after{background-color:transparent!important}.ant-table-selection{position:relative;display:inline-flex;flex-direction:column}.ant-table-selection-extra{position:absolute;top:0;z-index:1;cursor:pointer;transition:all .3s;margin-inline-start:100%;padding-inline-start:4px}.ant-table-selection-extra .anticon{color:#bfbfbf;font-size:10px}.ant-table-selection-extra .anticon:hover{color:#a6a6a6}.ant-table-expand-icon-col{width:48px}.ant-table-row-expand-icon-cell{text-align:center}.ant-table-row-indent{float:left;height:1px}.ant-table-row-expand-icon{color:#1890ff;text-decoration:none;cursor:pointer;transition:color .3s;position:relative;display:inline-flex;float:left;box-sizing:border-box;width:17px;height:17px;padding:0;color:inherit;line-height:17px;background:#fff;border:1px solid #f0f0f0;border-radius:4px;outline:none;transform:scale(.94117647);transition:all .3s;-webkit-user-select:none;user-select:none}.ant-table-row-expand-icon:focus,.ant-table-row-expand-icon:hover{color:#40a9ff}.ant-table-row-expand-icon:active{color:#096dd9}.ant-table-row-expand-icon:focus,.ant-table-row-expand-icon:hover,.ant-table-row-expand-icon:active{border-color:currentcolor}.ant-table-row-expand-icon:before,.ant-table-row-expand-icon:after{position:absolute;background:currentcolor;transition:transform .3s ease-out;content:""}.ant-table-row-expand-icon:before{top:7px;right:3px;left:3px;height:1px}.ant-table-row-expand-icon:after{top:3px;bottom:3px;left:7px;width:1px;transform:rotate(90deg)}.ant-table-row-expand-icon-collapsed:before{transform:rotate(-180deg)}.ant-table-row-expand-icon-collapsed:after{transform:rotate(0)}.ant-table-row-expand-icon-spaced{background:transparent;border:0;visibility:hidden}.ant-table-row-expand-icon-spaced:before,.ant-table-row-expand-icon-spaced:after{display:none;content:none}.ant-table-row-indent+.ant-table-row-expand-icon{margin-top:2.5005px;margin-right:8px}tr.ant-table-expanded-row>td,tr.ant-table-expanded-row:hover>td{background:#fbfbfb}tr.ant-table-expanded-row .ant-descriptions-view{display:flex}tr.ant-table-expanded-row .ant-descriptions-view table{flex:auto;width:auto}.ant-table .ant-table-expanded-row-fixed{position:relative;margin:-16px;padding:16px}.ant-table-tbody>tr.ant-table-placeholder{text-align:center}.ant-table-empty .ant-table-tbody>tr.ant-table-placeholder{color:#00000040}.ant-table-tbody>tr.ant-table-placeholder:hover>td{background:#fff}.ant-table-cell-fix-left,.ant-table-cell-fix-right{position:sticky!important;z-index:2;background:#fff}.ant-table-cell-fix-left-first:after,.ant-table-cell-fix-left-last:after{position:absolute;top:0;right:0;bottom:-1px;width:30px;transform:translate(100%);transition:box-shadow .3s;content:"";pointer-events:none}.ant-table-cell-fix-right-first:after,.ant-table-cell-fix-right-last:after{position:absolute;top:0;bottom:-1px;left:0;width:30px;transform:translate(-100%);transition:box-shadow .3s;content:"";pointer-events:none}.ant-table .ant-table-container:before,.ant-table .ant-table-container:after{position:absolute;top:0;bottom:0;z-index:1;width:30px;transition:box-shadow .3s;content:"";pointer-events:none}.ant-table .ant-table-container:before{left:0}.ant-table .ant-table-container:after{right:0}.ant-table-ping-left:not(.ant-table-has-fix-left) .ant-table-container{position:relative}.ant-table-ping-left:not(.ant-table-has-fix-left) .ant-table-container:before{box-shadow:inset 10px 0 8px -8px #00000026}.ant-table-ping-left .ant-table-cell-fix-left-first:after,.ant-table-ping-left .ant-table-cell-fix-left-last:after{box-shadow:inset 10px 0 8px -8px #00000026}.ant-table-ping-left .ant-table-cell-fix-left-last:before{background-color:transparent!important}.ant-table-ping-right:not(.ant-table-has-fix-right) .ant-table-container{position:relative}.ant-table-ping-right:not(.ant-table-has-fix-right) .ant-table-container:after{box-shadow:inset -10px 0 8px -8px #00000026}.ant-table-ping-right .ant-table-cell-fix-right-first:after,.ant-table-ping-right .ant-table-cell-fix-right-last:after{box-shadow:inset -10px 0 8px -8px #00000026}.ant-table-sticky-holder{position:sticky;z-index:3;background:#fff}.ant-table-sticky-scroll{position:sticky;bottom:0;z-index:3;display:flex;align-items:center;background:#ffffff;border-top:1px solid #f0f0f0;opacity:.6}.ant-table-sticky-scroll:hover{transform-origin:center bottom}.ant-table-sticky-scroll-bar{height:8px;background-color:#00000059;border-radius:4px}.ant-table-sticky-scroll-bar:hover,.ant-table-sticky-scroll-bar-active{background-color:#000c}@media all and (-ms-high-contrast: none){.ant-table-ping-left .ant-table-cell-fix-left-last:after{box-shadow:none!important}.ant-table-ping-right .ant-table-cell-fix-right-first:after{box-shadow:none!important}}.ant-table-title{border-radius:4px 4px 0 0}.ant-table-title+.ant-table-container{border-top-left-radius:0;border-top-right-radius:0}.ant-table-title+.ant-table-container table>thead>tr:first-child th:first-child{border-radius:0}.ant-table-title+.ant-table-container table>thead>tr:first-child th:last-child{border-radius:0}.ant-table-container{border-top-left-radius:4px;border-top-right-radius:4px}.ant-table-container table>thead>tr:first-child th:first-child{border-top-left-radius:4px}.ant-table-container table>thead>tr:first-child th:last-child{border-top-right-radius:4px}.ant-table-footer{border-radius:0 0 4px 4px}.ant-table-wrapper-rtl,.ant-table-rtl{direction:rtl}.ant-table-wrapper-rtl .ant-table table{text-align:right}.ant-table-wrapper-rtl .ant-table-thead>tr>th[colspan]:not([colspan="1"]){text-align:center}.ant-table-wrapper-rtl .ant-table-thead>tr>th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan]):before{right:auto;left:0}.ant-table-wrapper-rtl .ant-table-thead>tr>th{text-align:right}.ant-table-tbody>tr .ant-table-wrapper:only-child .ant-table.ant-table-rtl{margin:-16px 33px -16px -16px}.ant-table-wrapper.ant-table-wrapper-rtl .ant-table-pagination-left{justify-content:flex-end}.ant-table-wrapper.ant-table-wrapper-rtl .ant-table-pagination-right{justify-content:flex-start}.ant-table-wrapper-rtl .ant-table-column-sorter{margin-right:4px;margin-left:0}.ant-table-wrapper-rtl .ant-table-filter-column-title{padding:16px 16px 16px 2.3em}.ant-table-rtl .ant-table-thead tr th.ant-table-column-has-sorters .ant-table-filter-column-title{padding:0 0 0 2.3em}.ant-table-wrapper-rtl .ant-table-filter-trigger{margin:-4px 4px -4px -8px}.ant-dropdown-rtl .ant-table-filter-dropdown .ant-checkbox-wrapper+span,.ant-dropdown-rtl .ant-table-filter-dropdown-submenu .ant-checkbox-wrapper+span,.ant-dropdown-menu-submenu-rtl.ant-table-filter-dropdown .ant-checkbox-wrapper+span,.ant-dropdown-menu-submenu-rtl.ant-table-filter-dropdown-submenu .ant-checkbox-wrapper+span{padding-right:8px;padding-left:0}.ant-table-wrapper-rtl .ant-table-selection{text-align:center}.ant-table-wrapper-rtl .ant-table-row-indent,.ant-table-wrapper-rtl .ant-table-row-expand-icon{float:right}.ant-table-wrapper-rtl .ant-table-row-indent+.ant-table-row-expand-icon{margin-right:0;margin-left:8px}.ant-table-wrapper-rtl .ant-table-row-expand-icon:after{transform:rotate(-90deg)}.ant-table-wrapper-rtl .ant-table-row-expand-icon-collapsed:before{transform:rotate(180deg)}.ant-table-wrapper-rtl .ant-table-row-expand-icon-collapsed:after{transform:rotate(0)}nz-table,nz-table-title-footer,nz-table-inner-scroll,nz-table-inner-default,nz-table-selection{display:block}nz-filter-trigger{display:inline-flex}.nz-table-out-bordered>.ant-table-title{border:1px solid #f0f0f0;border-bottom:0}.nz-table-out-bordered>.ant-table-container{border:1px solid #f0f0f0;border-bottom:0}.nz-table-out-bordered>.ant-table-footer{border:1px solid #f0f0f0;border-top:0}cdk-virtual-scroll-viewport.ant-table-body{overflow-y:scroll}.nz-table-hide-scrollbar{scrollbar-color:#fafafa #fafafa}.nz-table-hide-scrollbar::-webkit-scrollbar{background-color:#fafafa}.ant-table.ant-table-small .nz-table-hide-scrollbar{scrollbar-color:#fafafa #fafafa}.ant-table.ant-table-small .nz-table-hide-scrollbar::-webkit-scrollbar{background-color:transparent}.ant-table-wrapper-rtl .ant-table thead>tr>th.ant-table-selection-column{text-align:center}.ant-table.ant-table-bordered>.ant-table-container>.cdk-virtual-scroll-viewport>.cdk-virtual-scroll-content-wrapper>table>thead>tr>th,.ant-table.ant-table-bordered>.ant-table-container>.cdk-virtual-scroll-viewport>.cdk-virtual-scroll-content-wrapper>table>tbody>tr>td,.ant-table.ant-table-bordered>.ant-table-container>.cdk-virtual-scroll-viewport>.cdk-virtual-scroll-content-wrapper>table>tfoot>tr>th,.ant-table.ant-table-bordered>.ant-table-container>.cdk-virtual-scroll-viewport>.cdk-virtual-scroll-content-wrapper>table>tfoot>tr>td{border-right:1px solid #f0f0f0}.ant-table.ant-table-bordered>.ant-table-container>.cdk-virtual-scroll-viewport>.cdk-virtual-scroll-content-wrapper>table>thead>tr:not(:last-child)>th{border-bottom:1px solid #f0f0f0}.ant-table.ant-table-bordered>.ant-table-container>.cdk-virtual-scroll-viewport>.cdk-virtual-scroll-content-wrapper>table>thead>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.cdk-virtual-scroll-viewport>.cdk-virtual-scroll-content-wrapper>table>tbody>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.cdk-virtual-scroll-viewport>.cdk-virtual-scroll-content-wrapper>table>tfoot>tr>.ant-table-cell-fix-right-first:after{border-right:1px solid #f0f0f0}.ant-table.ant-table-bordered>.ant-table-container>.cdk-virtual-scroll-viewport>.cdk-virtual-scroll-content-wrapper>table>tbody>tr>td>.ant-table-expanded-row-fixed{margin:-16px -17px}.ant-table.ant-table-bordered>.ant-table-container>.cdk-virtual-scroll-viewport>.cdk-virtual-scroll-content-wrapper>table>tbody>tr>td>.ant-table-expanded-row-fixed:after{position:absolute;top:0;right:1px;bottom:0;border-right:1px solid #f0f0f0;content:""}.ant-table.ant-table-bordered.ant-table-scroll-horizontal>.ant-table-container>.cdk-virtual-scroll-viewport>.cdk-virtual-scroll-content-wrapper>table>tbody>tr.ant-table-expanded-row>td,.ant-table.ant-table-bordered.ant-table-scroll-horizontal>.ant-table-container>.cdk-virtual-scroll-viewport>.cdk-virtual-scroll-content-wrapper>table>tbody>tr.ant-table-placeholder>td{border-right:0}.ant-table.ant-table-bordered.ant-table-middle>.ant-table-container>.cdk-virtual-scroll-viewport>.cdk-virtual-scroll-content-wrapper>table>tbody>tr>td>.ant-table-expanded-row-fixed{margin:-12px -9px}.ant-table.ant-table-bordered.ant-table-small>.ant-table-container>.cdk-virtual-scroll-viewport>.cdk-virtual-scroll-content-wrapper>table>tbody>tr>td>.ant-table-expanded-row-fixed{margin:-8px -9px}.ant-tabs-small>.ant-tabs-nav .ant-tabs-tab{padding:8px 0;font-size:14px}.ant-tabs-large>.ant-tabs-nav .ant-tabs-tab{padding:16px 0;font-size:16px}.ant-tabs-card.ant-tabs-small>.ant-tabs-nav .ant-tabs-tab{padding:6px 16px}.ant-tabs-card.ant-tabs-large>.ant-tabs-nav .ant-tabs-tab{padding:7px 16px 6px}.ant-tabs-rtl{direction:rtl}.ant-tabs-rtl .ant-tabs-nav .ant-tabs-tab{margin:0 0 0 32px}.ant-tabs-rtl .ant-tabs-nav .ant-tabs-tab:last-of-type{margin-left:0}.ant-tabs-rtl .ant-tabs-nav .ant-tabs-tab .anticon{margin-right:0;margin-left:12px}.ant-tabs-rtl .ant-tabs-nav .ant-tabs-tab .ant-tabs-tab-remove{margin-right:8px;margin-left:-4px}.ant-tabs-rtl .ant-tabs-nav .ant-tabs-tab .ant-tabs-tab-remove .anticon{margin:0}.ant-tabs-rtl.ant-tabs-left>.ant-tabs-nav{order:1}.ant-tabs-rtl.ant-tabs-left>.ant-tabs-content-holder{order:0}.ant-tabs-rtl.ant-tabs-right>.ant-tabs-nav{order:0}.ant-tabs-rtl.ant-tabs-right>.ant-tabs-content-holder{order:1}.ant-tabs-rtl.ant-tabs-card.ant-tabs-top>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-rtl.ant-tabs-card.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-rtl.ant-tabs-card.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-rtl.ant-tabs-card.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab{margin-right:2px;margin-left:0}.ant-tabs-rtl.ant-tabs-card.ant-tabs-top>.ant-tabs-nav .ant-tabs-nav-add,.ant-tabs-rtl.ant-tabs-card.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-nav-add,.ant-tabs-rtl.ant-tabs-card.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-nav-add,.ant-tabs-rtl.ant-tabs-card.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-nav-add{margin-right:2px;margin-left:0}.ant-tabs-dropdown-rtl{direction:rtl}.ant-tabs-dropdown-rtl .ant-tabs-dropdown-menu-item{text-align:right}.ant-tabs-top,.ant-tabs-bottom{flex-direction:column}.ant-tabs-top>.ant-tabs-nav,.ant-tabs-bottom>.ant-tabs-nav,.ant-tabs-top>div>.ant-tabs-nav,.ant-tabs-bottom>div>.ant-tabs-nav{margin:0 0 16px}.ant-tabs-top>.ant-tabs-nav:before,.ant-tabs-bottom>.ant-tabs-nav:before,.ant-tabs-top>div>.ant-tabs-nav:before,.ant-tabs-bottom>div>.ant-tabs-nav:before{position:absolute;right:0;left:0;border-bottom:1px solid #f0f0f0;content:""}.ant-tabs-top>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-ink-bar{height:2px}.ant-tabs-top>.ant-tabs-nav .ant-tabs-ink-bar-animated,.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-ink-bar-animated,.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-ink-bar-animated,.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-ink-bar-animated{transition:width .3s,left .3s,right .3s}.ant-tabs-top>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-top>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-nav-wrap:after{top:0;bottom:0;width:30px}.ant-tabs-top>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-nav-wrap:before{left:0;box-shadow:inset 10px 0 8px -8px #00000014}.ant-tabs-top>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-nav-wrap:after{right:0;box-shadow:inset -10px 0 8px -8px #00000014}.ant-tabs-top>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-left:before,.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-left:before,.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-left:before,.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-left:before{opacity:1}.ant-tabs-top>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-right:after,.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-right:after,.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-right:after,.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-right:after{opacity:1}.ant-tabs-top>.ant-tabs-nav:before,.ant-tabs-top>div>.ant-tabs-nav:before{bottom:0}.ant-tabs-top>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-ink-bar{bottom:0}.ant-tabs-bottom>.ant-tabs-nav,.ant-tabs-bottom>div>.ant-tabs-nav{order:1;margin-top:16px;margin-bottom:0}.ant-tabs-bottom>.ant-tabs-nav:before,.ant-tabs-bottom>div>.ant-tabs-nav:before{top:0}.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-ink-bar{top:0}.ant-tabs-bottom>.ant-tabs-content-holder,.ant-tabs-bottom>div>.ant-tabs-content-holder{order:0}.ant-tabs-left>.ant-tabs-nav,.ant-tabs-right>.ant-tabs-nav,.ant-tabs-left>div>.ant-tabs-nav,.ant-tabs-right>div>.ant-tabs-nav{flex-direction:column;min-width:50px}.ant-tabs-left>.ant-tabs-nav .ant-tabs-tab,.ant-tabs-right>.ant-tabs-nav .ant-tabs-tab,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-tab,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-tab{padding:8px 24px;text-align:center}.ant-tabs-left>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-right>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab{margin:16px 0 0}.ant-tabs-left>.ant-tabs-nav .ant-tabs-nav-wrap,.ant-tabs-right>.ant-tabs-nav .ant-tabs-nav-wrap,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-nav-wrap,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-nav-wrap{flex-direction:column}.ant-tabs-left>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-right>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-left>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-right>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-nav-wrap:after{right:0;left:0;height:30px}.ant-tabs-left>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-right>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-nav-wrap:before{top:0;box-shadow:inset 0 10px 8px -8px #00000014}.ant-tabs-left>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-right>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-nav-wrap:after{bottom:0;box-shadow:inset 0 -10px 8px -8px #00000014}.ant-tabs-left>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-top:before,.ant-tabs-right>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-top:before,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-top:before,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-top:before{opacity:1}.ant-tabs-left>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-bottom:after,.ant-tabs-right>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-bottom:after,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-bottom:after,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-bottom:after{opacity:1}.ant-tabs-left>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-right>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-ink-bar{width:2px}.ant-tabs-left>.ant-tabs-nav .ant-tabs-ink-bar-animated,.ant-tabs-right>.ant-tabs-nav .ant-tabs-ink-bar-animated,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-ink-bar-animated,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-ink-bar-animated{transition:height .3s,top .3s}.ant-tabs-left>.ant-tabs-nav .ant-tabs-nav-list,.ant-tabs-right>.ant-tabs-nav .ant-tabs-nav-list,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-nav-list,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-nav-list,.ant-tabs-left>.ant-tabs-nav .ant-tabs-nav-operations,.ant-tabs-right>.ant-tabs-nav .ant-tabs-nav-operations,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-nav-operations,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-nav-operations{flex:1 0 auto;flex-direction:column}.ant-tabs-left>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-ink-bar{right:0}.ant-tabs-left>.ant-tabs-content-holder,.ant-tabs-left>div>.ant-tabs-content-holder{margin-left:-1px;border-left:1px solid #f0f0f0}.ant-tabs-left>.ant-tabs-content-holder>.ant-tabs-content>.ant-tabs-tabpane,.ant-tabs-left>div>.ant-tabs-content-holder>.ant-tabs-content>.ant-tabs-tabpane{padding-left:24px}.ant-tabs-right>.ant-tabs-nav,.ant-tabs-right>div>.ant-tabs-nav{order:1}.ant-tabs-right>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-ink-bar{left:0}.ant-tabs-right>.ant-tabs-content-holder,.ant-tabs-right>div>.ant-tabs-content-holder{order:0;margin-right:-1px;border-right:1px solid #f0f0f0}.ant-tabs-right>.ant-tabs-content-holder>.ant-tabs-content>.ant-tabs-tabpane,.ant-tabs-right>div>.ant-tabs-content-holder>.ant-tabs-content>.ant-tabs-tabpane{padding-right:24px}.ant-tabs-dropdown{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;top:-9999px;left:-9999px;z-index:1050;display:block}.ant-tabs-dropdown-hidden{display:none}.ant-tabs-dropdown-menu{max-height:200px;margin:0;padding:4px 0;overflow-x:hidden;overflow-y:auto;text-align:left;list-style-type:none;background-color:#fff;background-clip:padding-box;border-radius:4px;outline:none;box-shadow:0 3px 6px -4px #0000001f,0 6px 16px #00000014,0 9px 28px 8px #0000000d}.ant-tabs-dropdown-menu-item{display:flex;align-items:center;min-width:120px;margin:0;padding:5px 12px;overflow:hidden;color:#000000d9;font-weight:400;font-size:14px;line-height:22px;white-space:nowrap;text-overflow:ellipsis;cursor:pointer;transition:all .3s}.ant-tabs-dropdown-menu-item>span{flex:1;white-space:nowrap}.ant-tabs-dropdown-menu-item-remove{flex:none;margin-left:12px;color:#00000073;font-size:12px;background:transparent;border:0;cursor:pointer}.ant-tabs-dropdown-menu-item-remove:hover{color:#40a9ff}.ant-tabs-dropdown-menu-item:hover{background:#f5f5f5}.ant-tabs-dropdown-menu-item-disabled,.ant-tabs-dropdown-menu-item-disabled:hover{color:#00000040;background:transparent;cursor:not-allowed}.ant-tabs-card>.ant-tabs-nav .ant-tabs-tab,.ant-tabs-card>div>.ant-tabs-nav .ant-tabs-tab{margin:0;padding:8px 16px;background:#fafafa;border:1px solid #f0f0f0;transition:all .3s cubic-bezier(.645,.045,.355,1)}.ant-tabs-card>.ant-tabs-nav .ant-tabs-tab-active,.ant-tabs-card>div>.ant-tabs-nav .ant-tabs-tab-active{color:#1890ff;background:#fff}.ant-tabs-card>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-card>div>.ant-tabs-nav .ant-tabs-ink-bar{visibility:hidden}.ant-tabs-card.ant-tabs-top>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-card.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-card.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-card.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab{margin-left:2px}.ant-tabs-card.ant-tabs-top>.ant-tabs-nav .ant-tabs-tab,.ant-tabs-card.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-tab{border-radius:4px 4px 0 0}.ant-tabs-card.ant-tabs-top>.ant-tabs-nav .ant-tabs-tab-active,.ant-tabs-card.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-tab-active{border-bottom-color:#fff}.ant-tabs-card.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-tab,.ant-tabs-card.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-tab{border-radius:0 0 4px 4px}.ant-tabs-card.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-tab-active,.ant-tabs-card.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-tab-active{border-top-color:#fff}.ant-tabs-card.ant-tabs-left>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-card.ant-tabs-right>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-card.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-card.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab{margin-top:2px}.ant-tabs-card.ant-tabs-left>.ant-tabs-nav .ant-tabs-tab,.ant-tabs-card.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-tab{border-radius:4px 0 0 4px}.ant-tabs-card.ant-tabs-left>.ant-tabs-nav .ant-tabs-tab-active,.ant-tabs-card.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-tab-active{border-right-color:#fff}.ant-tabs-card.ant-tabs-right>.ant-tabs-nav .ant-tabs-tab,.ant-tabs-card.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-tab{border-radius:0 4px 4px 0}.ant-tabs-card.ant-tabs-right>.ant-tabs-nav .ant-tabs-tab-active,.ant-tabs-card.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-tab-active{border-left-color:#fff}.ant-tabs{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:flex}.ant-tabs>.ant-tabs-nav,.ant-tabs>div>.ant-tabs-nav{position:relative;display:flex;flex:none;align-items:center}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-wrap,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-wrap{position:relative;display:inline-block;display:flex;flex:auto;align-self:stretch;overflow:hidden;white-space:nowrap;transform:translate(0)}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-wrap:after{position:absolute;z-index:1;opacity:0;transition:opacity .3s;content:"";pointer-events:none}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-list,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-list{position:relative;display:flex;transition:transform .3s}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-operations,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-operations{display:flex;align-self:stretch}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-operations-hidden,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-operations-hidden{position:absolute;visibility:hidden;pointer-events:none}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-more,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-more{position:relative;padding:8px 16px;background:transparent;border:0}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-more:after,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-more:after{position:absolute;right:0;bottom:0;left:0;height:5px;transform:translateY(100%);content:""}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-add,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-add{min-width:40px;margin-left:2px;padding:0 8px;background:#fafafa;border:1px solid #f0f0f0;border-radius:4px 4px 0 0;outline:none;cursor:pointer;transition:all .3s cubic-bezier(.645,.045,.355,1)}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-add:hover,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-add:hover{color:#40a9ff}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-add:active,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-add:active,.ant-tabs>.ant-tabs-nav .ant-tabs-nav-add:focus,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-add:focus{color:#096dd9}.ant-tabs-extra-content{flex:none}.ant-tabs-centered>.ant-tabs-nav .ant-tabs-nav-wrap:not([class*="ant-tabs-nav-wrap-ping"]),.ant-tabs-centered>div>.ant-tabs-nav .ant-tabs-nav-wrap:not([class*="ant-tabs-nav-wrap-ping"]){justify-content:center}.ant-tabs-ink-bar{position:absolute;background:#1890ff;pointer-events:none}.ant-tabs-tab{position:relative;display:inline-flex;align-items:center;padding:12px 0;font-size:14px;background:transparent;border:0;outline:none;cursor:pointer}.ant-tabs-tab-btn:focus,.ant-tabs-tab-remove:focus,.ant-tabs-tab-btn:active,.ant-tabs-tab-remove:active{color:#096dd9}.ant-tabs-tab-btn{outline:none;transition:all .3s}.ant-tabs-tab-remove{flex:none;margin-right:-4px;margin-left:8px;color:#00000073;font-size:12px;background:transparent;border:none;outline:none;cursor:pointer;transition:all .3s}.ant-tabs-tab-remove:hover{color:#000000d9}.ant-tabs-tab:hover{color:#40a9ff}.ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn{color:#1890ff;text-shadow:0 0 .25px currentcolor}.ant-tabs-tab.ant-tabs-tab-disabled{color:#00000040;cursor:not-allowed}.ant-tabs-tab.ant-tabs-tab-disabled .ant-tabs-tab-btn:focus,.ant-tabs-tab.ant-tabs-tab-disabled .ant-tabs-tab-remove:focus,.ant-tabs-tab.ant-tabs-tab-disabled .ant-tabs-tab-btn:active,.ant-tabs-tab.ant-tabs-tab-disabled .ant-tabs-tab-remove:active{color:#00000040}.ant-tabs-tab .ant-tabs-tab-remove .anticon{margin:0}.ant-tabs-tab .anticon{margin-right:12px}.ant-tabs-tab+.ant-tabs-tab{margin:0 0 0 32px}.ant-tabs-content{display:flex;width:100%}.ant-tabs-content-holder{flex:auto;min-width:0;min-height:0}.ant-tabs-content-animated{transition:margin .3s}.ant-tabs-tabpane{flex:none;width:100%;outline:none}.ant-tabs-dropdown-menu-item a[nz-tab-link]{position:relative}a[nz-tab-link]:before{position:absolute;inset:0;background-color:transparent;content:""}a[nz-tab-link]~*{position:relative}nz-tabset,nz-tab-nav-operation,nz-tabs-nav{display:block;overflow:hidden}.nz-tabs-dropdown.ant-dropdown .ant-dropdown-menu{max-height:200px;margin:0;padding:4px 0;overflow-x:hidden;overflow-y:auto;text-align:left;list-style-type:none;background-color:#fff;background-clip:padding-box;border-radius:4px;outline:none;box-shadow:0 3px 6px -4px #0000001f,0 6px 16px #00000014,0 9px 28px 8px #0000000d}.nz-tabs-dropdown.ant-dropdown .ant-dropdown-menu-item{min-width:120px;margin:0;padding:5px 12px;overflow:hidden;color:#000000d9;font-weight:400;font-size:14px;line-height:22px;white-space:nowrap;text-overflow:ellipsis;cursor:pointer;transition:all .3s}.nz-tabs-dropdown.ant-dropdown .ant-dropdown-menu-item:hover{background:#f5f5f5}.nz-tabs-dropdown.ant-dropdown .ant-dropdown-menu-item-disabled,.nz-tabs-dropdown.ant-dropdown .ant-dropdown-menu-item-disabled:hover{color:#00000040;background:transparent;cursor:not-allowed}.nz-tabs-dropdown.ant-dropdown .ant-dropdown-menu-item-disabled a,.nz-tabs-dropdown.ant-dropdown .ant-dropdown-menu-item-disabled:hover a{pointer-events:none;color:#00000040}.ant-tabs-rtl .ant-tabs-rtl-tab-next{right:auto;left:2px}.ant-tabs-tab-disabled a{pointer-events:none;color:#00000040}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-add,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-add{min-height:40px}.ant-tag{box-sizing:border-box;margin:0 8px 0 0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-block;height:auto;padding:0 7px;font-size:12px;line-height:20px;white-space:nowrap;background:#fafafa;border:1px solid #d9d9d9;border-radius:4px;opacity:1;transition:all .3s}.ant-tag,.ant-tag a,.ant-tag a:hover{color:#000000d9}.ant-tag>a:first-child:last-child{display:inline-block;margin:0 -8px;padding:0 8px}.ant-tag-close-icon{margin-left:3px;color:#00000073;font-size:10px;cursor:pointer;transition:all .3s}.ant-tag-close-icon:hover{color:#000000d9}.ant-tag-has-color{border-color:transparent}.ant-tag-has-color,.ant-tag-has-color a,.ant-tag-has-color a:hover,.ant-tag-has-color .anticon-close,.ant-tag-has-color .anticon-close:hover{color:#fff}.ant-tag-checkable{background-color:transparent;border-color:transparent;cursor:pointer}.ant-tag-checkable:not(.ant-tag-checkable-checked):hover{color:#1890ff}.ant-tag-checkable:active,.ant-tag-checkable-checked{color:#fff}.ant-tag-checkable-checked{background-color:#1890ff}.ant-tag-checkable:active{background-color:#096dd9}.ant-tag-hidden{display:none}.ant-tag-pink{color:#c41d7f;background:#fff0f6;border-color:#ffadd2}.ant-tag-pink-inverse{color:#fff;background:#eb2f96;border-color:#eb2f96}.ant-tag-magenta{color:#c41d7f;background:#fff0f6;border-color:#ffadd2}.ant-tag-magenta-inverse{color:#fff;background:#eb2f96;border-color:#eb2f96}.ant-tag-red{color:#cf1322;background:#fff1f0;border-color:#ffa39e}.ant-tag-red-inverse{color:#fff;background:#f5222d;border-color:#f5222d}.ant-tag-volcano{color:#d4380d;background:#fff2e8;border-color:#ffbb96}.ant-tag-volcano-inverse{color:#fff;background:#fa541c;border-color:#fa541c}.ant-tag-orange{color:#d46b08;background:#fff7e6;border-color:#ffd591}.ant-tag-orange-inverse{color:#fff;background:#fa8c16;border-color:#fa8c16}.ant-tag-yellow{color:#d4b106;background:#feffe6;border-color:#fffb8f}.ant-tag-yellow-inverse{color:#fff;background:#fadb14;border-color:#fadb14}.ant-tag-gold{color:#d48806;background:#fffbe6;border-color:#ffe58f}.ant-tag-gold-inverse{color:#fff;background:#faad14;border-color:#faad14}.ant-tag-cyan{color:#08979c;background:#e6fffb;border-color:#87e8de}.ant-tag-cyan-inverse{color:#fff;background:#13c2c2;border-color:#13c2c2}.ant-tag-lime{color:#7cb305;background:#fcffe6;border-color:#eaff8f}.ant-tag-lime-inverse{color:#fff;background:#a0d911;border-color:#a0d911}.ant-tag-green{color:#389e0d;background:#f6ffed;border-color:#b7eb8f}.ant-tag-green-inverse{color:#fff;background:#52c41a;border-color:#52c41a}.ant-tag-blue{color:#096dd9;background:#e6f7ff;border-color:#91d5ff}.ant-tag-blue-inverse{color:#fff;background:#1890ff;border-color:#1890ff}.ant-tag-geekblue{color:#1d39c4;background:#f0f5ff;border-color:#adc6ff}.ant-tag-geekblue-inverse{color:#fff;background:#2f54eb;border-color:#2f54eb}.ant-tag-purple{color:#531dab;background:#f9f0ff;border-color:#d3adf7}.ant-tag-purple-inverse{color:#fff;background:#722ed1;border-color:#722ed1}.ant-tag-success{color:#52c41a;background:#f6ffed;border-color:#b7eb8f}.ant-tag-processing{color:#1890ff;background:#e6f7ff;border-color:#91d5ff}.ant-tag-error{color:#ff4d4f;background:#fff2f0;border-color:#ffccc7}.ant-tag-warning{color:#faad14;background:#fffbe6;border-color:#ffe58f}.ant-tag>.anticon+span,.ant-tag>span+.anticon{margin-left:7px}.ant-tag.ant-tag-rtl{margin-right:0;margin-left:8px;direction:rtl;text-align:right}.ant-tag-rtl .ant-tag-close-icon{margin-right:3px;margin-left:0}.ant-tag-rtl.ant-tag>.anticon+span,.ant-tag-rtl.ant-tag>span+.anticon{margin-right:7px;margin-left:0}.ant-timeline{box-sizing:border-box;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;font-feature-settings:"tnum";margin:0;padding:0;list-style:none}.ant-timeline-item{position:relative;margin:0;padding-bottom:20px;font-size:14px;list-style:none}.ant-timeline-item-tail{position:absolute;top:10px;left:4px;height:calc(100% - 10px);border-left:2px solid #f0f0f0}.ant-timeline-item-pending .ant-timeline-item-head{font-size:12px;background-color:transparent}.ant-timeline-item-pending .ant-timeline-item-tail{display:none}.ant-timeline-item-head{position:absolute;width:10px;height:10px;background-color:#fff;border:2px solid transparent;border-radius:100px}.ant-timeline-item-head-blue{color:#1890ff;border-color:#1890ff}.ant-timeline-item-head-red{color:#ff4d4f;border-color:#ff4d4f}.ant-timeline-item-head-green{color:#52c41a;border-color:#52c41a}.ant-timeline-item-head-gray{color:#00000040;border-color:#00000040}.ant-timeline-item-head-custom{position:absolute;top:5.5px;left:5px;width:auto;height:auto;margin-top:0;padding:3px 1px;line-height:1;text-align:center;border:0;border-radius:0;transform:translate(-50%,-50%)}.ant-timeline-item-content{position:relative;top:-7.001px;margin:0 0 0 26px;word-break:break-word}.ant-timeline-item-last>.ant-timeline-item-tail{display:none}.ant-timeline-item-last>.ant-timeline-item-content{min-height:48px}.ant-timeline.ant-timeline-alternate .ant-timeline-item-tail,.ant-timeline.ant-timeline-right .ant-timeline-item-tail,.ant-timeline.ant-timeline-label .ant-timeline-item-tail,.ant-timeline.ant-timeline-alternate .ant-timeline-item-head,.ant-timeline.ant-timeline-right .ant-timeline-item-head,.ant-timeline.ant-timeline-label .ant-timeline-item-head,.ant-timeline.ant-timeline-alternate .ant-timeline-item-head-custom,.ant-timeline.ant-timeline-right .ant-timeline-item-head-custom,.ant-timeline.ant-timeline-label .ant-timeline-item-head-custom{left:50%}.ant-timeline.ant-timeline-alternate .ant-timeline-item-head,.ant-timeline.ant-timeline-right .ant-timeline-item-head,.ant-timeline.ant-timeline-label .ant-timeline-item-head{margin-left:-4px}.ant-timeline.ant-timeline-alternate .ant-timeline-item-head-custom,.ant-timeline.ant-timeline-right .ant-timeline-item-head-custom,.ant-timeline.ant-timeline-label .ant-timeline-item-head-custom{margin-left:1px}.ant-timeline.ant-timeline-alternate .ant-timeline-item-left .ant-timeline-item-content,.ant-timeline.ant-timeline-right .ant-timeline-item-left .ant-timeline-item-content,.ant-timeline.ant-timeline-label .ant-timeline-item-left .ant-timeline-item-content{left:calc(50% - 4px);width:calc(50% - 14px);text-align:left}.ant-timeline.ant-timeline-alternate .ant-timeline-item-right .ant-timeline-item-content,.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-content,.ant-timeline.ant-timeline-label .ant-timeline-item-right .ant-timeline-item-content{width:calc(50% - 12px);margin:0;text-align:right}.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-tail,.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-head,.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-head-custom{left:calc(100% - 6px)}.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-content{width:calc(100% - 18px)}.ant-timeline.ant-timeline-pending .ant-timeline-item-last .ant-timeline-item-tail{display:block;height:calc(100% - 14px);border-left:2px dotted #f0f0f0}.ant-timeline.ant-timeline-reverse .ant-timeline-item-last .ant-timeline-item-tail{display:none}.ant-timeline.ant-timeline-reverse .ant-timeline-item-pending .ant-timeline-item-tail{top:15px;display:block;height:calc(100% - 15px);border-left:2px dotted #f0f0f0}.ant-timeline.ant-timeline-reverse .ant-timeline-item-pending .ant-timeline-item-content{min-height:48px}.ant-timeline.ant-timeline-label .ant-timeline-item-label{position:absolute;top:-7.001px;width:calc(50% - 12px);text-align:right}.ant-timeline.ant-timeline-label .ant-timeline-item-right .ant-timeline-item-label{left:calc(50% + 14px);width:calc(50% - 14px);text-align:left}.ant-timeline-rtl{direction:rtl}.ant-timeline-rtl .ant-timeline-item-tail{right:4px;left:auto;border-right:2px solid #f0f0f0;border-left:none}.ant-timeline-rtl .ant-timeline-item-head-custom{right:5px;left:auto;transform:translate(50%,-50%)}.ant-timeline-rtl .ant-timeline-item-content{margin:0 18px 0 0}.ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-tail,.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-tail,.ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-tail,.ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-head,.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-head,.ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-head,.ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-head-custom,.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-head-custom,.ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-head-custom{right:50%;left:auto}.ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-head,.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-head,.ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-head{margin-right:-4px;margin-left:0}.ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-head-custom,.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-head-custom,.ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-head-custom{margin-right:1px;margin-left:0}.ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-left .ant-timeline-item-content,.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-left .ant-timeline-item-content,.ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-left .ant-timeline-item-content{right:calc(50% - 4px);left:auto;text-align:right}.ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-right .ant-timeline-item-content,.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-content,.ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-right .ant-timeline-item-content{text-align:left}.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-tail,.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-head,.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-head-custom{right:0;left:auto}.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-content{width:100%;margin-right:18px;text-align:right}.ant-timeline-rtl.ant-timeline.ant-timeline-pending .ant-timeline-item-last .ant-timeline-item-tail,.ant-timeline-rtl.ant-timeline.ant-timeline-reverse .ant-timeline-item-pending .ant-timeline-item-tail{border-right:2px dotted #f0f0f0;border-left:none}.ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-label{text-align:left}.ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-right .ant-timeline-item-label{right:calc(50% + 14px);text-align:right}.ant-tooltip{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;z-index:1070;display:block;width:max-content;width:intrinsic;max-width:250px;visibility:visible}.ant-tooltip-content{position:relative}.ant-tooltip-hidden{display:none}.ant-tooltip-placement-top,.ant-tooltip-placement-topLeft,.ant-tooltip-placement-topRight{padding-bottom:14.3137085px}.ant-tooltip-placement-right,.ant-tooltip-placement-rightTop,.ant-tooltip-placement-rightBottom{padding-left:14.3137085px}.ant-tooltip-placement-bottom,.ant-tooltip-placement-bottomLeft,.ant-tooltip-placement-bottomRight{padding-top:14.3137085px}.ant-tooltip-placement-left,.ant-tooltip-placement-leftTop,.ant-tooltip-placement-leftBottom{padding-right:14.3137085px}.ant-tooltip-inner{min-width:30px;min-height:32px;padding:6px 8px;color:#fff;text-align:left;text-decoration:none;word-wrap:break-word;background-color:#4a4a4a;border-radius:4px;box-shadow:0 3px 6px -4px #0000001f,0 6px 16px #00000014,0 9px 28px 8px #0000000d}.ant-tooltip-arrow{position:absolute;z-index:2;display:block;width:22px;height:22px;overflow:hidden;background:transparent;pointer-events:none}.ant-tooltip-arrow-content{--antd-arrow-background-color: linear-gradient(to right bottom, rgba(74, 74, 74, .9), #4a4a4a);position:absolute;inset:0;display:block;width:11.3137085px;height:11.3137085px;margin:auto;background-color:transparent;content:"";pointer-events:auto;border-radius:0 0 2px;pointer-events:none}.ant-tooltip-arrow-content:before{position:absolute;top:-11.3137085px;left:-11.3137085px;width:33.9411255px;height:33.9411255px;background:var(--antd-arrow-background-color);background-repeat:no-repeat;background-position:-10px -10px;content:"";clip-path:path("M 9.849242404917499 24.091883092036785 A 5 5 0 0 1 13.384776310850237 22.627416997969522 L 20.627416997969522 22.627416997969522 A 2 2 0 0 0 22.627416997969522 20.627416997969522 L 22.627416997969522 13.384776310850237 A 5 5 0 0 1 24.091883092036785 9.849242404917499 L 23.091883092036785 9.849242404917499 L 9.849242404917499 23.091883092036785 Z")}.ant-tooltip-placement-top .ant-tooltip-arrow,.ant-tooltip-placement-topLeft .ant-tooltip-arrow,.ant-tooltip-placement-topRight .ant-tooltip-arrow{bottom:0;transform:translateY(100%)}.ant-tooltip-placement-top .ant-tooltip-arrow-content,.ant-tooltip-placement-topLeft .ant-tooltip-arrow-content,.ant-tooltip-placement-topRight .ant-tooltip-arrow-content{box-shadow:3px 3px 7px #00000012;transform:translateY(-11px) rotate(45deg)}.ant-tooltip-placement-top .ant-tooltip-arrow{left:50%;transform:translateY(100%) translate(-50%)}.ant-tooltip-placement-topLeft .ant-tooltip-arrow{left:13px}.ant-tooltip-placement-topRight .ant-tooltip-arrow{right:13px}.ant-tooltip-placement-right .ant-tooltip-arrow,.ant-tooltip-placement-rightTop .ant-tooltip-arrow,.ant-tooltip-placement-rightBottom .ant-tooltip-arrow{left:0;transform:translate(-100%)}.ant-tooltip-placement-right .ant-tooltip-arrow-content,.ant-tooltip-placement-rightTop .ant-tooltip-arrow-content,.ant-tooltip-placement-rightBottom .ant-tooltip-arrow-content{box-shadow:-3px 3px 7px #00000012;transform:translate(11px) rotate(135deg)}.ant-tooltip-placement-right .ant-tooltip-arrow{top:50%;transform:translate(-100%) translateY(-50%)}.ant-tooltip-placement-rightTop .ant-tooltip-arrow{top:5px}.ant-tooltip-placement-rightBottom .ant-tooltip-arrow{bottom:5px}.ant-tooltip-placement-left .ant-tooltip-arrow,.ant-tooltip-placement-leftTop .ant-tooltip-arrow,.ant-tooltip-placement-leftBottom .ant-tooltip-arrow{right:0;transform:translate(100%)}.ant-tooltip-placement-left .ant-tooltip-arrow-content,.ant-tooltip-placement-leftTop .ant-tooltip-arrow-content,.ant-tooltip-placement-leftBottom .ant-tooltip-arrow-content{box-shadow:3px -3px 7px #00000012;transform:translate(-11px) rotate(315deg)}.ant-tooltip-placement-left .ant-tooltip-arrow{top:50%;transform:translate(100%) translateY(-50%)}.ant-tooltip-placement-leftTop .ant-tooltip-arrow{top:5px}.ant-tooltip-placement-leftBottom .ant-tooltip-arrow{bottom:5px}.ant-tooltip-placement-bottom .ant-tooltip-arrow,.ant-tooltip-placement-bottomLeft .ant-tooltip-arrow,.ant-tooltip-placement-bottomRight .ant-tooltip-arrow{top:0;transform:translateY(-100%)}.ant-tooltip-placement-bottom .ant-tooltip-arrow-content,.ant-tooltip-placement-bottomLeft .ant-tooltip-arrow-content,.ant-tooltip-placement-bottomRight .ant-tooltip-arrow-content{box-shadow:-3px -3px 7px #00000012;transform:translateY(11px) rotate(225deg)}.ant-tooltip-placement-bottom .ant-tooltip-arrow{left:50%;transform:translateY(-100%) translate(-50%)}.ant-tooltip-placement-bottomLeft .ant-tooltip-arrow{left:13px}.ant-tooltip-placement-bottomRight .ant-tooltip-arrow{right:13px}.ant-tooltip-pink .ant-tooltip-inner{background-color:#eb2f96}.ant-tooltip-pink .ant-tooltip-arrow-content:before{background:#eb2f96}.ant-tooltip-magenta .ant-tooltip-inner{background-color:#eb2f96}.ant-tooltip-magenta .ant-tooltip-arrow-content:before{background:#eb2f96}.ant-tooltip-red .ant-tooltip-inner{background-color:#f5222d}.ant-tooltip-red .ant-tooltip-arrow-content:before{background:#f5222d}.ant-tooltip-volcano .ant-tooltip-inner{background-color:#fa541c}.ant-tooltip-volcano .ant-tooltip-arrow-content:before{background:#fa541c}.ant-tooltip-orange .ant-tooltip-inner{background-color:#fa8c16}.ant-tooltip-orange .ant-tooltip-arrow-content:before{background:#fa8c16}.ant-tooltip-yellow .ant-tooltip-inner{background-color:#fadb14}.ant-tooltip-yellow .ant-tooltip-arrow-content:before{background:#fadb14}.ant-tooltip-gold .ant-tooltip-inner{background-color:#faad14}.ant-tooltip-gold .ant-tooltip-arrow-content:before{background:#faad14}.ant-tooltip-cyan .ant-tooltip-inner{background-color:#13c2c2}.ant-tooltip-cyan .ant-tooltip-arrow-content:before{background:#13c2c2}.ant-tooltip-lime .ant-tooltip-inner{background-color:#a0d911}.ant-tooltip-lime .ant-tooltip-arrow-content:before{background:#a0d911}.ant-tooltip-green .ant-tooltip-inner{background-color:#52c41a}.ant-tooltip-green .ant-tooltip-arrow-content:before{background:#52c41a}.ant-tooltip-blue .ant-tooltip-inner{background-color:#1890ff}.ant-tooltip-blue .ant-tooltip-arrow-content:before{background:#1890ff}.ant-tooltip-geekblue .ant-tooltip-inner{background-color:#2f54eb}.ant-tooltip-geekblue .ant-tooltip-arrow-content:before{background:#2f54eb}.ant-tooltip-purple .ant-tooltip-inner{background-color:#722ed1}.ant-tooltip-purple .ant-tooltip-arrow-content:before{background:#722ed1}.ant-tooltip-rtl{direction:rtl}.ant-tooltip-rtl .ant-tooltip-inner{text-align:right}.ant-tooltip{position:relative}.ant-transfer-customize-list .ant-transfer-list{flex:1 1 50%;width:auto;height:auto;min-height:200px}.ant-transfer-customize-list .ant-table-wrapper .ant-table-small{border:0;border-radius:0}.ant-transfer-customize-list .ant-table-wrapper .ant-table-small .ant-table-selection-column{width:40px;min-width:40px}.ant-transfer-customize-list .ant-table-wrapper .ant-table-small>.ant-table-content>.ant-table-body>table>.ant-table-thead>tr>th{background:#fafafa}.ant-transfer-customize-list .ant-table-wrapper .ant-table-small>.ant-table-content .ant-table-row:last-child td{border-bottom:1px solid #f0f0f0}.ant-transfer-customize-list .ant-table-wrapper .ant-table-small .ant-table-body{margin:0}.ant-transfer-customize-list .ant-table-wrapper .ant-table-pagination.ant-pagination{margin:16px 0 4px}.ant-transfer-customize-list .ant-input[disabled]{background-color:transparent}.ant-transfer-status-error .ant-transfer-list{border-color:#ff4d4f}.ant-transfer-status-error .ant-transfer-list-search:not([disabled]){border-color:#d9d9d9}.ant-transfer-status-error .ant-transfer-list-search:not([disabled]):hover{border-color:#40a9ff;border-right-width:1px}.ant-input-rtl .ant-transfer-status-error .ant-transfer-list-search:not([disabled]):hover{border-right-width:0;border-left-width:1px!important}.ant-transfer-status-error .ant-transfer-list-search:not([disabled]):focus{border-color:#40a9ff;box-shadow:0 0 0 2px #1890ff33;border-right-width:1px;outline:0}.ant-input-rtl .ant-transfer-status-error .ant-transfer-list-search:not([disabled]):focus{border-right-width:0;border-left-width:1px!important}.ant-transfer-status-warning .ant-transfer-list{border-color:#faad14}.ant-transfer-status-warning .ant-transfer-list-search:not([disabled]){border-color:#d9d9d9}.ant-transfer-status-warning .ant-transfer-list-search:not([disabled]):hover{border-color:#40a9ff;border-right-width:1px}.ant-input-rtl .ant-transfer-status-warning .ant-transfer-list-search:not([disabled]):hover{border-right-width:0;border-left-width:1px!important}.ant-transfer-status-warning .ant-transfer-list-search:not([disabled]):focus{border-color:#40a9ff;box-shadow:0 0 0 2px #1890ff33;border-right-width:1px;outline:0}.ant-input-rtl .ant-transfer-status-warning .ant-transfer-list-search:not([disabled]):focus{border-right-width:0;border-left-width:1px!important}.ant-transfer{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:flex;align-items:stretch}.ant-transfer-disabled .ant-transfer-list{background:#f5f5f5}.ant-transfer-list{display:flex;flex-direction:column;width:180px;height:200px;border:1px solid #d9d9d9;border-radius:4px}.ant-transfer-list-with-pagination{width:250px;height:auto}.ant-transfer-list-search .anticon-search{color:#00000040}.ant-transfer-list-header{display:flex;flex:none;align-items:center;height:40px;padding:8px 12px 9px;color:#000000d9;background:#fff;border-bottom:1px solid #f0f0f0;border-radius:4px 4px 0 0}.ant-transfer-list-header>*:not(:last-child){margin-right:4px}.ant-transfer-list-header>*{flex:none}.ant-transfer-list-header-title{flex:auto;overflow:hidden;white-space:nowrap;text-align:right;text-overflow:ellipsis}.ant-transfer-list-header-dropdown{font-size:10px;transform:translateY(10%);cursor:pointer}.ant-transfer-list-header-dropdown[disabled]{cursor:not-allowed}.ant-transfer-list-body{display:flex;flex:auto;flex-direction:column;overflow:hidden;font-size:14px}.ant-transfer-list-body-search-wrapper{position:relative;flex:none;padding:12px}.ant-transfer-list-content{flex:auto;margin:0;padding:0;overflow:auto;list-style:none}.ant-transfer-list-content-item{display:flex;align-items:center;min-height:32px;padding:6px 12px;line-height:20px;transition:all .3s}.ant-transfer-list-content-item>*:not(:last-child){margin-right:8px}.ant-transfer-list-content-item>*{flex:none}.ant-transfer-list-content-item-text{flex:auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-transfer-list-content-item-remove{color:#1890ff;text-decoration:none;outline:none;cursor:pointer;transition:color .3s;position:relative;color:#d9d9d9}.ant-transfer-list-content-item-remove:focus,.ant-transfer-list-content-item-remove:hover{color:#40a9ff}.ant-transfer-list-content-item-remove:active{color:#096dd9}.ant-transfer-list-content-item-remove:after{position:absolute;inset:-6px -50%;content:""}.ant-transfer-list-content-item-remove:hover{color:#40a9ff}.ant-transfer-list-content-item:not(.ant-transfer-list-content-item-disabled):hover{background-color:#f5f5f5;cursor:pointer}.ant-transfer-list-content-item:not(.ant-transfer-list-content-item-disabled).ant-transfer-list-content-item-checked:hover{background-color:#dcf4ff}.ant-transfer-list-content-show-remove .ant-transfer-list-content-item:not(.ant-transfer-list-content-item-disabled):hover{background:transparent;cursor:default}.ant-transfer-list-content-item-checked{background-color:#e6f7ff}.ant-transfer-list-content-item-disabled{color:#00000040;cursor:not-allowed}.ant-transfer-list-pagination{padding:8px 0;text-align:right;border-top:1px solid #f0f0f0}.ant-transfer-list-body-not-found{flex:none;width:100%;margin:auto 0;color:#00000040;text-align:center}.ant-transfer-list-footer{border-top:1px solid #f0f0f0}.ant-transfer-operation{display:flex;flex:none;flex-direction:column;align-self:center;margin:0 8px;vertical-align:middle}.ant-transfer-operation .ant-btn{display:block}.ant-transfer-operation .ant-btn:first-child{margin-bottom:4px}.ant-transfer-operation .ant-btn .anticon{font-size:12px}.ant-transfer .ant-empty-image{max-height:-2px}.ant-transfer-rtl{direction:rtl}.ant-transfer-rtl .ant-transfer-list-search{padding-right:8px;padding-left:24px}.ant-transfer-rtl .ant-transfer-list-search-action{right:auto;left:12px}.ant-transfer-rtl .ant-transfer-list-header>*:not(:last-child){margin-right:0;margin-left:4px}.ant-transfer-rtl .ant-transfer-list-header{right:0;left:auto}.ant-transfer-rtl .ant-transfer-list-header-title{text-align:left}.ant-transfer-rtl .ant-transfer-list-content-item>*:not(:last-child){margin-right:0;margin-left:8px}.ant-transfer-rtl .ant-transfer-list-pagination{text-align:left}.ant-transfer-rtl .ant-transfer-list-footer{right:0;left:auto}.ant-typography{color:#000000d9;overflow-wrap:break-word}.ant-typography.ant-typography-secondary{color:#00000073}.ant-typography.ant-typography-success{color:#52c41a}.ant-typography.ant-typography-warning{color:#faad14}.ant-typography.ant-typography-danger{color:#ff4d4f}a.ant-typography.ant-typography-danger:active,a.ant-typography.ant-typography-danger:focus{color:#d9363e}a.ant-typography.ant-typography-danger:hover{color:#ff7875}.ant-typography.ant-typography-disabled{color:#00000040;cursor:not-allowed;-webkit-user-select:none;user-select:none}div.ant-typography,.ant-typography p{margin-bottom:1em}h1.ant-typography,div.ant-typography-h1,div.ant-typography-h1>textarea,.ant-typography h1{margin-bottom:.5em;color:#000000d9;font-weight:600;font-size:38px;line-height:1.23}h2.ant-typography,div.ant-typography-h2,div.ant-typography-h2>textarea,.ant-typography h2{margin-bottom:.5em;color:#000000d9;font-weight:600;font-size:30px;line-height:1.35}h3.ant-typography,div.ant-typography-h3,div.ant-typography-h3>textarea,.ant-typography h3{margin-bottom:.5em;color:#000000d9;font-weight:600;font-size:24px;line-height:1.35}h4.ant-typography,div.ant-typography-h4,div.ant-typography-h4>textarea,.ant-typography h4{margin-bottom:.5em;color:#000000d9;font-weight:600;font-size:20px;line-height:1.4}h5.ant-typography,div.ant-typography-h5,div.ant-typography-h5>textarea,.ant-typography h5{margin-bottom:.5em;color:#000000d9;font-weight:600;font-size:16px;line-height:1.5}.ant-typography+h1.ant-typography,.ant-typography+h2.ant-typography,.ant-typography+h3.ant-typography,.ant-typography+h4.ant-typography,.ant-typography+h5.ant-typography{margin-top:1.2em}.ant-typography div+h1,.ant-typography ul+h1,.ant-typography li+h1,.ant-typography p+h1,.ant-typography h1+h1,.ant-typography h2+h1,.ant-typography h3+h1,.ant-typography h4+h1,.ant-typography h5+h1,.ant-typography div+h2,.ant-typography ul+h2,.ant-typography li+h2,.ant-typography p+h2,.ant-typography h1+h2,.ant-typography h2+h2,.ant-typography h3+h2,.ant-typography h4+h2,.ant-typography h5+h2,.ant-typography div+h3,.ant-typography ul+h3,.ant-typography li+h3,.ant-typography p+h3,.ant-typography h1+h3,.ant-typography h2+h3,.ant-typography h3+h3,.ant-typography h4+h3,.ant-typography h5+h3,.ant-typography div+h4,.ant-typography ul+h4,.ant-typography li+h4,.ant-typography p+h4,.ant-typography h1+h4,.ant-typography h2+h4,.ant-typography h3+h4,.ant-typography h4+h4,.ant-typography h5+h4,.ant-typography div+h5,.ant-typography ul+h5,.ant-typography li+h5,.ant-typography p+h5,.ant-typography h1+h5,.ant-typography h2+h5,.ant-typography h3+h5,.ant-typography h4+h5,.ant-typography h5+h5{margin-top:1.2em}a.ant-typography-ellipsis,span.ant-typography-ellipsis{display:inline-block;max-width:100%}a.ant-typography,.ant-typography a{color:#1890ff;outline:none;cursor:pointer;transition:color .3s;text-decoration:none}a.ant-typography:focus,.ant-typography a:focus,a.ant-typography:hover,.ant-typography a:hover{color:#40a9ff}a.ant-typography:active,.ant-typography a:active{color:#096dd9}a.ant-typography:active,.ant-typography a:active,a.ant-typography:hover,.ant-typography a:hover{text-decoration:none}a.ant-typography[disabled],.ant-typography a[disabled],a.ant-typography.ant-typography-disabled,.ant-typography a.ant-typography-disabled{color:#00000040;cursor:not-allowed}a.ant-typography[disabled]:active,.ant-typography a[disabled]:active,a.ant-typography.ant-typography-disabled:active,.ant-typography a.ant-typography-disabled:active,a.ant-typography[disabled]:hover,.ant-typography a[disabled]:hover,a.ant-typography.ant-typography-disabled:hover,.ant-typography a.ant-typography-disabled:hover{color:#00000040}a.ant-typography[disabled]:active,.ant-typography a[disabled]:active,a.ant-typography.ant-typography-disabled:active,.ant-typography a.ant-typography-disabled:active{pointer-events:none}.ant-typography code{margin:0 .2em;padding:.2em .4em .1em;font-size:85%;background:rgba(150,150,150,.1);border:1px solid rgba(100,100,100,.2);border-radius:3px}.ant-typography kbd{margin:0 .2em;padding:.15em .4em .1em;font-size:90%;background:rgba(150,150,150,.06);border:1px solid rgba(100,100,100,.2);border-bottom-width:2px;border-radius:3px}.ant-typography mark{padding:0;background-color:#ffe58f}.ant-typography u,.ant-typography ins{text-decoration:underline;-webkit-text-decoration-skip:ink;text-decoration-skip-ink:auto}.ant-typography s,.ant-typography del{text-decoration:line-through}.ant-typography strong{font-weight:600}.ant-typography-expand,.ant-typography-edit,.ant-typography-copy{color:#1890ff;text-decoration:none;outline:none;cursor:pointer;transition:color .3s;margin-left:4px}.ant-typography-expand:focus,.ant-typography-edit:focus,.ant-typography-copy:focus,.ant-typography-expand:hover,.ant-typography-edit:hover,.ant-typography-copy:hover{color:#40a9ff}.ant-typography-expand:active,.ant-typography-edit:active,.ant-typography-copy:active{color:#096dd9}.ant-typography-copy-success,.ant-typography-copy-success:hover,.ant-typography-copy-success:focus{color:#52c41a}.ant-typography-edit-content{position:relative}div.ant-typography-edit-content{left:-12px;margin-top:-5px;margin-bottom:calc(1em - 5px)}.ant-typography-edit-content-confirm{position:absolute;right:10px;bottom:8px;color:#00000073;font-weight:400;font-size:14px;font-style:normal;pointer-events:none}.ant-typography-edit-content textarea{height:1em;margin:0!important;-moz-transition:none}.ant-typography ul,.ant-typography ol{margin:0 0 1em;padding:0}.ant-typography ul li,.ant-typography ol li{margin:0 0 0 20px;padding:0 0 0 4px}.ant-typography ul{list-style-type:circle}.ant-typography ul ul{list-style-type:disc}.ant-typography ol{list-style-type:decimal}.ant-typography pre,.ant-typography blockquote{margin:1em 0}.ant-typography pre{padding:.4em .6em;white-space:pre-wrap;word-wrap:break-word;background:rgba(150,150,150,.1);border:1px solid rgba(100,100,100,.2);border-radius:3px}.ant-typography pre code{display:inline;margin:0;padding:0;font-size:inherit;font-family:inherit;background:transparent;border:0}.ant-typography blockquote{padding:0 0 0 .6em;border-left:4px solid rgba(100,100,100,.2);opacity:.85}.ant-typography-single-line{white-space:nowrap}.ant-typography-ellipsis-single-line{overflow:hidden;text-overflow:ellipsis}a.ant-typography-ellipsis-single-line,span.ant-typography-ellipsis-single-line{vertical-align:bottom}.ant-typography-ellipsis-multiple-line{display:-webkit-box;overflow:hidden;-webkit-line-clamp:3;-webkit-box-orient:vertical}.ant-typography-rtl{direction:rtl}.ant-typography-rtl .ant-typography-expand,.ant-typography-rtl .ant-typography-edit,.ant-typography-rtl .ant-typography-copy{margin-right:4px;margin-left:0}.ant-typography-rtl .ant-typography-expand{float:left}div.ant-typography-edit-content.ant-typography-rtl{right:-12px;left:auto}.ant-typography-rtl .ant-typography-edit-content-confirm{right:auto;left:10px}.ant-typography-rtl.ant-typography ul li,.ant-typography-rtl.ant-typography ol li{margin:0 20px 0 0;padding:0 4px 0 0}.ant-upload{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";outline:0}.ant-upload p{margin:0}.ant-upload-btn{display:block;width:100%;outline:none}.ant-upload input[type=file]{cursor:pointer}.ant-upload.ant-upload-select{display:inline-block}.ant-upload.ant-upload-disabled{cursor:not-allowed}.ant-upload.ant-upload-select-picture-card{width:104px;height:104px;margin-right:8px;margin-bottom:8px;text-align:center;vertical-align:top;background-color:#fafafa;border:1px dashed #d9d9d9;border-radius:4px;cursor:pointer;transition:border-color .3s}.ant-upload.ant-upload-select-picture-card>.ant-upload{display:flex;align-items:center;justify-content:center;height:100%;text-align:center}.ant-upload.ant-upload-select-picture-card:hover{border-color:#1890ff}.ant-upload-disabled.ant-upload.ant-upload-select-picture-card:hover{border-color:#d9d9d9}.ant-upload.ant-upload-drag{position:relative;width:100%;height:100%;text-align:center;background:#fafafa;border:1px dashed #d9d9d9;border-radius:4px;cursor:pointer;transition:border-color .3s}.ant-upload.ant-upload-drag .ant-upload{padding:16px 0}.ant-upload.ant-upload-drag.ant-upload-drag-hover:not(.ant-upload-disabled){border-color:#096dd9}.ant-upload.ant-upload-drag.ant-upload-disabled{cursor:not-allowed}.ant-upload.ant-upload-drag .ant-upload-btn{display:table;height:100%}.ant-upload.ant-upload-drag .ant-upload-drag-container{display:table-cell;vertical-align:middle}.ant-upload.ant-upload-drag:not(.ant-upload-disabled):hover{border-color:#40a9ff}.ant-upload.ant-upload-drag p.ant-upload-drag-icon{margin-bottom:20px}.ant-upload.ant-upload-drag p.ant-upload-drag-icon .anticon{color:#40a9ff;font-size:48px}.ant-upload.ant-upload-drag p.ant-upload-text{margin:0 0 4px;color:#000000d9;font-size:16px}.ant-upload.ant-upload-drag p.ant-upload-hint{color:#00000073;font-size:14px}.ant-upload.ant-upload-drag .anticon-plus{color:#00000040;font-size:30px;transition:all .3s}.ant-upload.ant-upload-drag .anticon-plus:hover,.ant-upload.ant-upload-drag:hover .anticon-plus{color:#00000073}.ant-upload-picture-card-wrapper{display:inline-block;width:100%}.ant-upload-picture-card-wrapper:before{display:table;content:""}.ant-upload-picture-card-wrapper:after{display:table;clear:both;content:""}.ant-upload-list{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;list-style:none;font-feature-settings:"tnum";line-height:1.5715}.ant-upload-list:before{display:table;content:""}.ant-upload-list:after{display:table;clear:both;content:""}.ant-upload-list-item{position:relative;height:22.001px;margin-top:8px;font-size:14px}.ant-upload-list-item-name{display:inline-block;width:100%;padding-left:22px;overflow:hidden;line-height:1.5715;white-space:nowrap;text-overflow:ellipsis}.ant-upload-list-item-card-actions{position:absolute;right:0}.ant-upload-list-item-card-actions-btn{opacity:0}.ant-upload-list-item-card-actions-btn.ant-btn-sm{height:22.001px;line-height:1;vertical-align:top}.ant-upload-list-item-card-actions.picture{top:22px;line-height:0}.ant-upload-list-item-card-actions-btn:focus,.ant-upload-list-item-card-actions.picture .ant-upload-list-item-card-actions-btn{opacity:1}.ant-upload-list-item-card-actions .anticon{color:#00000073;transition:all .3s}.ant-upload-list-item-card-actions:hover .anticon{color:#000000d9}.ant-upload-list-item-info{height:100%;transition:background-color .3s}.ant-upload-list-item-info>span{display:block;width:100%;height:100%}.ant-upload-list-item-info .anticon-loading .anticon,.ant-upload-list-item-info .ant-upload-text-icon .anticon{position:absolute;top:5px;color:#00000073;font-size:14px}.ant-upload-list-item:hover .ant-upload-list-item-info{background-color:#f5f5f5}.ant-upload-list-item:hover .ant-upload-list-item-card-actions-btn{opacity:1}.ant-upload-list-item-error,.ant-upload-list-item-error .ant-upload-text-icon>.anticon,.ant-upload-list-item-error .ant-upload-list-item-name{color:#ff4d4f}.ant-upload-list-item-error .ant-upload-list-item-card-actions .anticon{color:#ff4d4f}.ant-upload-list-item-error .ant-upload-list-item-card-actions-btn{opacity:1}.ant-upload-list-item-progress{position:absolute;bottom:-12px;width:100%;padding-left:26px;font-size:14px;line-height:0}.ant-upload-list-picture .ant-upload-list-item,.ant-upload-list-picture-card .ant-upload-list-item{position:relative;height:66px;padding:8px;border:1px solid #d9d9d9;border-radius:4px}.ant-upload-list-picture .ant-upload-list-item:hover,.ant-upload-list-picture-card .ant-upload-list-item:hover{background:transparent}.ant-upload-list-picture .ant-upload-list-item-error,.ant-upload-list-picture-card .ant-upload-list-item-error{border-color:#ff4d4f}.ant-upload-list-picture .ant-upload-list-item:hover .ant-upload-list-item-info,.ant-upload-list-picture-card .ant-upload-list-item:hover .ant-upload-list-item-info{background:transparent}.ant-upload-list-picture .ant-upload-list-item-uploading,.ant-upload-list-picture-card .ant-upload-list-item-uploading{border-style:dashed}.ant-upload-list-picture .ant-upload-list-item-thumbnail,.ant-upload-list-picture-card .ant-upload-list-item-thumbnail{width:48px;height:48px;line-height:60px;text-align:center;opacity:.8}.ant-upload-list-picture .ant-upload-list-item-thumbnail .anticon,.ant-upload-list-picture-card .ant-upload-list-item-thumbnail .anticon{font-size:26px}.ant-upload-list-picture .ant-upload-list-item-error .ant-upload-list-item-thumbnail .anticon svg path[fill="#e6f7ff"],.ant-upload-list-picture-card .ant-upload-list-item-error .ant-upload-list-item-thumbnail .anticon svg path[fill="#e6f7ff"]{fill:#fff2f0}.ant-upload-list-picture .ant-upload-list-item-error .ant-upload-list-item-thumbnail .anticon svg path[fill="#1890ff"],.ant-upload-list-picture-card .ant-upload-list-item-error .ant-upload-list-item-thumbnail .anticon svg path[fill="#1890ff"]{fill:#ff4d4f}.ant-upload-list-picture .ant-upload-list-item-icon,.ant-upload-list-picture-card .ant-upload-list-item-icon{position:absolute;top:50%;left:50%;font-size:26px;transform:translate(-50%,-50%)}.ant-upload-list-picture .ant-upload-list-item-icon .anticon,.ant-upload-list-picture-card .ant-upload-list-item-icon .anticon{font-size:26px}.ant-upload-list-picture .ant-upload-list-item-image,.ant-upload-list-picture-card .ant-upload-list-item-image{max-width:100%}.ant-upload-list-picture .ant-upload-list-item-thumbnail img,.ant-upload-list-picture-card .ant-upload-list-item-thumbnail img{display:block;width:48px;height:48px;overflow:hidden}.ant-upload-list-picture .ant-upload-list-item-name,.ant-upload-list-picture-card .ant-upload-list-item-name{display:inline-block;box-sizing:border-box;max-width:100%;margin:0 0 0 8px;padding-right:8px;padding-left:48px;overflow:hidden;line-height:44px;white-space:nowrap;text-overflow:ellipsis;transition:all .3s}.ant-upload-list-picture .ant-upload-list-item-uploading .ant-upload-list-item-name,.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-name{margin-bottom:12px}.ant-upload-list-picture .ant-upload-list-item-progress,.ant-upload-list-picture-card .ant-upload-list-item-progress{bottom:14px;width:calc(100% - 24px);margin-top:0;padding-left:56px}.ant-upload-list-picture-card-container{display:inline-block;width:104px;height:104px;margin:0 8px 8px 0;vertical-align:top}.ant-upload-list-picture-card .ant-upload-list-item{height:100%;margin:0}.ant-upload-list-picture-card .ant-upload-list-item-info{position:relative;height:100%;overflow:hidden}.ant-upload-list-picture-card .ant-upload-list-item-info:before{position:absolute;z-index:1;width:100%;height:100%;background-color:#00000080;opacity:0;transition:all .3s;content:" "}.ant-upload-list-picture-card .ant-upload-list-item:hover .ant-upload-list-item-info:before{opacity:1}.ant-upload-list-picture-card .ant-upload-list-item-actions{position:absolute;top:50%;left:50%;z-index:10;white-space:nowrap;transform:translate(-50%,-50%);opacity:0;transition:all .3s}.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-eye,.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-download,.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-delete{z-index:10;width:16px;margin:0 4px;color:#ffffffd9;font-size:16px;cursor:pointer;transition:all .3s}.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-eye:hover,.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-download:hover,.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-delete:hover{color:#fff}.ant-upload-list-picture-card .ant-upload-list-item-info:hover+.ant-upload-list-item-actions,.ant-upload-list-picture-card .ant-upload-list-item-actions:hover{opacity:1}.ant-upload-list-picture-card .ant-upload-list-item-thumbnail,.ant-upload-list-picture-card .ant-upload-list-item-thumbnail img{position:static;display:block;width:100%;height:100%;object-fit:contain}.ant-upload-list-picture-card .ant-upload-list-item-name{display:none;margin:8px 0 0;padding:0;line-height:1.5715;text-align:center}.ant-upload-list-picture-card .ant-upload-list-item-file+.ant-upload-list-item-name{position:absolute;bottom:10px;display:block}.ant-upload-list-picture-card .ant-upload-list-item-uploading.ant-upload-list-item{background-color:#fafafa}.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info{height:auto}.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info:before,.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info .anticon-eye,.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info .anticon-delete{display:none}.ant-upload-list-picture-card .ant-upload-list-item-progress{bottom:32px;width:calc(100% - 14px);padding-left:0}.ant-upload-list-text-container,.ant-upload-list-picture-container{transition:opacity .3s,height .3s}.ant-upload-list-text-container:before,.ant-upload-list-picture-container:before{display:table;width:0;height:0;content:""}.ant-upload-list-text-container .ant-upload-span,.ant-upload-list-picture-container .ant-upload-span{display:block;flex:auto}.ant-upload-list-text .ant-upload-span,.ant-upload-list-picture .ant-upload-span{display:flex;align-items:center}.ant-upload-list-text .ant-upload-span>*,.ant-upload-list-picture .ant-upload-span>*{flex:none}.ant-upload-list-text .ant-upload-list-item-name,.ant-upload-list-picture .ant-upload-list-item-name{flex:auto;margin:0;padding:0 8px}.ant-upload-list-text .ant-upload-list-item-card-actions,.ant-upload-list-picture .ant-upload-list-item-card-actions,.ant-upload-list-text .ant-upload-text-icon .anticon{position:static}.ant-upload-list .ant-upload-animate-inline-appear,.ant-upload-list .ant-upload-animate-inline-enter,.ant-upload-list .ant-upload-animate-inline-leave{animation-duration:.3s;animation-fill-mode:cubic-bezier(.78,.14,.15,.86)}.ant-upload-list .ant-upload-animate-inline-appear,.ant-upload-list .ant-upload-animate-inline-enter{animation-name:uploadAnimateInlineIn}.ant-upload-list .ant-upload-animate-inline-leave{animation-name:uploadAnimateInlineOut}@keyframes uploadAnimateInlineIn{0%{width:0;height:0;margin:0;padding:0;opacity:0}}@keyframes uploadAnimateInlineOut{to{width:0;height:0;margin:0;padding:0;opacity:0}}.ant-upload-rtl{direction:rtl}.ant-upload-rtl.ant-upload.ant-upload-select-picture-card{margin-right:auto;margin-left:8px}.ant-upload-list-rtl{direction:rtl}.ant-upload-list-rtl .ant-upload-list-item-list-type-text:hover .ant-upload-list-item-name-icon-count-1{padding-right:22px;padding-left:14px}.ant-upload-list-rtl .ant-upload-list-item-list-type-text:hover .ant-upload-list-item-name-icon-count-2{padding-right:22px;padding-left:28px}.ant-upload-list-rtl .ant-upload-list-item-name{padding-right:22px;padding-left:0}.ant-upload-list-rtl .ant-upload-list-item-name-icon-count-1{padding-left:14px}.ant-upload-list-rtl .ant-upload-list-item-card-actions{right:auto;left:0}.ant-upload-list-rtl .ant-upload-list-item-card-actions .anticon{padding-right:0;padding-left:5px}.ant-upload-list-rtl .ant-upload-list-item-info{padding:0 4px 0 12px}.ant-upload-list-rtl .ant-upload-list-item-error .ant-upload-list-item-card-actions .anticon{padding-right:0;padding-left:5px}.ant-upload-list-rtl .ant-upload-list-item-progress{padding-right:26px;padding-left:0}.ant-upload-list-picture .ant-upload-list-item-info,.ant-upload-list-picture-card .ant-upload-list-item-info{padding:0}.ant-upload-list-rtl.ant-upload-list-picture .ant-upload-list-item-thumbnail,.ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-thumbnail{right:8px;left:auto}.ant-upload-list-rtl.ant-upload-list-picture .ant-upload-list-item-icon,.ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-icon{right:50%;left:auto;transform:translate(50%,-50%)}.ant-upload-list-rtl.ant-upload-list-picture .ant-upload-list-item-name,.ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-name{margin:0 8px 0 0;padding-right:48px;padding-left:8px}.ant-upload-list-rtl.ant-upload-list-picture .ant-upload-list-item-name-icon-count-1,.ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-name-icon-count-1{padding-right:48px;padding-left:18px}.ant-upload-list-rtl.ant-upload-list-picture .ant-upload-list-item-name-icon-count-2,.ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-name-icon-count-2{padding-right:48px;padding-left:36px}.ant-upload-list-rtl.ant-upload-list-picture .ant-upload-list-item-progress,.ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-progress{padding-right:0;padding-left:0}.ant-upload-list-rtl .ant-upload-list-picture-card-container{margin:0 0 8px 8px}.ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-actions{right:50%;left:auto;transform:translate(50%,-50%)}.ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-file+.ant-upload-list-item-name{margin:8px 0 0;padding:0}.ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item{float:unset}.ant-select-auto-complete{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum"}.ant-select-auto-complete .ant-select-clear{right:13px}.ant-select-dropdown-hidden{display:none}.ant-cascader-checkbox{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;top:.2em;line-height:1;white-space:nowrap;outline:none;cursor:pointer}.ant-cascader-checkbox-wrapper:hover .ant-cascader-checkbox-inner,.ant-cascader-checkbox:hover .ant-cascader-checkbox-inner,.ant-cascader-checkbox-input:focus+.ant-cascader-checkbox-inner{border-color:#1890ff}.ant-cascader-checkbox-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #1890ff;border-radius:4px;visibility:hidden;animation:antCheckboxEffect .36s ease-in-out;animation-fill-mode:backwards;content:""}.ant-cascader-checkbox:hover:after,.ant-cascader-checkbox-wrapper:hover .ant-cascader-checkbox:after{visibility:visible}.ant-cascader-checkbox-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;direction:ltr;background-color:#fff;border:1px solid #d9d9d9;border-radius:4px;border-collapse:separate;transition:all .3s}.ant-cascader-checkbox-inner:after{position:absolute;top:50%;left:21.5%;display:table;width:5.71428571px;height:9.14285714px;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(0) translate(-50%,-50%);opacity:0;transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;content:" "}.ant-cascader-checkbox-input{position:absolute;inset:0;z-index:1;width:100%;height:100%;cursor:pointer;opacity:0}.ant-cascader-checkbox-checked .ant-cascader-checkbox-inner:after{position:absolute;display:table;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(1) translate(-50%,-50%);opacity:1;transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;content:" "}.ant-cascader-checkbox-checked .ant-cascader-checkbox-inner{background-color:#1890ff;border-color:#1890ff}.ant-cascader-checkbox-disabled{cursor:not-allowed}.ant-cascader-checkbox-disabled.ant-cascader-checkbox-checked .ant-cascader-checkbox-inner:after{border-color:#00000040;animation-name:none}.ant-cascader-checkbox-disabled .ant-cascader-checkbox-input{cursor:not-allowed;pointer-events:none}.ant-cascader-checkbox-disabled .ant-cascader-checkbox-inner{background-color:#f5f5f5;border-color:#d9d9d9!important}.ant-cascader-checkbox-disabled .ant-cascader-checkbox-inner:after{border-color:#f5f5f5;border-collapse:separate;animation-name:none}.ant-cascader-checkbox-disabled+span{color:#00000040;cursor:not-allowed}.ant-cascader-checkbox-disabled:hover:after,.ant-cascader-checkbox-wrapper:hover .ant-cascader-checkbox-disabled:after{visibility:hidden}.ant-cascader-checkbox-wrapper{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-flex;align-items:baseline;line-height:unset;cursor:pointer}.ant-cascader-checkbox-wrapper:after{display:inline-block;width:0;overflow:hidden;content:"\a0"}.ant-cascader-checkbox-wrapper.ant-cascader-checkbox-wrapper-disabled{cursor:not-allowed}.ant-cascader-checkbox-wrapper+.ant-cascader-checkbox-wrapper{margin-left:8px}.ant-cascader-checkbox-wrapper.ant-cascader-checkbox-wrapper-in-form-item input[type=checkbox]{width:14px;height:14px}.ant-cascader-checkbox+span{padding-right:8px;padding-left:8px}.ant-cascader-checkbox-group{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-block}.ant-cascader-checkbox-group-item{margin-right:8px}.ant-cascader-checkbox-group-item:last-child{margin-right:0}.ant-cascader-checkbox-group-item+.ant-cascader-checkbox-group-item{margin-left:0}.ant-cascader-checkbox-indeterminate .ant-cascader-checkbox-inner{background-color:#fff;border-color:#d9d9d9}.ant-cascader-checkbox-indeterminate .ant-cascader-checkbox-inner:after{top:50%;left:50%;width:8px;height:8px;background-color:#1890ff;border:0;transform:translate(-50%,-50%) scale(1);opacity:1;content:" "}.ant-cascader-checkbox-indeterminate.ant-cascader-checkbox-disabled .ant-cascader-checkbox-inner:after{background-color:#00000040;border-color:#00000040}.ant-cascader-checkbox-rtl{direction:rtl}.ant-cascader-checkbox-group-rtl .ant-cascader-checkbox-group-item{margin-right:0;margin-left:8px}.ant-cascader-checkbox-group-rtl .ant-cascader-checkbox-group-item:last-child{margin-left:0!important}.ant-cascader-checkbox-group-rtl .ant-cascader-checkbox-group-item+.ant-cascader-checkbox-group-item{margin-left:8px}.ant-cascader{width:184px}.ant-cascader-checkbox{top:0;margin-right:8px}.ant-cascader-menus{display:flex;flex-wrap:nowrap;align-items:flex-start}.ant-cascader-menus.ant-cascader-menu-empty .ant-cascader-menu{width:100%;height:auto}.ant-cascader-menu{flex-grow:1;min-width:111px;height:180px;margin:-4px 0;padding:4px 0;overflow:auto;vertical-align:top;list-style:none;border-right:1px solid #f0f0f0;-ms-overflow-style:-ms-autohiding-scrollbar}.ant-cascader-menu-item{display:flex;flex-wrap:nowrap;align-items:center;padding:5px 12px;overflow:hidden;line-height:22px;white-space:nowrap;text-overflow:ellipsis;cursor:pointer;transition:all .3s}.ant-cascader-menu-item:hover{background:#f5f5f5}.ant-cascader-menu-item-disabled{color:#00000040;cursor:not-allowed}.ant-cascader-menu-item-disabled:hover{background:transparent}.ant-cascader-menu-empty .ant-cascader-menu-item{color:#00000040;cursor:default;pointer-events:none}.ant-cascader-menu-item-active:not(.ant-cascader-menu-item-disabled),.ant-cascader-menu-item-active:not(.ant-cascader-menu-item-disabled):hover{font-weight:600;background-color:#e6f7ff}.ant-cascader-menu-item-content{flex:auto}.ant-cascader-menu-item-expand .ant-cascader-menu-item-expand-icon,.ant-cascader-menu-item-loading-icon{margin-left:4px;color:#00000073;font-size:10px}.ant-cascader-menu-item-disabled.ant-cascader-menu-item-expand .ant-cascader-menu-item-expand-icon,.ant-cascader-menu-item-disabled.ant-cascader-menu-item-loading-icon{color:#00000040}.ant-cascader-menu-item-keyword{color:#ff4d4f}.ant-cascader-rtl .ant-cascader-menu-item-expand-icon,.ant-cascader-rtl .ant-cascader-menu-item-loading-icon{margin-right:4px;margin-left:0}.ant-cascader-rtl .ant-cascader-checkbox{top:0;margin-right:0;margin-left:8px}.ant-cascader-menus{position:relative;margin-top:2px;margin-bottom:2px}nz-tree-virtual-scroll-view{display:block;position:relative;overflow:auto;contain:strict;transform:translateZ(0);will-change:scroll-position;-webkit-overflow-scrolling:touch}nz-tree-virtual-scroll-view .ant-tree-list,nz-tree-virtual-scroll-view .ant-tree-list-holder{height:100%}nz-tree-virtual-scroll-view .ant-tree-switcher+.ant-tree-switcher.nz-tree-leaf-line-icon,nz-tree-view .ant-tree-switcher+.ant-tree-switcher.nz-tree-leaf-line-icon{display:none}nz-tree-view .ant-tree-list-holder-inner{display:flex;flex-direction:column}@keyframes ant-tree-node-fx-do-not-use{0%{opacity:0}to{opacity:1}}.ant-tree.ant-tree-directory .ant-tree-treenode{position:relative}.ant-tree.ant-tree-directory .ant-tree-treenode:before{position:absolute;inset:0 0 4px;transition:background-color .3s;content:"";pointer-events:none}.ant-tree.ant-tree-directory .ant-tree-treenode:hover:before{background:#f5f5f5}.ant-tree.ant-tree-directory .ant-tree-treenode>*{z-index:1}.ant-tree.ant-tree-directory .ant-tree-treenode .ant-tree-switcher{transition:color .3s}.ant-tree.ant-tree-directory .ant-tree-treenode .ant-tree-node-content-wrapper{border-radius:0;-webkit-user-select:none;user-select:none}.ant-tree.ant-tree-directory .ant-tree-treenode .ant-tree-node-content-wrapper:hover{background:transparent}.ant-tree.ant-tree-directory .ant-tree-treenode .ant-tree-node-content-wrapper.ant-tree-node-selected{color:#fff;background:transparent}.ant-tree.ant-tree-directory .ant-tree-treenode-selected:hover:before,.ant-tree.ant-tree-directory .ant-tree-treenode-selected:before{background:#1890ff}.ant-tree.ant-tree-directory .ant-tree-treenode-selected .ant-tree-switcher{color:#fff}.ant-tree.ant-tree-directory .ant-tree-treenode-selected .ant-tree-node-content-wrapper{color:#fff;background:transparent}.ant-tree-checkbox{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;top:.2em;line-height:1;white-space:nowrap;outline:none;cursor:pointer}.ant-tree-checkbox-wrapper:hover .ant-tree-checkbox-inner,.ant-tree-checkbox:hover .ant-tree-checkbox-inner,.ant-tree-checkbox-input:focus+.ant-tree-checkbox-inner{border-color:#1890ff}.ant-tree-checkbox-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #1890ff;border-radius:4px;visibility:hidden;animation:antCheckboxEffect .36s ease-in-out;animation-fill-mode:backwards;content:""}.ant-tree-checkbox:hover:after,.ant-tree-checkbox-wrapper:hover .ant-tree-checkbox:after{visibility:visible}.ant-tree-checkbox-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;direction:ltr;background-color:#fff;border:1px solid #d9d9d9;border-radius:4px;border-collapse:separate;transition:all .3s}.ant-tree-checkbox-inner:after{position:absolute;top:50%;left:21.5%;display:table;width:5.71428571px;height:9.14285714px;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(0) translate(-50%,-50%);opacity:0;transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;content:" "}.ant-tree-checkbox-input{position:absolute;inset:0;z-index:1;width:100%;height:100%;cursor:pointer;opacity:0}.ant-tree-checkbox-checked .ant-tree-checkbox-inner:after{position:absolute;display:table;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(1) translate(-50%,-50%);opacity:1;transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;content:" "}.ant-tree-checkbox-checked .ant-tree-checkbox-inner{background-color:#1890ff;border-color:#1890ff}.ant-tree-checkbox-disabled{cursor:not-allowed}.ant-tree-checkbox-disabled.ant-tree-checkbox-checked .ant-tree-checkbox-inner:after{border-color:#00000040;animation-name:none}.ant-tree-checkbox-disabled .ant-tree-checkbox-input{cursor:not-allowed;pointer-events:none}.ant-tree-checkbox-disabled .ant-tree-checkbox-inner{background-color:#f5f5f5;border-color:#d9d9d9!important}.ant-tree-checkbox-disabled .ant-tree-checkbox-inner:after{border-color:#f5f5f5;border-collapse:separate;animation-name:none}.ant-tree-checkbox-disabled+span{color:#00000040;cursor:not-allowed}.ant-tree-checkbox-disabled:hover:after,.ant-tree-checkbox-wrapper:hover .ant-tree-checkbox-disabled:after{visibility:hidden}.ant-tree-checkbox-wrapper{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-flex;align-items:baseline;line-height:unset;cursor:pointer}.ant-tree-checkbox-wrapper:after{display:inline-block;width:0;overflow:hidden;content:"\a0"}.ant-tree-checkbox-wrapper.ant-tree-checkbox-wrapper-disabled{cursor:not-allowed}.ant-tree-checkbox-wrapper+.ant-tree-checkbox-wrapper{margin-left:8px}.ant-tree-checkbox-wrapper.ant-tree-checkbox-wrapper-in-form-item input[type=checkbox]{width:14px;height:14px}.ant-tree-checkbox+span{padding-right:8px;padding-left:8px}.ant-tree-checkbox-group{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-block}.ant-tree-checkbox-group-item{margin-right:8px}.ant-tree-checkbox-group-item:last-child{margin-right:0}.ant-tree-checkbox-group-item+.ant-tree-checkbox-group-item{margin-left:0}.ant-tree-checkbox-indeterminate .ant-tree-checkbox-inner{background-color:#fff;border-color:#d9d9d9}.ant-tree-checkbox-indeterminate .ant-tree-checkbox-inner:after{top:50%;left:50%;width:8px;height:8px;background-color:#1890ff;border:0;transform:translate(-50%,-50%) scale(1);opacity:1;content:" "}.ant-tree-checkbox-indeterminate.ant-tree-checkbox-disabled .ant-tree-checkbox-inner:after{background-color:#00000040;border-color:#00000040}.ant-tree-checkbox-rtl{direction:rtl}.ant-tree-checkbox-group-rtl .ant-tree-checkbox-group-item{margin-right:0;margin-left:8px}.ant-tree-checkbox-group-rtl .ant-tree-checkbox-group-item:last-child{margin-left:0!important}.ant-tree-checkbox-group-rtl .ant-tree-checkbox-group-item+.ant-tree-checkbox-group-item{margin-left:8px}.ant-tree{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";background:#fff;border-radius:4px;transition:background-color .3s}.ant-tree-focused:not(:hover):not(.ant-tree-active-focused){background:#e6f7ff}.ant-tree-list-holder-inner{align-items:flex-start}.ant-tree.ant-tree-block-node .ant-tree-list-holder-inner{align-items:stretch}.ant-tree.ant-tree-block-node .ant-tree-list-holder-inner .ant-tree-node-content-wrapper{flex:auto}.ant-tree.ant-tree-block-node .ant-tree-list-holder-inner .ant-tree-treenode.dragging{position:relative}.ant-tree.ant-tree-block-node .ant-tree-list-holder-inner .ant-tree-treenode.dragging:after{position:absolute;inset:0 0 4px;border:1px solid #1890ff;opacity:0;animation:ant-tree-node-fx-do-not-use .3s;animation-play-state:running;animation-fill-mode:forwards;content:"";pointer-events:none}.ant-tree .ant-tree-treenode{display:flex;align-items:flex-start;padding:0 0 4px;outline:none}.ant-tree .ant-tree-treenode-disabled .ant-tree-node-content-wrapper{color:#00000040;cursor:not-allowed}.ant-tree .ant-tree-treenode-disabled .ant-tree-node-content-wrapper:hover{background:transparent}.ant-tree .ant-tree-treenode-active .ant-tree-node-content-wrapper{background:#f5f5f5}.ant-tree .ant-tree-treenode:not(.ant-tree .ant-tree-treenode-disabled).filter-node .ant-tree-title{color:inherit;font-weight:500}.ant-tree-indent{align-self:stretch;white-space:nowrap;-webkit-user-select:none;user-select:none}.ant-tree-indent-unit{display:inline-block;width:24px}.ant-tree-draggable-icon{width:24px;line-height:24px;text-align:center;opacity:.2;transition:opacity .3s}.ant-tree-treenode:hover .ant-tree-draggable-icon{opacity:.45}.ant-tree-switcher{position:relative;flex:none;align-self:stretch;width:24px;margin:0;line-height:24px;text-align:center;cursor:pointer;-webkit-user-select:none;user-select:none}.ant-tree-switcher .ant-tree-switcher-icon,.ant-tree-switcher .ant-select-tree-switcher-icon{display:inline-block;font-size:10px;vertical-align:baseline}.ant-tree-switcher .ant-tree-switcher-icon svg,.ant-tree-switcher .ant-select-tree-switcher-icon svg{transition:transform .3s}.ant-tree-switcher-noop{cursor:default}.ant-tree-switcher_close .ant-tree-switcher-icon svg{transform:rotate(-90deg)}.ant-tree-switcher-loading-icon{color:#1890ff}.ant-tree-switcher-leaf-line{position:relative;z-index:1;display:inline-block;width:100%;height:100%}.ant-tree-switcher-leaf-line:before{position:absolute;top:0;right:12px;bottom:-4px;margin-left:-1px;border-right:1px solid #d9d9d9;content:" "}.ant-tree-switcher-leaf-line:after{position:absolute;width:10px;height:14px;border-bottom:1px solid #d9d9d9;content:" "}.ant-tree-checkbox{top:initial;margin:4px 8px 0 0}.ant-tree .ant-tree-node-content-wrapper{position:relative;z-index:auto;min-height:24px;margin:0;padding:0 4px;color:inherit;line-height:24px;background:transparent;border-radius:4px;cursor:pointer;transition:all .3s,border 0s,line-height 0s,box-shadow 0s}.ant-tree .ant-tree-node-content-wrapper:hover{background-color:#f5f5f5}.ant-tree .ant-tree-node-content-wrapper.ant-tree-node-selected{background-color:#bae7ff}.ant-tree .ant-tree-node-content-wrapper .ant-tree-iconEle{display:inline-block;width:24px;height:24px;line-height:24px;text-align:center;vertical-align:top}.ant-tree .ant-tree-node-content-wrapper .ant-tree-iconEle:empty{display:none}.ant-tree-unselectable .ant-tree-node-content-wrapper:hover{background-color:transparent}.ant-tree-node-content-wrapper{line-height:24px;-webkit-user-select:none;user-select:none}.ant-tree-node-content-wrapper .ant-tree-drop-indicator{position:absolute;z-index:1;height:2px;background-color:#1890ff;border-radius:1px;pointer-events:none}.ant-tree-node-content-wrapper .ant-tree-drop-indicator:after{position:absolute;top:-3px;left:-6px;width:8px;height:8px;background-color:transparent;border:2px solid #1890ff;border-radius:50%;content:""}.ant-tree .ant-tree-treenode.drop-container>[draggable]{box-shadow:0 0 0 2px #1890ff}.ant-tree-show-line .ant-tree-indent-unit{position:relative;height:100%}.ant-tree-show-line .ant-tree-indent-unit:before{position:absolute;top:0;right:12px;bottom:-4px;border-right:1px solid #d9d9d9;content:""}.ant-tree-show-line .ant-tree-indent-unit-end:before{display:none}.ant-tree-show-line .ant-tree-switcher{background:#fff}.ant-tree-show-line .ant-tree-switcher-line-icon{vertical-align:-.15em}.ant-tree .ant-tree-treenode-leaf-last .ant-tree-switcher-leaf-line:before{top:auto!important;bottom:auto!important;height:14px!important}.ant-tree-rtl{direction:rtl}.ant-tree-rtl .ant-tree-node-content-wrapper[draggable=true] .ant-tree-drop-indicator:after{right:-6px;left:unset}.ant-tree .ant-tree-treenode-rtl{direction:rtl}.ant-tree-rtl .ant-tree-switcher_close .ant-tree-switcher-icon svg{transform:rotate(90deg)}.ant-tree-rtl.ant-tree-show-line .ant-tree-indent-unit:before{right:auto;left:-13px;border-right:none;border-left:1px solid #d9d9d9}.ant-tree-rtl .ant-tree-checkbox,.ant-tree-select-dropdown-rtl .ant-select-tree-checkbox{margin:4px 0 0 8px}.font-highlight{color:#ff4d4f}.ant-tree-child-tree{overflow:hidden}nz-tree{display:block}.ant-select-tree-checkbox{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;top:.2em;line-height:1;white-space:nowrap;outline:none;cursor:pointer}.ant-select-tree-checkbox-wrapper:hover .ant-select-tree-checkbox-inner,.ant-select-tree-checkbox:hover .ant-select-tree-checkbox-inner,.ant-select-tree-checkbox-input:focus+.ant-select-tree-checkbox-inner{border-color:#1890ff}.ant-select-tree-checkbox-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #1890ff;border-radius:4px;visibility:hidden;animation:antCheckboxEffect .36s ease-in-out;animation-fill-mode:backwards;content:""}.ant-select-tree-checkbox:hover:after,.ant-select-tree-checkbox-wrapper:hover .ant-select-tree-checkbox:after{visibility:visible}.ant-select-tree-checkbox-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;direction:ltr;background-color:#fff;border:1px solid #d9d9d9;border-radius:4px;border-collapse:separate;transition:all .3s}.ant-select-tree-checkbox-inner:after{position:absolute;top:50%;left:21.5%;display:table;width:5.71428571px;height:9.14285714px;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(0) translate(-50%,-50%);opacity:0;transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;content:" "}.ant-select-tree-checkbox-input{position:absolute;inset:0;z-index:1;width:100%;height:100%;cursor:pointer;opacity:0}.ant-select-tree-checkbox-checked .ant-select-tree-checkbox-inner:after{position:absolute;display:table;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(1) translate(-50%,-50%);opacity:1;transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;content:" "}.ant-select-tree-checkbox-checked .ant-select-tree-checkbox-inner{background-color:#1890ff;border-color:#1890ff}.ant-select-tree-checkbox-disabled{cursor:not-allowed}.ant-select-tree-checkbox-disabled.ant-select-tree-checkbox-checked .ant-select-tree-checkbox-inner:after{border-color:#00000040;animation-name:none}.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-input{cursor:not-allowed;pointer-events:none}.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-inner{background-color:#f5f5f5;border-color:#d9d9d9!important}.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-inner:after{border-color:#f5f5f5;border-collapse:separate;animation-name:none}.ant-select-tree-checkbox-disabled+span{color:#00000040;cursor:not-allowed}.ant-select-tree-checkbox-disabled:hover:after,.ant-select-tree-checkbox-wrapper:hover .ant-select-tree-checkbox-disabled:after{visibility:hidden}.ant-select-tree-checkbox-wrapper{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-flex;align-items:baseline;line-height:unset;cursor:pointer}.ant-select-tree-checkbox-wrapper:after{display:inline-block;width:0;overflow:hidden;content:"\a0"}.ant-select-tree-checkbox-wrapper.ant-select-tree-checkbox-wrapper-disabled{cursor:not-allowed}.ant-select-tree-checkbox-wrapper+.ant-select-tree-checkbox-wrapper{margin-left:8px}.ant-select-tree-checkbox-wrapper.ant-select-tree-checkbox-wrapper-in-form-item input[type=checkbox]{width:14px;height:14px}.ant-select-tree-checkbox+span{padding-right:8px;padding-left:8px}.ant-select-tree-checkbox-group{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-block}.ant-select-tree-checkbox-group-item{margin-right:8px}.ant-select-tree-checkbox-group-item:last-child{margin-right:0}.ant-select-tree-checkbox-group-item+.ant-select-tree-checkbox-group-item{margin-left:0}.ant-select-tree-checkbox-indeterminate .ant-select-tree-checkbox-inner{background-color:#fff;border-color:#d9d9d9}.ant-select-tree-checkbox-indeterminate .ant-select-tree-checkbox-inner:after{top:50%;left:50%;width:8px;height:8px;background-color:#1890ff;border:0;transform:translate(-50%,-50%) scale(1);opacity:1;content:" "}.ant-select-tree-checkbox-indeterminate.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-inner:after{background-color:#00000040;border-color:#00000040}.ant-select-tree-checkbox-rtl{direction:rtl}.ant-select-tree-checkbox-group-rtl .ant-select-tree-checkbox-group-item{margin-right:0;margin-left:8px}.ant-select-tree-checkbox-group-rtl .ant-select-tree-checkbox-group-item:last-child{margin-left:0!important}.ant-select-tree-checkbox-group-rtl .ant-select-tree-checkbox-group-item+.ant-select-tree-checkbox-group-item{margin-left:8px}.ant-tree-select-dropdown{padding:8px 4px}.ant-tree-select-dropdown-rtl{direction:rtl}.ant-tree-select-dropdown .ant-select-tree{border-radius:0}.ant-tree-select-dropdown .ant-select-tree-list-holder-inner{align-items:stretch}.ant-tree-select-dropdown .ant-select-tree-list-holder-inner .ant-select-tree-treenode .ant-select-tree-node-content-wrapper{flex:auto}.ant-select-tree{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";background:#fff;border-radius:4px;transition:background-color .3s}.ant-select-tree-focused:not(:hover):not(.ant-select-tree-active-focused){background:#e6f7ff}.ant-select-tree-list-holder-inner{align-items:flex-start}.ant-select-tree.ant-select-tree-block-node .ant-select-tree-list-holder-inner{align-items:stretch}.ant-select-tree.ant-select-tree-block-node .ant-select-tree-list-holder-inner .ant-select-tree-node-content-wrapper{flex:auto}.ant-select-tree.ant-select-tree-block-node .ant-select-tree-list-holder-inner .ant-select-tree-treenode.dragging{position:relative}.ant-select-tree.ant-select-tree-block-node .ant-select-tree-list-holder-inner .ant-select-tree-treenode.dragging:after{position:absolute;inset:0 0 4px;border:1px solid #1890ff;opacity:0;animation:ant-tree-node-fx-do-not-use .3s;animation-play-state:running;animation-fill-mode:forwards;content:"";pointer-events:none}.ant-select-tree .ant-select-tree-treenode{display:flex;align-items:flex-start;padding:0 0 4px;outline:none}.ant-select-tree .ant-select-tree-treenode-disabled .ant-select-tree-node-content-wrapper{color:#00000040;cursor:not-allowed}.ant-select-tree .ant-select-tree-treenode-disabled .ant-select-tree-node-content-wrapper:hover{background:transparent}.ant-select-tree .ant-select-tree-treenode-active .ant-select-tree-node-content-wrapper{background:#f5f5f5}.ant-select-tree .ant-select-tree-treenode:not(.ant-select-tree .ant-select-tree-treenode-disabled).filter-node .ant-select-tree-title{color:inherit;font-weight:500}.ant-select-tree-indent{align-self:stretch;white-space:nowrap;-webkit-user-select:none;user-select:none}.ant-select-tree-indent-unit{display:inline-block;width:24px}.ant-select-tree-draggable-icon{width:24px;line-height:24px;text-align:center;opacity:.2;transition:opacity .3s}.ant-select-tree-treenode:hover .ant-select-tree-draggable-icon{opacity:.45}.ant-select-tree-switcher{position:relative;flex:none;align-self:stretch;width:24px;margin:0;line-height:24px;text-align:center;cursor:pointer;-webkit-user-select:none;user-select:none}.ant-select-tree-switcher .ant-tree-switcher-icon,.ant-select-tree-switcher .ant-select-tree-switcher-icon{display:inline-block;font-size:10px;vertical-align:baseline}.ant-select-tree-switcher .ant-tree-switcher-icon svg,.ant-select-tree-switcher .ant-select-tree-switcher-icon svg{transition:transform .3s}.ant-select-tree-switcher-noop{cursor:default}.ant-select-tree-switcher_close .ant-select-tree-switcher-icon svg{transform:rotate(-90deg)}.ant-select-tree-switcher-loading-icon{color:#1890ff}.ant-select-tree-switcher-leaf-line{position:relative;z-index:1;display:inline-block;width:100%;height:100%}.ant-select-tree-switcher-leaf-line:before{position:absolute;top:0;right:12px;bottom:-4px;margin-left:-1px;border-right:1px solid #d9d9d9;content:" "}.ant-select-tree-switcher-leaf-line:after{position:absolute;width:10px;height:14px;border-bottom:1px solid #d9d9d9;content:" "}.ant-select-tree-checkbox{top:initial;margin:4px 8px 0 0}.ant-select-tree .ant-select-tree-node-content-wrapper{position:relative;z-index:auto;min-height:24px;margin:0;padding:0 4px;color:inherit;line-height:24px;background:transparent;border-radius:4px;cursor:pointer;transition:all .3s,border 0s,line-height 0s,box-shadow 0s}.ant-select-tree .ant-select-tree-node-content-wrapper:hover{background-color:#f5f5f5}.ant-select-tree .ant-select-tree-node-content-wrapper.ant-select-tree-node-selected{background-color:#bae7ff}.ant-select-tree .ant-select-tree-node-content-wrapper .ant-select-tree-iconEle{display:inline-block;width:24px;height:24px;line-height:24px;text-align:center;vertical-align:top}.ant-select-tree .ant-select-tree-node-content-wrapper .ant-select-tree-iconEle:empty{display:none}.ant-select-tree-unselectable .ant-select-tree-node-content-wrapper:hover{background-color:transparent}.ant-select-tree-node-content-wrapper{line-height:24px;-webkit-user-select:none;user-select:none}.ant-select-tree-node-content-wrapper .ant-tree-drop-indicator{position:absolute;z-index:1;height:2px;background-color:#1890ff;border-radius:1px;pointer-events:none}.ant-select-tree-node-content-wrapper .ant-tree-drop-indicator:after{position:absolute;top:-3px;left:-6px;width:8px;height:8px;background-color:transparent;border:2px solid #1890ff;border-radius:50%;content:""}.ant-select-tree .ant-select-tree-treenode.drop-container>[draggable]{box-shadow:0 0 0 2px #1890ff}.ant-select-tree-show-line .ant-select-tree-indent-unit{position:relative;height:100%}.ant-select-tree-show-line .ant-select-tree-indent-unit:before{position:absolute;top:0;right:12px;bottom:-4px;border-right:1px solid #d9d9d9;content:""}.ant-select-tree-show-line .ant-select-tree-indent-unit-end:before{display:none}.ant-select-tree-show-line .ant-select-tree-switcher{background:#fff}.ant-select-tree-show-line .ant-select-tree-switcher-line-icon{vertical-align:-.15em}.ant-select-tree .ant-select-tree-treenode-leaf-last .ant-select-tree-switcher-leaf-line:before{top:auto!important;bottom:auto!important;height:14px!important}.ant-tree-select-dropdown-rtl .ant-select-tree .ant-select-tree-switcher_close .ant-select-tree-switcher-icon svg{transform:rotate(90deg)}.ant-tree-select-dropdown-rtl .ant-select-tree .ant-select-tree-switcher-loading-icon{transform:scaleY(-1)}.ant-tree.ant-select-tree.ant-tree-show-line nz-tree-node[builtin]:not(:last-child)>li:before{content:" ";width:1px;border-left:1px solid #d9d9d9;height:calc(100% - 16px);position:absolute;left:12px;margin:26px 0}.ant-select-dropdown.ant-select-tree-dropdown{top:100%;left:0;position:relative;width:100%;margin-top:4px;margin-bottom:4px;overflow:auto}.ant-picker-calendar{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";background:#fff}.ant-picker-calendar-header{display:flex;justify-content:flex-end;padding:12px 0}.ant-picker-calendar-header .ant-picker-calendar-year-select{min-width:80px}.ant-picker-calendar-header .ant-picker-calendar-month-select{min-width:70px;margin-left:8px}.ant-picker-calendar-header .ant-picker-calendar-mode-switch{margin-left:8px}.ant-picker-calendar .ant-picker-panel{background:#fff;border:0;border-top:1px solid #f0f0f0;border-radius:0}.ant-picker-calendar .ant-picker-panel .ant-picker-month-panel,.ant-picker-calendar .ant-picker-panel .ant-picker-date-panel{width:auto}.ant-picker-calendar .ant-picker-panel .ant-picker-body{padding:8px 0}.ant-picker-calendar .ant-picker-panel .ant-picker-content{width:100%}.ant-picker-calendar-mini{border-radius:4px}.ant-picker-calendar-mini .ant-picker-calendar-header{padding-right:8px;padding-left:8px}.ant-picker-calendar-mini .ant-picker-panel{border-radius:0 0 4px 4px}.ant-picker-calendar-mini .ant-picker-content{height:256px}.ant-picker-calendar-mini .ant-picker-content th{height:auto;padding:0;line-height:18px}.ant-picker-calendar-mini .ant-picker-cell:before{pointer-events:none}.ant-picker-calendar-full .ant-picker-panel{display:block;width:100%;text-align:right;background:#fff;border:0}.ant-picker-calendar-full .ant-picker-panel .ant-picker-body th,.ant-picker-calendar-full .ant-picker-panel .ant-picker-body td{padding:0}.ant-picker-calendar-full .ant-picker-panel .ant-picker-body th{height:auto;padding:0 12px 5px 0;line-height:18px}.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell:before{display:none}.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell:hover .ant-picker-calendar-date{background:#f5f5f5}.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell .ant-picker-calendar-date-today:before{display:none}.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected .ant-picker-calendar-date,.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected:hover .ant-picker-calendar-date,.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected .ant-picker-calendar-date-today,.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected:hover .ant-picker-calendar-date-today{background:#e6f7ff}.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected .ant-picker-calendar-date .ant-picker-calendar-date-value,.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected:hover .ant-picker-calendar-date .ant-picker-calendar-date-value,.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected .ant-picker-calendar-date-today .ant-picker-calendar-date-value,.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected:hover .ant-picker-calendar-date-today .ant-picker-calendar-date-value{color:#1890ff}.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date{display:block;width:auto;height:auto;margin:0 4px;padding:4px 8px 0;border:0;border-top:2px solid #f0f0f0;border-radius:0;transition:background .3s}.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-value{line-height:24px;transition:color .3s}.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-content{position:static;width:auto;height:86px;overflow-y:auto;color:#000000d9;line-height:1.5715;text-align:left}.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-today{border-color:#1890ff}.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-today .ant-picker-calendar-date-value{color:#000000d9}@media only screen and (max-width: 480px){.ant-picker-calendar-header{display:block}.ant-picker-calendar-header .ant-picker-calendar-year-select{width:50%}.ant-picker-calendar-header .ant-picker-calendar-month-select{width:calc(50% - 8px)}.ant-picker-calendar-header .ant-picker-calendar-mode-switch{width:100%;margin-top:8px;margin-left:0}.ant-picker-calendar-header .ant-picker-calendar-mode-switch>label{width:50%;text-align:center}}.ant-picker-calendar-rtl{direction:rtl}.ant-picker-calendar-rtl .ant-picker-calendar-header .ant-picker-calendar-month-select,.ant-picker-calendar-rtl .ant-picker-calendar-header .ant-picker-calendar-mode-switch{margin-right:8px;margin-left:0}.ant-picker-calendar-rtl.ant-picker-calendar-full .ant-picker-panel{text-align:left}.ant-picker-calendar-rtl.ant-picker-calendar-full .ant-picker-panel .ant-picker-body th{padding:0 0 5px 12px}.ant-picker-calendar-rtl.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-content{text-align:right}.ant-result{padding:48px 32px}.ant-result-success .ant-result-icon>.anticon{color:#52c41a}.ant-result-error .ant-result-icon>.anticon{color:#ff4d4f}.ant-result-info .ant-result-icon>.anticon{color:#1890ff}.ant-result-warning .ant-result-icon>.anticon{color:#faad14}.ant-result-image{width:250px;height:295px;margin:auto}.ant-result-icon{margin-bottom:24px;text-align:center}.ant-result-icon>.anticon{font-size:72px}.ant-result-title{color:#000000d9;font-size:24px;line-height:1.8;text-align:center}.ant-result-subtitle{color:#00000073;font-size:14px;line-height:1.6;text-align:center}.ant-result-extra{margin:24px 0 0;text-align:center}.ant-result-extra>*{margin-right:8px}.ant-result-extra>*:last-child{margin-right:0}.ant-result-content{margin-top:24px;padding:24px 40px;background-color:#fafafa}.ant-result-rtl{direction:rtl}.ant-result-rtl .ant-result-extra>*{margin-right:0;margin-left:8px}.ant-result-rtl .ant-result-extra>*:last-child{margin-left:0}nz-result{display:block}.ant-space{display:inline-flex}.ant-space-vertical{flex-direction:column}.ant-space-align-center{align-items:center}.ant-space-align-start{align-items:flex-start}.ant-space-align-end{align-items:flex-end}.ant-space-align-baseline{align-items:baseline}.ant-space-item:empty{display:none}.ant-space-rtl{direction:rtl}nz-space-item{display:block}.ant-image{position:relative;display:inline-block}.ant-image-img{width:100%;height:auto;vertical-align:middle}.ant-image-img-placeholder{background-color:#f5f5f5;background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTQuNSAyLjVoLTEzQS41LjUgMCAwIDAgMSAzdjEwYS41LjUgMCAwIDAgLjUuNWgxM2EuNS41IDAgMCAwIC41LS41VjNhLjUuNSAwIDAgMC0uNS0uNXpNNS4yODEgNC43NWExIDEgMCAwIDEgMCAyIDEgMSAwIDAgMSAwLTJ6bTguMDMgNi44M2EuMTI3LjEyNyAwIDAgMS0uMDgxLjAzSDIuNzY5YS4xMjUuMTI1IDAgMCAxLS4wOTYtLjIwN2wyLjY2MS0zLjE1NmEuMTI2LjEyNiAwIDAgMSAuMTc3LS4wMTZsLjAxNi4wMTZMNy4wOCAxMC4wOWwyLjQ3LTIuOTNhLjEyNi4xMjYgMCAwIDEgLjE3Ny0uMDE2bC4wMTUuMDE2IDMuNTg4IDQuMjQ0YS4xMjcuMTI3IDAgMCAxLS4wMi4xNzV6IiBmaWxsPSIjOEM4QzhDIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz48L3N2Zz4=);background-repeat:no-repeat;background-position:center center;background-size:30%}.ant-image-mask{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#fff;background:rgba(0,0,0,.5);cursor:pointer;opacity:0;transition:opacity .3s}.ant-image-mask-info{padding:0 4px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-image-mask-info .anticon{margin-inline-end:4px}.ant-image-mask:hover{opacity:1}.ant-image-placeholder{position:absolute;inset:0}.ant-image-preview{pointer-events:none;height:100%;text-align:center}.ant-image-preview.ant-zoom-enter,.ant-image-preview.ant-zoom-appear{transform:none;opacity:0;animation-duration:.3s;-webkit-user-select:none;user-select:none}.ant-image-preview-mask{position:fixed;inset:0;z-index:1000;height:100%;background-color:#00000073}.ant-image-preview-mask-hidden{display:none}.ant-image-preview-wrap{position:fixed;inset:0;overflow:auto;outline:0;-webkit-overflow-scrolling:touch}.ant-image-preview-body{position:absolute;inset:0;overflow:hidden}.ant-image-preview-img{max-width:100%;max-height:100%;vertical-align:middle;transform:scaleZ(1);cursor:grab;transition:transform .3s cubic-bezier(.215,.61,.355,1) 0s;-webkit-user-select:none;user-select:none;pointer-events:auto}.ant-image-preview-img-wrapper{position:absolute;inset:0;transition:transform .3s cubic-bezier(.215,.61,.355,1) 0s}.ant-image-preview-img-wrapper:before{display:inline-block;width:1px;height:50%;margin-right:-1px;content:""}.ant-image-preview-moving .ant-image-preview-img{cursor:grabbing}.ant-image-preview-moving .ant-image-preview-img-wrapper{transition-duration:0s}.ant-image-preview-wrap{z-index:1080}.ant-image-preview-operations{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;font-feature-settings:"tnum";position:absolute;top:0;right:0;z-index:1;display:flex;flex-direction:row-reverse;align-items:center;width:100%;color:#ffffffd9;list-style:none;background:rgba(0,0,0,.1);pointer-events:auto}.ant-image-preview-operations-operation{margin-left:12px;padding:12px;cursor:pointer}.ant-image-preview-operations-operation-disabled{color:#ffffff40;pointer-events:none}.ant-image-preview-operations-operation:last-of-type{margin-left:0}.ant-image-preview-operations-progress{position:absolute;left:50%;transform:translate(-50%)}.ant-image-preview-operations-icon{font-size:18px}.ant-image-preview-switch-left,.ant-image-preview-switch-right{position:absolute;top:50%;right:10px;z-index:1;display:flex;align-items:center;justify-content:center;width:44px;height:44px;margin-top:-22px;color:#ffffffd9;background:rgba(0,0,0,.1);border-radius:50%;cursor:pointer;pointer-events:auto}.ant-image-preview-switch-left-disabled,.ant-image-preview-switch-right-disabled{color:#ffffff40;cursor:not-allowed}.ant-image-preview-switch-left-disabled>.anticon,.ant-image-preview-switch-right-disabled>.anticon{cursor:not-allowed}.ant-image-preview-switch-left>.anticon,.ant-image-preview-switch-right>.anticon{font-size:18px}.ant-image-preview-switch-left{left:10px}.ant-image-preview-switch-right{right:10px}.cdk-overlay-backdrop.ant-image-preview-mask{opacity:1}.ant-cron-expression{display:flex;flex-wrap:nowrap}.ant-cron-expression-content{width:100%}.ant-cron-expression-content .ant-cron-expression-input-group-error{border-color:#ff4d4f;box-shadow:none}.ant-cron-expression-content .ant-cron-expression-input-group-error-focus{box-shadow:0 0 0 2px #ff4d4f33}.ant-cron-expression nz-cron-expression-input{width:20%}.ant-cron-expression-input-group{display:flex;flex-wrap:nowrap;align-items:center}.ant-cron-expression-input-group input{border:none!important;box-shadow:none!important;width:100%;outline:none;padding:0;border-radius:0}.ant-cron-expression-input-group-focus{border-color:#1890ff;box-shadow:0 0 0 2px #1890ff33;outline:0}.ant-cron-expression nz-cron-expression-label{width:20%}.ant-cron-expression-label-group{display:flex;width:100%;flex-wrap:nowrap;justify-content:space-around;padding-top:0!important;padding-bottom:0!important}.ant-cron-expression-label-group-default{padding:0 12px}.ant-cron-expression-label-foucs{color:#1890ff}.ant-cron-expression-map{margin-left:12px}.ant-cron-expression-preview{display:flex;padding:12px}.ant-cron-expression-preview-dateTime{flex:1 1 auto;display:flex;align-items:center}.ant-cron-expression-preview-dateTime-center{justify-content:center}.ant-cron-expression-preview-content{flex:0 0 220px;display:flex;align-items:center;padding-left:16px}.ant-cron-expression-preview-content-date{flex:1 1 auto}.ant-cron-expression-preview-list,.ant-cron-expression-preview-icon,.ant-cron-expression-preview-list li,.ant-cron-expression-preview-icon li{list-style:none;margin:0;padding:0}.ant-cron-expression-preview-list{overflow-y:scroll;height:132px}.ant-cron-expression-preview-icon{height:100%}.ant-cron-expression-error{color:#ff4d4f}.ant-cron-expression-hint p{display:flex}.ant-cron-expression-hint span{display:inline-block;min-width:40px}.ql-toolbar{border-top-left-radius:4px;border-top-right-radius:4px;border-bottom:none}.ql-toolbar.ql-snow{font-family:inherit!important;border-bottom:none!important}.ql-toolbar,.ql-container,.description-editor{border:1px solid #d9d9d9}.description-editor{border-top:none}.ql-editor.ql-blank:before{color:#bfbfbf!important;font-size:14px}.gantt__row-bars li{border-radius:4px}.custom-table .ant-table-tbody>tr>td{transition:none!important}.custom-table .ant-table-tbody>tr.ant-table-row:hover>td,.custom-table .ant-table-tbody>tr>td.ant-table-cell-row-hover{color:#1890ff}.teams-switch .team-name{background:#e6f7ff;padding:12px 16px;color:#1890ff}.teams-switch .team-name:hover{background:#ccefff}.menu-border-0:after{border:none!important}.menu-hover:hover{background-color:#f0f2f5!important}.ant-table:not(.ant-table-bordered) table td,.ant-table:not(.ant-table-bordered) table th{border:none!important;transition:none!important}.ant-table:not(.ant-table-bordered) table nz-divider{border-color:#b0b0b0!important}.ant-table:not(.ant-table-bordered) table tr:hover td:not(.nz-disable-td){background-color:#edebf0!important}.ant-table:not(.ant-table-bordered) table tr:hover td:not(.nz-disable-td):first-child{border-top-left-radius:4px;border-bottom-left-radius:4px}.ant-table:not(.ant-table-bordered) table tr:hover td:not(.nz-disable-td):last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.ant-table:not(.ant-table-bordered) table thead tr th:first-child{border-top-left-radius:4px;border-bottom-left-radius:4px}.ant-table:not(.ant-table-bordered) table thead tr th:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.ant-table:not(.ant-table-bordered) table tbody tr:nth-of-type(even)>*:not(.nz-disable-td){background:#f8f7f9}.ant-table:not(.ant-table-bordered) table tbody tr:nth-of-type(even)>*:not(.nz-disable-td):first-child{border-top-left-radius:4px;border-bottom-left-radius:4px}.ant-table:not(.ant-table-bordered) table tbody tr:nth-of-type(even)>*:not(.nz-disable-td):last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.ant-tabs-card .ant-tabs-tab{transition:none!important}.ant-tabs-card .ant-tabs-tab:hover{background-color:#edebf0!important}.admin-role{color:#faad14}.owner-role{color:#1890ff}.member-role{color:#00000073}.ant-page-header-back-button{padding:6px 8px;border-radius:4px;border:1px solid transparent;transition:all .2s ease-out}.ant-page-header-back-button:hover{background:#fafafa;border-color:#f0f0f0}:root{--ant-primary-color: #1890ff;--ant-primary-color-hover: #40a9ff;--ant-primary-color-active: #096dd9;--ant-primary-color-outline: rgba(24, 144, 255, .2);--ant-primary-1: #e6f7ff;--ant-primary-2: #bae7ff;--ant-primary-3: #91d5ff;--ant-primary-4: #69c0ff;--ant-primary-5: #40a9ff;--ant-primary-6: #1890ff;--ant-primary-7: #096dd9;--ant-primary-color-deprecated-l-35: #cbe6ff;--ant-primary-color-deprecated-l-20: #7ec1ff;--ant-primary-color-deprecated-t-20: #46a6ff;--ant-primary-color-deprecated-t-50: #8cc8ff;--ant-primary-color-deprecated-f-12: rgba(24, 144, 255, .12);--ant-primary-color-active-deprecated-f-30: rgba(230, 247, 255, .3);--ant-primary-color-active-deprecated-d-02: #dcf4ff;--ant-success-color: #52c41a;--ant-success-color-hover: #73d13d;--ant-success-color-active: #389e0d;--ant-success-color-outline: rgba(82, 196, 26, .2);--ant-success-color-deprecated-bg: #f6ffed;--ant-success-color-deprecated-border: #b7eb8f;--ant-error-color: #ff4d4f;--ant-error-color-hover: #ff7875;--ant-error-color-active: #d9363e;--ant-error-color-outline: rgba(255, 77, 79, .2);--ant-error-color-deprecated-bg: #fff2f0;--ant-error-color-deprecated-border: #ffccc7;--ant-warning-color: #faad14;--ant-warning-color-hover: #ffc53d;--ant-warning-color-active: #d48806;--ant-warning-color-outline: rgba(250, 173, 20, .2);--ant-warning-color-deprecated-bg: #fffbe6;--ant-warning-color-deprecated-border: #ffe58f;--ant-info-color: #1890ff;--ant-info-color-deprecated-bg: #e6f7ff;--ant-info-color-deprecated-border: #91d5ff }/*! - * Bootstrap Grid v5.1.3 (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors - * Copyright 2011-2021 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */:root{--bs-blue: #0d6efd;--bs-indigo: #6610f2;--bs-purple: #6f42c1;--bs-pink: #d63384;--bs-red: #dc3545;--bs-orange: #fd7e14;--bs-yellow: #ffc107;--bs-green: #198754;--bs-teal: #20c997;--bs-cyan: #0dcaf0;--bs-white: #fff;--bs-gray: #6c757d;--bs-gray-dark: #343a40;--bs-gray-100: #f8f9fa;--bs-gray-200: #e9ecef;--bs-gray-300: #dee2e6;--bs-gray-400: #ced4da;--bs-gray-500: #adb5bd;--bs-gray-600: #6c757d;--bs-gray-700: #495057;--bs-gray-800: #343a40;--bs-gray-900: #212529;--bs-primary: #0d6efd;--bs-secondary: #6c757d;--bs-success: #198754;--bs-info: #0dcaf0;--bs-warning: #ffc107;--bs-danger: #dc3545;--bs-light: #f8f9fa;--bs-dark: #212529;--bs-primary-rgb: 13, 110, 253;--bs-secondary-rgb: 108, 117, 125;--bs-success-rgb: 25, 135, 84;--bs-info-rgb: 13, 202, 240;--bs-warning-rgb: 255, 193, 7;--bs-danger-rgb: 220, 53, 69;--bs-light-rgb: 248, 249, 250;--bs-dark-rgb: 33, 37, 41;--bs-white-rgb: 255, 255, 255;--bs-black-rgb: 0, 0, 0;--bs-body-color-rgb: 33, 37, 41;--bs-body-bg-rgb: 255, 255, 255;--bs-font-sans-serif: -apple-system, BlinkMacSystemFont, "Inter", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, .15), rgba(255, 255, 255, 0));--bs-body-font-family: var(--bs-font-sans-serif);--bs-body-font-size: 1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--bs-body-color: #212529;--bs-body-bg: #fff}.container,.container-fluid,.container-xxl,.container-xl,.container-lg,.container-md,.container-sm{width:100%;padding-right:var(--bs-gutter-x, .75rem);padding-left:var(--bs-gutter-x, .75rem);margin-right:auto;margin-left:auto}@media (min-width: 576px){.container-sm,.container{max-width:540px}}@media (min-width: 768px){.container-md,.container-sm,.container{max-width:720px}}@media (min-width: 992px){.container-lg,.container-md,.container-sm,.container{max-width:960px}}@media (min-width: 1200px){.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1140px}}@media (min-width: 1400px){.container-xxl,.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1320px}}.row{--bs-gutter-x: 1.5rem;--bs-gutter-y: 0;display:flex;flex-wrap:wrap;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x))}.row>*{box-sizing:border-box;flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x: 0}.g-0,.gy-0{--bs-gutter-y: 0}.g-1,.gx-1{--bs-gutter-x: .25rem}.g-1,.gy-1{--bs-gutter-y: .25rem}.g-2,.gx-2{--bs-gutter-x: .5rem}.g-2,.gy-2{--bs-gutter-y: .5rem}.g-3,.gx-3{--bs-gutter-x: 1rem}.g-3,.gy-3{--bs-gutter-y: 1rem}.g-4,.gx-4{--bs-gutter-x: 1.5rem}.g-4,.gy-4{--bs-gutter-y: 1.5rem}.g-5,.gx-5{--bs-gutter-x: 3rem}.g-5,.gy-5{--bs-gutter-y: 3rem}@media (min-width: 576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x: 0}.g-sm-0,.gy-sm-0{--bs-gutter-y: 0}.g-sm-1,.gx-sm-1{--bs-gutter-x: .25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y: .25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x: .5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y: .5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x: 1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y: 1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x: 1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y: 1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x: 3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y: 3rem}}@media (min-width: 768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x: 0}.g-md-0,.gy-md-0{--bs-gutter-y: 0}.g-md-1,.gx-md-1{--bs-gutter-x: .25rem}.g-md-1,.gy-md-1{--bs-gutter-y: .25rem}.g-md-2,.gx-md-2{--bs-gutter-x: .5rem}.g-md-2,.gy-md-2{--bs-gutter-y: .5rem}.g-md-3,.gx-md-3{--bs-gutter-x: 1rem}.g-md-3,.gy-md-3{--bs-gutter-y: 1rem}.g-md-4,.gx-md-4{--bs-gutter-x: 1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y: 1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x: 3rem}.g-md-5,.gy-md-5{--bs-gutter-y: 3rem}}@media (min-width: 992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x: 0}.g-lg-0,.gy-lg-0{--bs-gutter-y: 0}.g-lg-1,.gx-lg-1{--bs-gutter-x: .25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y: .25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x: .5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y: .5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x: 1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y: 1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x: 1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y: 1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x: 3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y: 3rem}}@media (min-width: 1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x: 0}.g-xl-0,.gy-xl-0{--bs-gutter-y: 0}.g-xl-1,.gx-xl-1{--bs-gutter-x: .25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y: .25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x: .5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y: .5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x: 1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y: 1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x: 1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y: 1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x: 3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y: 3rem}}@media (min-width: 1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x: 0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y: 0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x: .25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y: .25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x: .5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y: .5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x: 1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y: 1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x: 1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y: 1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x: 3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y: 3rem}}@media (min-width: 576px){.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}}@media (min-width: 768px){.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}}@media (min-width: 992px){.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}}@media (min-width: 1200px){.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}}@media (min-width: 1400px){.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}}/*! - * Bootstrap Utilities v5.1.3 (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors - * Copyright 2011-2021 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */.clearfix:after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:hover,.link-primary:focus{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:hover,.link-secondary:focus{color:#565e64}.link-success{color:#198754}.link-success:hover,.link-success:focus{color:#146c43}.link-info{color:#0dcaf0}.link-info:hover,.link-info:focus{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:hover,.link-warning:focus{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:hover,.link-danger:focus{color:#b02a37}.link-light{color:#f8f9fa}.link-light:hover,.link-light:focus{color:#f9fafb}.link-dark{color:#212529}.link-dark:hover,.link-dark:focus{color:#1a1e21}.ratio{position:relative;width:100%}.ratio:before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio: 100%}.ratio-4x3{--bs-aspect-ratio: 75%}.ratio-16x9{--bs-aspect-ratio: 56.25%}.ratio-21x9{--bs-aspect-ratio: 42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:sticky;top:0;z-index:1020}@media (min-width: 576px){.sticky-sm-top{position:sticky;top:0;z-index:1020}}@media (min-width: 768px){.sticky-md-top{position:sticky;top:0;z-index:1020}}@media (min-width: 992px){.sticky-lg-top{position:sticky;top:0;z-index:1020}}@media (min-width: 1200px){.sticky-xl-top{position:sticky;top:0;z-index:1020}}@media (min-width: 1400px){.sticky-xxl-top{position:sticky;top:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link:after{position:absolute;inset:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem #00000026!important}.shadow-sm{box-shadow:0 .125rem .25rem #00000013!important}.shadow-lg{box-shadow:0 1rem 3rem #0000002d!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translate(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity: 1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity: 1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity: 1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity: 1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity: 1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity: 1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity: 1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity: 1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity: 1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity: 1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity: 1;color:rgba(var(--bs-body-color-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity: 1;color:#6c757d!important}.text-black-50{--bs-text-opacity: 1;color:#00000080!important}.text-white-50{--bs-text-opacity: 1;color:#ffffff80!important}.text-reset{--bs-text-opacity: 1;color:inherit!important}.text-opacity-25{--bs-text-opacity: .25}.text-opacity-50{--bs-text-opacity: .5}.text-opacity-75{--bs-text-opacity: .75}.text-opacity-100{--bs-text-opacity: 1}.bg-primary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity: 1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity: 1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity: 1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity: 1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity: 1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity: 1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity: 1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity: 1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity: 1;background-color:rgba(var(--bs-body-bg-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity: 1;background-color:transparent!important}.bg-opacity-10{--bs-bg-opacity: .1}.bg-opacity-25{--bs-bg-opacity: .25}.bg-opacity-50{--bs-bg-opacity: .5}.bg-opacity-75{--bs-bg-opacity: .75}.bg-opacity-100{--bs-bg-opacity: 1}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width: 576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width: 768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width: 992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width: 1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width: 1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width: 1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}}:root{--column_count: 20;--column_width: 70px}.header{color:#202125;margin-bottom:40px}.header h2{font-weight:600}.header p{font-weight:300}.wrapper{max-width:100vw;min-width:700px;margin:0 auto;padding:0;gap:10px;overflow:auto;max-height:80vh}.gantt{display:grid;border:0;position:relative;box-sizing:border-box;margin-bottom:10px}.gantt__row{display:grid;grid-template-columns:200px 1fr;background-color:#fff}.gantt__row--empty{background-color:#fafafa!important;z-index:1}.gantt__row--empty .gantt__row-first{border-width:1px 1px 0 0;border-left:1px solid #f0f0f0}.gantt__row--lines{position:absolute;height:100%;width:100%;background-color:transparent;grid-template-columns:200px repeat(var(--column_count),var(--column_width))}.gantt__row--lines span{display:block;border-right:1px solid #f0f0f0}.gantt__row--lines span.weekend{background:linear-gradient(-45deg,rgb(230,230,230) 12.5%,transparent 12.5%,transparent 50%,rgb(230,230,230) 50%,rgb(230,230,230) 62.5%,transparent 62.5%,transparent) 0% 0%/5px 5px;z-index:0}.gantt__row--lines:after{grid-row:1;grid-column:0;background-color:#1688b345;z-index:2;height:100%}.gantt__row--months{color:#000;background-color:#fafafa!important;grid-template-columns:200px repeat(var(--column_count),var(--column_width))}.gantt__row--months .gantt__row-first{border-top:0!important;background-color:#fafafa!important}.gantt__row--months span{text-align:center;font-size:13px;align-self:center;font-weight:700;padding:20px 0}.gantt__row-first{background-color:#fff;border:none;padding:15px 10px;font-size:13px;text-align:left}.gantt__row-first-user{border-width:1px 1px 0px 0px}.gantt__row-bars{list-style:none;display:grid;padding:0;margin:0 0 7px;grid-template-columns:repeat(var(--column_count),var(--column_width));grid-gap:0px 0;align-items:center}.gantt__row-bars li{font-weight:500;text-align:left;font-size:13px;min-height:15px;background-color:#55de84;padding:0;margin:7px 1px 0;color:#fff;overflow:hidden;position:relative;cursor:pointer;line-height:21px}.gantt__row-bars li .ant-btn{width:100%;text-align:left;padding:0 15px;color:#fff;border:none;font-weight:400}.gantt__row-bars li .ant-btn :hover{color:#fff}.gantt__row-bars li.stripes{background-image:repeating-linear-gradient(45deg,transparent,transparent 5px,rgba(255,255,255,.1) 5px,rgba(255,255,255,.1) 12px)}.gantt__row-bars li:before,.gantt__row-bars li:after{content:"";height:100%;top:0;z-index:4;position:absolute;background-color:#0000004d}.gantt__row-bars li:before{left:0}.gantt__row-bars li:after{right:0}.gantt__row-month-range{list-style:none;display:grid;padding:12px 0;margin:0;grid-template-columns:repeat(var(--column_count),var(--column_width));grid-gap:8px 0;border-top:1px solid #f0f0f0;align-items:center}.gantt__row-month-range li{font-weight:500;text-align:left;font-size:14px;min-height:15px;background-color:#55de84;padding:0;margin:0 1px;color:#fff;overflow:hidden;position:relative;animation:.5s ease-out 0s 1 scale-up-hor-left}.gantt__row-month-range li .ant-btn{width:100%;text-align:left;padding:0 8px;color:#fff;border:none;font-weight:400}.gantt__row-month-range li .ant-btn :hover{color:#fff}.gantt__row-month-range li:before,.gantt__row-month-range li:after{content:"";height:100%;top:0;z-index:4;position:absolute;background-color:#0000004d}.gantt__row-month-range li:before{left:0}.gantt__row-month-range li:after{right:0}.project-header{background-color:#f0f2f5!important;font-size:14px;border-right:1px solid #f0f0f0;border-top:1px solid #bdbdbd;margin-top:1px}.col{padding:16px 0;text-align:center;border-radius:0;min-height:30px;margin-top:8px;margin-bottom:8px;background:rgba(0,160,233,.7);color:#fff}.col.right{background:#00a0e9}.sticky-left{position:sticky;left:0;z-index:1}.grid-header{position:sticky;top:0;z-index:1}.grid-header.sticky-left{z-index:2}.grid-column{height:100%}.gantt__row a{color:#000000d9;font-weight:600}.gantt__row--months span{font-weight:500;padding:12px 10px;color:#000000d9;text-align:left}.gantt-user{border-right:1px solid #f0f0f0;border-left:1px solid #f0f0f0;padding-left:20px}.dotted-border{border-bottom:1px dashed #707070}.dotted-border :last-of-type{border-bottom:none!important}.p-default{padding:24px!important}.border-l-b{border-left:1px solid #f0f0f0;border-bottom:1px solid #f0f0f0;border-right:1px solid #f0f0f0}.project-name{font-size:14px;font-weight:500;color:#292929}.project-name:hover{color:#40a9ff}.ant-popover-inner-content{padding:12px 24px}@keyframes scale-up-hor-left{0%{transform:scaleX(.4);transform-origin:0% 0%}to{transform:scaleX(1);transform-origin:0% 0%}}@keyframes slideDown{0%{transform:translateY(-50%);opacity:0}to{transform:translateY(0);opacity:1}}.weekend{background:linear-gradient(-45deg,rgb(230,230,230) 12.5%,transparent 12.5%,transparent 50%,rgb(230,230,230) 50%,rgb(230,230,230) 62.5%,transparent 62.5%,transparent) 0% 0%/5px 5px}.sunday{border-right:solid 1px silver!important}.member{font-size:13px;font-weight:400}.header-bg{background-color:#fafafa!important}.month{font-weight:500}*::-webkit-scrollbar{background-color:#fff;width:16px}*::-webkit-scrollbar-track{background-color:#fff}*::-webkit-scrollbar-thumb{background-color:#ccc;border-radius:16px;border:4px solid #fff}*::-webkit-scrollbar-thumb:hover{background-color:#ababab}*::-webkit-scrollbar-button{display:none}html,body{background:#ffffff;min-height:100vh}.list-unstyled{padding-left:0;list-style:none}svg.icon-md{width:16px;height:16px}.ant-form-item-label>label.ant-form-item-required:not(.ant-form-item-required-mark-optional):before{display:inline-block;margin-right:4px;color:#ff4d4f;font-size:14px;font-family:SimSun,sans-serif;line-height:1;content:"*"}.undecorated-input{border:none;outline:none;background:transparent}.undecorated-input:focus{outline:none}.breadcrumb-container{padding-top:0!important;padding-bottom:0;margin-bottom:0}.gantt-overflow::-webkit-scrollbar-track,.sidemenu-list::-webkit-scrollbar-track,.scrollable-list::-webkit-scrollbar-track,.invite-box::-webkit-scrollbar-track,.board-column::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px #eceded;border-radius:10px;background-color:#eceded}.gantt-overflow::-webkit-scrollbar,.sidemenu-list::-webkit-scrollbar,.scrollable-list::-webkit-scrollbar,.invite-box::-webkit-scrollbar,.board-column::-webkit-scrollbar{width:6px;height:6px;background-color:#f5f5f5}.gantt-overflow::-webkit-scrollbar-thumb,.sidemenu-list::-webkit-scrollbar-thumb,.scrollable-list::-webkit-scrollbar-thumb,.invite-box::-webkit-scrollbar-thumb,.board-column::-webkit-scrollbar-thumb{border-radius:10px;-webkit-box-shadow:inset 0 0 6px #cccdcd;background-color:#cccdcd}.sider{position:fixed;left:0;height:100vh;bottom:0;top:48px}@media (max-width: 1200px){.table-xl-scroll table{width:87vw}.table-xl-scroll .ant-table-container{overflow:auto}}@media (max-width: 991px){.table-xl-scroll table{width:max-content}.page-layout-ml-auto{margin-left:0}.ant-layout-sider-zero-width-trigger{top:95px}.sider{z-index:999}.ant-table-content{overflow:auto}.mt-0{margin-top:0!important}}@media (max-width: 767px){.pd-mobile-ps-0{padding-right:0!important;padding-left:0!important}.pd-mobile-pe-0{padding-right:0!important;padding-left:0!important;margin-top:24px!important}}.page-layout-pt-auto{padding-top:48px}.ant-table-thead>tr>th,.ant-table-tbody>tr>td,.ant-table tfoot>tr>th,.ant-table tfoot>tr>td{padding:12px 10px}.ant-list-bordered .ant-list-item.default-list-item-p-11{padding:11px}.acc-settings-dropdown-item{height:2.5rem}.profile-name-default{height:30px;margin-top:-15px}.profile-email-default{height:40px}.cdk-overlay-container{z-index:1100}.default-nz-space{gap:16px}.p-default .ant-table-container,.overview-table .ant-table-container{padding:24px}.p-default .ant-table-content,.overview-table .ant-table-content{overflow:auto}.ant-progress-bg{height:6px!important}a:hover{transition:.2s all}a:hover .ant-typography{color:#40a9ff;transition:.2s all}.b-none:before{display:none}.w-0{width:0px}a .ant-badge-status-text{margin-left:5px}a .ant-badge-status-dot{width:8px;height:8px;vertical-align:baseline;top:0}.mt-deafult{margin-top:24px}.gap-default{gap:6px}.mt-def{margin-top:24px}.ant-collapse>.ant-collapse-item>.ant-collapse-header{padding:16px;font-size:15px}.custom-tabs .ant-tabs-nav{background:white!important;padding:0 24px;margin-bottom:0}.custom-tabs .ant-tabs-content-holder{margin:24px}.bg-and-grey{background-color:#f0f2f5}.project-single .single-d-none{display:none!important}.project-single .page-data{margin:0}.project-single .page-data .mt-3{margin-top:0!important}.project-single .custom-avatar .ant-avatar-string{margin-left:auto;margin-right:auto;left:0;right:0}.ant-table{overflow-y:auto}.grid-entry .project-name{color:#262626}.editable-cell{position:relative;padding:5px 12px;cursor:pointer}.editable-row{display:flex;justify-content:space-between}.editable-row .plus-icon{display:none}.editable-row:hover .plus-icon{display:block}.editable-row textarea{width:100%}.editable-row:hover .editable-cell{border:1px solid #d9d9d9;border-radius:4px;padding:4px 11px;cursor:text;width:100%}.editable{cursor:pointer}.editable .plus-icon{display:none;position:absolute;right:10px;top:0;margin:auto;bottom:0;align-items:center}.editable:hover{background:#f0f2f5!important}.editable:hover .plus-icon{display:flex}.editable nz-select nz-select-top-control{display:flex!important;align-items:center!important;height:100%!important;border:0!important;background-color:transparent!important}.img-fluid{max-width:100%;height:auto}nz-tabset{-webkit-user-select:none;user-select:none}.ant-layout-sider{background:#FFFFFF}.rounded-4{border-radius:.5rem!important}.actions-row.compact td{padding-top:0!important;padding-bottom:0!important}.actions-row .actions-col{width:70px}.actions-row [nztype=delete]{opacity:.7}.actions-row:hover .actions{opacity:1}.actions-row .actions{opacity:0}.actions-row.removing{animation:row-removing .2s ease-out}@keyframes row-removing{0%{transform:rotateX(0)}to{transform:rotateX(90deg)}}.cursor-pointer{cursor:pointer}.text-left{text-align:left!important}.user-select-none{pointer-events:none;-webkit-user-select:none;user-select:none}nz-collapse-panel .ant-collapse-header{font-weight:600}.thead-height-0 thead{height:0;display:block}.my-dashboard nz-table-inner-scroll .ant-table-body{min-height:300px}.p-default-gaant{padding:12px!important}.btn-primary-custom{border-color:#40a9ff;color:#40a9ff}.pin-button{font-size:18px}nz-page-header{background-color:transparent!important}.task-due-label .ant-progress-text{font-weight:700;font-size:.8em}.teams-dropdown-avatar .ant-avatar-string{display:contents;left:0!important;right:0!important}.ant-upload-list{overflow:hidden}.body-padding-0 .ant-card-body{padding:0}nz-segmented{font-weight:500}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{width:170px}.active-half.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{background:linear-gradient(90deg,#1890ff 50%,rgba(0,0,0,.0588235294) 50%);background-color:transparent;transition:all 3s}nz-message-container nz-message .ant-message-notice-content{border-radius:21px;font-weight:500;border:none}.custom-select{font-size:13px}.custom-select .anticon-down{color:#000000d9}.custom-select nz-select-top-control{display:flex!important;align-items:center!important;height:100%!important;border:0!important;background-color:transparent!important}.sub-tasks-skeleton ul,.sub-tasks-skeleton li{margin:0!important}.sub-tasks-skeleton li{width:100%!important;height:28px!important;border-radius:0!important;border:1px solid #ececec}.sub-task-actions .plus-icon{display:none;position:absolute;right:10px;top:0;margin:auto;bottom:0;align-items:center}nz-list-item-meta-title h4{margin-bottom:0!important}.task-list-label{font-size:11px;font-weight:500}nz-avatar span{inset:0!important;transform:none!important}.ant-table-column-sorter,.ant-table-filter-trigger{color:#8b8b8b}.members-dropdown{max-height:255px;overflow:hidden;overflow-y:auto}.task-list-row .custom-select nz-select-item{font-weight:500}.avatar-dashed{color:#000000d9;background:#fff;border:1px dashed #c4c4c4;opacity:.8}.avatar-dashed:hover{cursor:pointer;border:1px dashed #adadad}.empty-label{padding:0 24px;line-height:19px}.task-view nz-form-label{width:130px;max-width:130px}.task-view .control-hover{cursor:pointer}.task-view .description-hover{cursor:text;-webkit-user-select:text;user-select:text}.task-view .description-hover,.task-view .control-hover{border:1px solid transparent;border-radius:4px}.task-view .description-hover:hover,.task-view .control-hover:hover{border-color:#d9d9d9}.task-form-drawer-opened .ant-collapse-header{padding:0!important}.task-form-drawer-opened .ant-drawer-body{padding-top:0!important}.task-form-drawer-opened .ant-drawer-header-title{margin-bottom:16px}.task-form-drawer-opened .ant-drawer-header{border-bottom:1px solid whitesmoke;padding-bottom:0}.task-form-drawer-opened .task-drawer-tabset .ant-tabs-nav-wrap{margin-left:27px;padding-top:8px}.task-description-editor .description-editor{display:none}.task-description-editor .description-editor>div{outline:none}.task-description-editor .description-editor.editing{display:block}.task-description-editor p{line-height:inherit;margin:0;height:auto}.task-description-editor .ql-toolbar{display:none!important}.task-description-editor.editing .ql-toolbar{display:block!important}.task-view-comments .ant-comment-inner{padding-bottom:0}.task-view-comments .ant-comment-actions{margin-top:0;margin-bottom:0}/*! - * Quill Editor v1.3.7 - * https://quilljs.com/ - * Copyright (c) 2014, Jason Chen - * Copyright (c) 2013, salesforce.com - */.ql-container{box-sizing:border-box;font-family:Helvetica,Arial,sans-serif;font-size:13px;height:100%;margin:0;position:relative}.ql-container.ql-disabled .ql-tooltip{visibility:hidden}.ql-container.ql-disabled .ql-editor ul[data-checked]>li:before{pointer-events:none}.ql-clipboard{left:-100000px;height:1px;overflow-y:hidden;position:absolute;top:50%}.ql-clipboard p{margin:0;padding:0}.ql-editor{box-sizing:border-box;line-height:1.42;height:100%;outline:none;overflow-y:auto;padding:12px 15px;tab-size:4;-moz-tab-size:4;text-align:left;white-space:pre-wrap;word-wrap:break-word}.ql-editor>*{cursor:text}.ql-editor p,.ql-editor ol,.ql-editor ul,.ql-editor pre,.ql-editor blockquote,.ql-editor h1,.ql-editor h2,.ql-editor h3,.ql-editor h4,.ql-editor h5,.ql-editor h6{margin:0;padding:0;counter-reset:list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol,.ql-editor ul{padding-left:1.5em}.ql-editor ol>li,.ql-editor ul>li{list-style-type:none}.ql-editor ul>li:before{content:"\2022"}.ql-editor ul[data-checked=true],.ql-editor ul[data-checked=false]{pointer-events:none}.ql-editor ul[data-checked=true]>li *,.ql-editor ul[data-checked=false]>li *{pointer-events:all}.ql-editor ul[data-checked=true]>li:before,.ql-editor ul[data-checked=false]>li:before{color:#777;cursor:pointer;pointer-events:all}.ql-editor ul[data-checked=true]>li:before{content:"\2611"}.ql-editor ul[data-checked=false]>li:before{content:"\2610"}.ql-editor li:before{display:inline-block;white-space:nowrap;width:1.2em}.ql-editor li:not(.ql-direction-rtl):before{margin-left:-1.5em;margin-right:.3em;text-align:right}.ql-editor li.ql-direction-rtl:before{margin-left:.3em;margin-right:-1.5em}.ql-editor ol li:not(.ql-direction-rtl),.ql-editor ul li:not(.ql-direction-rtl){padding-left:1.5em}.ql-editor ol li.ql-direction-rtl,.ql-editor ul li.ql-direction-rtl{padding-right:1.5em}.ql-editor ol li{counter-reset:list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;counter-increment:list-0}.ql-editor ol li:before{content:counter(list-0,decimal) ". "}.ql-editor ol li.ql-indent-1{counter-increment:list-1}.ql-editor ol li.ql-indent-1:before{content:counter(list-1,lower-alpha) ". "}.ql-editor ol li.ql-indent-1{counter-reset:list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-2{counter-increment:list-2}.ql-editor ol li.ql-indent-2:before{content:counter(list-2,lower-roman) ". "}.ql-editor ol li.ql-indent-2{counter-reset:list-3 list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-3{counter-increment:list-3}.ql-editor ol li.ql-indent-3:before{content:counter(list-3,decimal) ". "}.ql-editor ol li.ql-indent-3{counter-reset:list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-4{counter-increment:list-4}.ql-editor ol li.ql-indent-4:before{content:counter(list-4,lower-alpha) ". "}.ql-editor ol li.ql-indent-4{counter-reset:list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-5{counter-increment:list-5}.ql-editor ol li.ql-indent-5:before{content:counter(list-5,lower-roman) ". "}.ql-editor ol li.ql-indent-5{counter-reset:list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-6{counter-increment:list-6}.ql-editor ol li.ql-indent-6:before{content:counter(list-6,decimal) ". "}.ql-editor ol li.ql-indent-6{counter-reset:list-7 list-8 list-9}.ql-editor ol li.ql-indent-7{counter-increment:list-7}.ql-editor ol li.ql-indent-7:before{content:counter(list-7,lower-alpha) ". "}.ql-editor ol li.ql-indent-7{counter-reset:list-8 list-9}.ql-editor ol li.ql-indent-8{counter-increment:list-8}.ql-editor ol li.ql-indent-8:before{content:counter(list-8,lower-roman) ". "}.ql-editor ol li.ql-indent-8{counter-reset:list-9}.ql-editor ol li.ql-indent-9{counter-increment:list-9}.ql-editor ol li.ql-indent-9:before{content:counter(list-9,decimal) ". "}.ql-editor .ql-indent-1:not(.ql-direction-rtl){padding-left:3em}.ql-editor li.ql-indent-1:not(.ql-direction-rtl){padding-left:4.5em}.ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right{padding-right:3em}.ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right{padding-right:4.5em}.ql-editor .ql-indent-2:not(.ql-direction-rtl){padding-left:6em}.ql-editor li.ql-indent-2:not(.ql-direction-rtl){padding-left:7.5em}.ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right{padding-right:6em}.ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right{padding-right:7.5em}.ql-editor .ql-indent-3:not(.ql-direction-rtl){padding-left:9em}.ql-editor li.ql-indent-3:not(.ql-direction-rtl){padding-left:10.5em}.ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right{padding-right:9em}.ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right{padding-right:10.5em}.ql-editor .ql-indent-4:not(.ql-direction-rtl){padding-left:12em}.ql-editor li.ql-indent-4:not(.ql-direction-rtl){padding-left:13.5em}.ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right{padding-right:12em}.ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right{padding-right:13.5em}.ql-editor .ql-indent-5:not(.ql-direction-rtl){padding-left:15em}.ql-editor li.ql-indent-5:not(.ql-direction-rtl){padding-left:16.5em}.ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right{padding-right:15em}.ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right{padding-right:16.5em}.ql-editor .ql-indent-6:not(.ql-direction-rtl){padding-left:18em}.ql-editor li.ql-indent-6:not(.ql-direction-rtl){padding-left:19.5em}.ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right{padding-right:18em}.ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right{padding-right:19.5em}.ql-editor .ql-indent-7:not(.ql-direction-rtl){padding-left:21em}.ql-editor li.ql-indent-7:not(.ql-direction-rtl){padding-left:22.5em}.ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right{padding-right:21em}.ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right{padding-right:22.5em}.ql-editor .ql-indent-8:not(.ql-direction-rtl){padding-left:24em}.ql-editor li.ql-indent-8:not(.ql-direction-rtl){padding-left:25.5em}.ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right{padding-right:24em}.ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right{padding-right:25.5em}.ql-editor .ql-indent-9:not(.ql-direction-rtl){padding-left:27em}.ql-editor li.ql-indent-9:not(.ql-direction-rtl){padding-left:28.5em}.ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right{padding-right:27em}.ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right{padding-right:28.5em}.ql-editor .ql-video{display:block;max-width:100%}.ql-editor .ql-video.ql-align-center{margin:0 auto}.ql-editor .ql-video.ql-align-right{margin:0 0 0 auto}.ql-editor .ql-bg-black{background-color:#000}.ql-editor .ql-bg-red{background-color:#e60000}.ql-editor .ql-bg-orange{background-color:#f90}.ql-editor .ql-bg-yellow{background-color:#ff0}.ql-editor .ql-bg-green{background-color:#008a00}.ql-editor .ql-bg-blue{background-color:#06c}.ql-editor .ql-bg-purple{background-color:#93f}.ql-editor .ql-color-white{color:#fff}.ql-editor .ql-color-red{color:#e60000}.ql-editor .ql-color-orange{color:#f90}.ql-editor .ql-color-yellow{color:#ff0}.ql-editor .ql-color-green{color:#008a00}.ql-editor .ql-color-blue{color:#06c}.ql-editor .ql-color-purple{color:#93f}.ql-editor .ql-font-serif{font-family:Georgia,Times New Roman,serif}.ql-editor .ql-font-monospace{font-family:Monaco,Courier New,monospace}.ql-editor .ql-size-small{font-size:.75em}.ql-editor .ql-size-large{font-size:1.5em}.ql-editor .ql-size-huge{font-size:2.5em}.ql-editor .ql-direction-rtl{direction:rtl;text-align:inherit}.ql-editor .ql-align-center{text-align:center}.ql-editor .ql-align-justify{text-align:justify}.ql-editor .ql-align-right{text-align:right}.ql-editor.ql-blank:before{color:#0009;content:attr(data-placeholder);font-style:italic;left:15px;pointer-events:none;position:absolute;right:15px}.ql-snow.ql-toolbar:after,.ql-snow .ql-toolbar:after{clear:both;content:"";display:table}.ql-snow.ql-toolbar button,.ql-snow .ql-toolbar button{background:none;border:none;cursor:pointer;display:inline-block;float:left;height:24px;padding:3px 5px;width:28px}.ql-snow.ql-toolbar button svg,.ql-snow .ql-toolbar button svg{float:left;height:100%}.ql-snow.ql-toolbar button:active:hover,.ql-snow .ql-toolbar button:active:hover{outline:none}.ql-snow.ql-toolbar input.ql-image[type=file],.ql-snow .ql-toolbar input.ql-image[type=file]{display:none}.ql-snow.ql-toolbar button:hover,.ql-snow .ql-toolbar button:hover,.ql-snow.ql-toolbar button:focus,.ql-snow .ql-toolbar button:focus,.ql-snow.ql-toolbar button.ql-active,.ql-snow .ql-toolbar button.ql-active,.ql-snow.ql-toolbar .ql-picker-label:hover,.ql-snow .ql-toolbar .ql-picker-label:hover,.ql-snow.ql-toolbar .ql-picker-label.ql-active,.ql-snow .ql-toolbar .ql-picker-label.ql-active,.ql-snow.ql-toolbar .ql-picker-item:hover,.ql-snow .ql-toolbar .ql-picker-item:hover,.ql-snow.ql-toolbar .ql-picker-item.ql-selected,.ql-snow .ql-toolbar .ql-picker-item.ql-selected{color:#06c}.ql-snow.ql-toolbar button:hover .ql-fill,.ql-snow .ql-toolbar button:hover .ql-fill,.ql-snow.ql-toolbar button:focus .ql-fill,.ql-snow .ql-toolbar button:focus .ql-fill,.ql-snow.ql-toolbar button.ql-active .ql-fill,.ql-snow .ql-toolbar button.ql-active .ql-fill,.ql-snow.ql-toolbar .ql-picker-label:hover .ql-fill,.ql-snow .ql-toolbar .ql-picker-label:hover .ql-fill,.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-fill,.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-fill,.ql-snow.ql-toolbar .ql-picker-item:hover .ql-fill,.ql-snow .ql-toolbar .ql-picker-item:hover .ql-fill,.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-fill,.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-fill,.ql-snow.ql-toolbar button:hover .ql-stroke.ql-fill,.ql-snow .ql-toolbar button:hover .ql-stroke.ql-fill,.ql-snow.ql-toolbar button:focus .ql-stroke.ql-fill,.ql-snow .ql-toolbar button:focus .ql-stroke.ql-fill,.ql-snow.ql-toolbar button.ql-active .ql-stroke.ql-fill,.ql-snow .ql-toolbar button.ql-active .ql-stroke.ql-fill,.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill,.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill{fill:#06c}.ql-snow.ql-toolbar button:hover .ql-stroke,.ql-snow .ql-toolbar button:hover .ql-stroke,.ql-snow.ql-toolbar button:focus .ql-stroke,.ql-snow .ql-toolbar button:focus .ql-stroke,.ql-snow.ql-toolbar button.ql-active .ql-stroke,.ql-snow .ql-toolbar button.ql-active .ql-stroke,.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke,.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke,.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke,.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke,.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke,.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke,.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke,.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke,.ql-snow.ql-toolbar button:hover .ql-stroke-miter,.ql-snow .ql-toolbar button:hover .ql-stroke-miter,.ql-snow.ql-toolbar button:focus .ql-stroke-miter,.ql-snow .ql-toolbar button:focus .ql-stroke-miter,.ql-snow.ql-toolbar button.ql-active .ql-stroke-miter,.ql-snow .ql-toolbar button.ql-active .ql-stroke-miter,.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke-miter,.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke-miter,.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke-miter,.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke-miter,.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter,.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter{stroke:#06c}@media (pointer: coarse){.ql-snow.ql-toolbar button:hover:not(.ql-active),.ql-snow .ql-toolbar button:hover:not(.ql-active){color:#444}.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-fill,.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-fill,.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill,.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill{fill:#444}.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke,.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke,.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter,.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter{stroke:#444}}.ql-snow,.ql-snow *{box-sizing:border-box}.ql-snow .ql-hidden{display:none}.ql-snow .ql-out-bottom,.ql-snow .ql-out-top{visibility:hidden}.ql-snow .ql-tooltip{position:absolute;transform:translateY(10px)}.ql-snow .ql-tooltip a{cursor:pointer;text-decoration:none}.ql-snow .ql-tooltip.ql-flip{transform:translateY(-10px)}.ql-snow .ql-formats{display:inline-block;vertical-align:middle}.ql-snow .ql-formats:after{clear:both;content:"";display:table}.ql-snow .ql-stroke{fill:none;stroke:#444;stroke-linecap:round;stroke-linejoin:round;stroke-width:2}.ql-snow .ql-stroke-miter{fill:none;stroke:#444;stroke-miterlimit:10;stroke-width:2}.ql-snow .ql-fill,.ql-snow .ql-stroke.ql-fill{fill:#444}.ql-snow .ql-empty{fill:none}.ql-snow .ql-even{fill-rule:evenodd}.ql-snow .ql-thin,.ql-snow .ql-stroke.ql-thin{stroke-width:1}.ql-snow .ql-transparent{opacity:.4}.ql-snow .ql-direction svg:last-child{display:none}.ql-snow .ql-direction.ql-active svg:last-child{display:inline}.ql-snow .ql-direction.ql-active svg:first-child{display:none}.ql-snow .ql-editor h1{font-size:2em}.ql-snow .ql-editor h2{font-size:1.5em}.ql-snow .ql-editor h3{font-size:1.17em}.ql-snow .ql-editor h4{font-size:1em}.ql-snow .ql-editor h5{font-size:.83em}.ql-snow .ql-editor h6{font-size:.67em}.ql-snow .ql-editor a{text-decoration:underline}.ql-snow .ql-editor blockquote{border-left:4px solid #ccc;margin-bottom:5px;margin-top:5px;padding-left:16px}.ql-snow .ql-editor code,.ql-snow .ql-editor pre{background-color:#f0f0f0;border-radius:3px}.ql-snow .ql-editor pre{white-space:pre-wrap;margin-bottom:5px;margin-top:5px;padding:5px 10px}.ql-snow .ql-editor code{font-size:85%;padding:2px 4px}.ql-snow .ql-editor pre.ql-syntax{background-color:#23241f;color:#f8f8f2;overflow:visible}.ql-snow .ql-editor img{max-width:100%}.ql-snow .ql-picker{color:#444;display:inline-block;float:left;font-size:14px;font-weight:500;height:24px;position:relative;vertical-align:middle}.ql-snow .ql-picker-label{cursor:pointer;display:inline-block;height:100%;padding-left:8px;padding-right:2px;position:relative;width:100%}.ql-snow .ql-picker-label:before{display:inline-block;line-height:22px}.ql-snow .ql-picker-options{background-color:#fff;display:none;min-width:100%;padding:4px 8px;position:absolute;white-space:nowrap}.ql-snow .ql-picker-options .ql-picker-item{cursor:pointer;display:block;padding-bottom:5px;padding-top:5px}.ql-snow .ql-picker.ql-expanded .ql-picker-label{color:#ccc;z-index:2}.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-fill{fill:#ccc}.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-stroke{stroke:#ccc}.ql-snow .ql-picker.ql-expanded .ql-picker-options{display:block;margin-top:-1px;top:100%;z-index:1}.ql-snow .ql-color-picker,.ql-snow .ql-icon-picker{width:28px}.ql-snow .ql-color-picker .ql-picker-label,.ql-snow .ql-icon-picker .ql-picker-label{padding:2px 4px}.ql-snow .ql-color-picker .ql-picker-label svg,.ql-snow .ql-icon-picker .ql-picker-label svg{right:4px}.ql-snow .ql-icon-picker .ql-picker-options{padding:4px 0}.ql-snow .ql-icon-picker .ql-picker-item{height:24px;width:24px;padding:2px 4px}.ql-snow .ql-color-picker .ql-picker-options{padding:3px 5px;width:152px}.ql-snow .ql-color-picker .ql-picker-item{border:1px solid transparent;float:left;height:16px;margin:2px;padding:0;width:16px}.ql-snow .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg{position:absolute;margin-top:-9px;right:0;top:50%;width:18px}.ql-snow .ql-picker.ql-header .ql-picker-label[data-label]:not([data-label=""]):before,.ql-snow .ql-picker.ql-font .ql-picker-label[data-label]:not([data-label=""]):before,.ql-snow .ql-picker.ql-size .ql-picker-label[data-label]:not([data-label=""]):before,.ql-snow .ql-picker.ql-header .ql-picker-item[data-label]:not([data-label=""]):before,.ql-snow .ql-picker.ql-font .ql-picker-item[data-label]:not([data-label=""]):before,.ql-snow .ql-picker.ql-size .ql-picker-item[data-label]:not([data-label=""]):before{content:attr(data-label)}.ql-snow .ql-picker.ql-header{width:98px}.ql-snow .ql-picker.ql-header .ql-picker-label:before,.ql-snow .ql-picker.ql-header .ql-picker-item:before{content:"Normal"}.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]:before,.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]:before{content:"Heading 1"}.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]:before,.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]:before{content:"Heading 2"}.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]:before,.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]:before{content:"Heading 3"}.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]:before,.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]:before{content:"Heading 4"}.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]:before,.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]:before{content:"Heading 5"}.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]:before,.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]:before{content:"Heading 6"}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]:before{font-size:2em}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]:before{font-size:1.5em}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]:before{font-size:1.17em}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]:before{font-size:1em}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]:before{font-size:.83em}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]:before{font-size:.67em}.ql-snow .ql-picker.ql-font{width:108px}.ql-snow .ql-picker.ql-font .ql-picker-label:before,.ql-snow .ql-picker.ql-font .ql-picker-item:before{content:"Sans Serif"}.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=serif]:before,.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]:before{content:"Serif"}.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=monospace]:before,.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]:before{content:"Monospace"}.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]:before{font-family:Georgia,Times New Roman,serif}.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]:before{font-family:Monaco,Courier New,monospace}.ql-snow .ql-picker.ql-size{width:98px}.ql-snow .ql-picker.ql-size .ql-picker-label:before,.ql-snow .ql-picker.ql-size .ql-picker-item:before{content:"Normal"}.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=small]:before,.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]:before{content:"Small"}.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=large]:before,.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]:before{content:"Large"}.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=huge]:before,.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]:before{content:"Huge"}.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]:before{font-size:10px}.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]:before{font-size:18px}.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]:before{font-size:32px}.ql-snow .ql-color-picker.ql-background .ql-picker-item{background-color:#fff}.ql-snow .ql-color-picker.ql-color .ql-picker-item{background-color:#000}.ql-toolbar.ql-snow{border:1px solid #ccc;box-sizing:border-box;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;padding:8px}.ql-toolbar.ql-snow .ql-formats{margin-right:15px}.ql-toolbar.ql-snow .ql-picker-label{border:1px solid transparent}.ql-toolbar.ql-snow .ql-picker-options{border:1px solid transparent;box-shadow:#0003 0 2px 8px}.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label,.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-options{border-color:#ccc}.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item.ql-selected,.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item:hover{border-color:#000}.ql-toolbar.ql-snow+.ql-container.ql-snow{border-top:0px}.ql-snow .ql-tooltip{background-color:#fff;border:1px solid #ccc;box-shadow:0 0 5px #ddd;color:#444;padding:5px 12px;white-space:nowrap}.ql-snow .ql-tooltip:before{content:"Visit URL:";line-height:26px;margin-right:8px}.ql-snow .ql-tooltip input[type=text]{display:none;border:1px solid #ccc;font-size:13px;height:26px;margin:0;padding:3px 5px;width:170px}.ql-snow .ql-tooltip a.ql-preview{display:inline-block;max-width:200px;overflow-x:hidden;text-overflow:ellipsis;vertical-align:top}.ql-snow .ql-tooltip a.ql-action:after{border-right:1px solid #ccc;content:"Edit";margin-left:16px;padding-right:8px}.ql-snow .ql-tooltip a.ql-remove:before{content:"Remove";margin-left:8px}.ql-snow .ql-tooltip a{line-height:26px}.ql-snow .ql-tooltip.ql-editing a.ql-preview,.ql-snow .ql-tooltip.ql-editing a.ql-remove{display:none}.ql-snow .ql-tooltip.ql-editing input[type=text]{display:inline-block}.ql-snow .ql-tooltip.ql-editing a.ql-action:after{border-right:0px;content:"Save";padding-right:0}.ql-snow .ql-tooltip[data-mode=link]:before{content:"Enter link:"}.ql-snow .ql-tooltip[data-mode=formula]:before{content:"Enter formula:"}.ql-snow .ql-tooltip[data-mode=video]:before{content:"Enter video:"}.ql-snow a{color:#06c}.ql-container.ql-snow{border:1px solid #ccc}.mce-content-body {padding:12px;} diff --git a/worklenz-frontend/src/assets/icons/file-icon.png b/worklenz-frontend/src/assets/icons/file-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bcb12883861095f9cebbfad137947f08d66fdd0c GIT binary patch literal 12544 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 GKmQ-zXK|JQ literal 0 HcmV?d00001 diff --git a/worklenz-frontend/src/assets/icons/icon-128x128.png b/worklenz-frontend/src/assets/icons/icon-128x128.png deleted file mode 100644 index fe1a33ff4d253712cd2d924646a78fdd7d0f2927..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8396 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 JQr9i^zW{_`m#+W- diff --git a/worklenz-frontend/src/assets/icons/icon-144x144.png b/worklenz-frontend/src/assets/icons/icon-144x144.png deleted file mode 100644 index 3a3f356d4a05533dbe7a8b603cd9fcb7b8ecfe03..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9344 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 diff --git a/worklenz-frontend/src/assets/icons/icon-192x192.png b/worklenz-frontend/src/assets/icons/icon-192x192.png deleted file mode 100644 index 4891b3f7349370d3548a3f2a0fe978705a7ff3d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12077 zcmbVy2RPhc_vaYB*9akMM1)~xjNW?$QGyVJX-bsA=utz6-V!BsClIh=rs zGM}QKJP_cD_d@gexw^O!<^7Zd{*WsV-k*FdA;9;?5HDvX0kx9}`78{K_%sNfcs?0% z1Plv@!}(<7#4&JtSqxqdF2;w1BM}k^qy$0=hD69CrQ{J(e1H82fcZRe_VOm0+JDUk z{!W^>ZwZ=sBG%K% z-OGvK#&;qSjUjk@DG2~w|6GKt`#)mch<_Ohm`uVC?Jj{3ho3C!kAXPsKjYlJJzf5o z9EX*_yWm~%ZeBz%7V*zmcSnL3f#^v1--!O_>;EzVFs^~YKXd%Iy12UjGX>F0%Lgpu zFN6HIqlu>e?sy3kJdxn-iN$OA05t_qR^u+O;fY6k5j;%^1ed?J%II&E`H+(0NWKdO zXsnal$s$Dl?Gd~t+6%8F0QL}+Z4f973?qqv!w_&83=#;H!pS23 zQ&}h}Bn~f!!NPE|Qg|2!g_40`kZ?3iQVNHWLZjqlP)PWHDoaWR3zx&;F))<9Jsu`) zkHW#^&@xgmtRxzTLf|B&B(cc|scp6kZZ8g~r0A{u@pHep*WlC8dQ# zAmlW(}9|p)IAMAtfiLt)ZqV_iyw5;YoiwpTxf$=fCq(ARL^=OHL;ISM zUB!F+(60Cg1wh7+7qwl z08adW0E!Yo{Xb0b=Sn30=SBYU-M_`2zlVc>cJlV0SPK66CmQ43KnV5(v35M8MH&JT z=GN6zGxf_}$+_ijI(EOJm3YPXS@ZjVLQ1NLD#!+kgM*WfLn97uF*B`$JT*L6CW{rW zlDzM4TC~dB*OEG*|JdZ}DdDHOfh)_IxS5TCuA#a?KX;76$F7}tH7ax)Gz`vB3E}7W zUjFo5Iq`ST8>j2)RJuXGTy(Q5F4yCq?OGz2eRpo$s;H~SaZo^e$xgw`CZDetD9Meezf!t%cpX8Dd43>88e&9l`-V=E z)Ll_TjYBi&1DW>}i$*vn4x+!|+Y%!It8HT{H+kBxuZm%+lx>;wv3I7V)sCu|c2ZbtrA$ zF{XiYiZ{EeySv+?d~+pVvducL4?(oH&UWCjpI~x7q+6FscrY_FqZpEUVDOX92vRHO zdZIuP+9#g_MXGrI75nMaJ-T}VKk3}LC4D=(@g+oP4ZjtJ!Xjq*K>RraEh0#nKlJR3 zi-!ku2Sda8&|zv+SGN{fE~z9%?<*J6Gi=U@v9X0)P$6U`bZp2)̙&_d%Kg`t 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 diff --git a/worklenz-frontend/src/assets/icons/icon-384x384.png b/worklenz-frontend/src/assets/icons/icon-384x384.png deleted file mode 100644 index 25ad78ccd2eb5a7f4098762c763d6c394c69c4dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26068 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 U@aNG&|KFA4`eu4%Iu5u054L(TB>(^b diff --git a/worklenz-frontend/src/assets/icons/icon-512x512.png b/worklenz-frontend/src/assets/icons/icon-512x512.png deleted file mode 100644 index 25216c5ff1bae98ef2baca277d61f8e6a2e0f2c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29712 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 V#4V*$Z!RPFXJKMvTwqAP^nb>AIwt@C diff --git a/worklenz-frontend/src/assets/icons/icon-96x96.png b/worklenz-frontend/src/assets/icons/icon-96x96.png deleted file mode 100644 index b2642954a17ff08e490b6b4817cced3443fbe4ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6682 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 b@4H0uxg1elfA3Fg|K8YIIa!vQ2PFL$B12tm diff --git a/worklenz-frontend/src/assets/images/block-user.png b/worklenz-frontend/src/assets/icons/insightsIcons/block-user.png similarity index 100% rename from worklenz-frontend/src/assets/images/block-user.png rename to worklenz-frontend/src/assets/icons/insightsIcons/block-user.png diff --git a/worklenz-frontend/src/assets/images/clipboard.png b/worklenz-frontend/src/assets/icons/insightsIcons/clipboard.png similarity index 100% rename from worklenz-frontend/src/assets/images/clipboard.png rename to worklenz-frontend/src/assets/icons/insightsIcons/clipboard.png diff --git a/worklenz-frontend/src/assets/images/clock-green.png b/worklenz-frontend/src/assets/icons/insightsIcons/clock-green.png similarity index 100% rename from worklenz-frontend/src/assets/images/clock-green.png rename to worklenz-frontend/src/assets/icons/insightsIcons/clock-green.png diff --git a/worklenz-frontend/src/assets/images/group.png b/worklenz-frontend/src/assets/icons/insightsIcons/group.png similarity index 100% rename from worklenz-frontend/src/assets/images/group.png rename to worklenz-frontend/src/assets/icons/insightsIcons/group.png diff --git a/worklenz-frontend/src/assets/images/insights-check.png b/worklenz-frontend/src/assets/icons/insightsIcons/insights-check.png similarity index 100% rename from worklenz-frontend/src/assets/images/insights-check.png rename to worklenz-frontend/src/assets/icons/insightsIcons/insights-check.png diff --git a/worklenz-frontend/src/assets/images/warning.png b/worklenz-frontend/src/assets/icons/insightsIcons/warning.png similarity index 100% rename from worklenz-frontend/src/assets/images/warning.png rename to worklenz-frontend/src/assets/icons/insightsIcons/warning.png diff --git a/worklenz-frontend/src/assets/images/404.svg b/worklenz-frontend/src/assets/images/404.svg deleted file mode 100644 index b46116f4..00000000 --- a/worklenz-frontend/src/assets/images/404.svg +++ /dev/null @@ -1,325 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/worklenz-frontend/src/assets/images/chevron-down-solid.svg b/worklenz-frontend/src/assets/images/chevron-down-solid.svg deleted file mode 100644 index f918ece4..00000000 --- a/worklenz-frontend/src/assets/images/chevron-down-solid.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/worklenz-frontend/src/assets/images/clock-red.png b/worklenz-frontend/src/assets/images/clock-red.png deleted file mode 100644 index ccda72a4bfa9cafa12731d702aab66bdf5febba0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4922 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 G7ykzcFiFh- diff --git a/worklenz-frontend/src/assets/images/clock-yellow.png b/worklenz-frontend/src/assets/images/clock-yellow.png deleted file mode 100644 index d9770cae738241e9f64cb5e6b6e8ef82f8d668c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46382 zcmZ^LbyQW~^Y%eR6a)kjlvKJ=q#FV0?w00~(jAJFbPEVbH%NCV(gISKymVf=8{Tt4 zKi~JC-&xCbS#r@OJ>f{RIH<-+!%B;01qi&p=8{1pEY_Q{j4V z@Z-UIaSeO$^WDwA9g8>p;Ol^dh>)_�Jdto$?4YV7IbQny&E^!RJq5gE)$-;&N}W z?|-{bCi5s_8dqEpJA&-v0oljPBW%zNB;8i z;?xnFj7i>y!|J#UySUWyy1h|BD+cw4_CP7o$t>XTtwd?V!K)PGqw z>5%ksaF8Z0U~sA->0)UxkV^I>;-HbA#o7$x*j<*!F5;8pP zubXr~1gFEl{ZCf~Y$B*8jh*^$hX1>zYf@?SN zZby3F0D$L!i=Jo;^jVE{qJvw%$r4 z2xs}sKWn+aKs8oU+do{jpuM*&KLgLB2|K>f%GJT;1db+PLneu8cZD%U_f! zBBF|lC-%=MkRCixj9D)0*b&DDXR%aftvcB=aS2ODg}xamd^tw zD)WeSz8RyS=WpHX#9iaTxLC!6ak|AUcM%-%dTBc;+=&&i{a80hIpMMMt<-gJOl-^6 z_P2}+DK&LLA2`tZCnRt*Bk?o5i{b22N>3@}+;^;N)EHg5y7OXb^NUNOgtS5??yg;$ zVT+qK4MJIz5!PIsx!_=tgU)ykQDPNGW3S+n$hq&N9|lAUP>YHsg(>B7ii&bkxH+RY zx*Zk5wLx2yM(DFe04FGTKiuAlJ^q#6lDLfxmneQ)u@Ex%8@;Z2U0nwm%pE3XW)B0b zh*0*yCmOL|#M^S|u!6&~;Zy$MVq!{2e7fP6OZeoG1@$9A|ALfE7o2ZfnKmd4k2RNt3spBNM0}=7-<_h015+?n6vD z5LZk$_i_Q9c;ntArB{;6=)Fl&L^3b>xgB!>%0DkM6daiWBi{ogp(jUi*#iT13I01> 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 QTp2cf)N|_VHy1K6wi=X7K~YZ#!yC1%5jl4+eM* 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 - - diff --git a/worklenz-frontend/src/assets/images/google-icon.png b/worklenz-frontend/src/assets/images/google-icon.png index 3b80c0fae5c81f88c196070a044ec6673537be3d..c1fb9a9292ec43b2f73b88ee90bd16c4f1d04ffd 100644 GIT binary patch literal 716 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 diff --git a/worklenz-frontend/src/assets/images/google_sign_in.png b/worklenz-frontend/src/assets/images/google_sign_in.png deleted file mode 100644 index 4118cc2ff700328c378ce47c5f75b27dd00dc446..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4308 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 diff --git a/worklenz-frontend/src/assets/images/group_1.png b/worklenz-frontend/src/assets/images/group_1.png deleted file mode 100644 index 28b50510ece9feb20658bebcb405d49fcc51466b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49239 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 ObhK_=FVe7m{C@yrh0J>Z diff --git a/worklenz-frontend/src/assets/images/insights-calendar.png b/worklenz-frontend/src/assets/images/insights-calendar.png deleted file mode 100644 index b0783765f44b9966d18a7dd5416e490bdc77ba37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19138 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 X`hWdQ2{q{)^pL!avUKrNlehmD;WFB8 diff --git a/worklenz-frontend/src/assets/images/logo-dark-mode.png b/worklenz-frontend/src/assets/images/logo-dark-mode.png new file mode 100644 index 0000000000000000000000000000000000000000..31af3a16c7351ab162556d831db87fdc6b42f615 GIT binary patch literal 2969 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 UHGPf)e)K@b*Ub%T^j#nQ4+;$HJ^%m! literal 0 HcmV?d00001 diff --git a/worklenz-frontend/src/assets/images/logo-lg.png b/worklenz-frontend/src/assets/images/logo-lg.png deleted file mode 100644 index c76469bf6244bacd02f1630c88a0dc6c46a44b1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 230748 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

    1. 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 + ); +}; + +export default CustomSearchbar; diff --git a/worklenz-frontend/src/components/CustomTableTitle.tsx b/worklenz-frontend/src/components/CustomTableTitle.tsx new file mode 100644 index 00000000..ed45e964 --- /dev/null +++ b/worklenz-frontend/src/components/CustomTableTitle.tsx @@ -0,0 +1,19 @@ +import { Flex, Tooltip, Typography } from 'antd'; +import { colors } from '../styles/colors'; +import { ExclamationCircleOutlined } from '@ant-design/icons'; + +// this custom table title used when the typography font weigh 500 needed +const CustomTableTitle = ({ title, tooltip }: { title: string; tooltip?: string | null }) => { + return ( + + {title} + {tooltip && ( + + + + )} + + ); +}; + +export default CustomTableTitle; diff --git a/worklenz-frontend/src/components/EmptyListPlaceholder.tsx b/worklenz-frontend/src/components/EmptyListPlaceholder.tsx new file mode 100644 index 00000000..1d10002c --- /dev/null +++ b/worklenz-frontend/src/components/EmptyListPlaceholder.tsx @@ -0,0 +1,30 @@ +import { Empty, Typography } from 'antd'; +import React from 'react'; + +type EmptyListPlaceholderProps = { + imageSrc?: string; + imageHeight?: number; + text: string; +}; + +const EmptyListPlaceholder = ({ + imageSrc = 'https://app.worklenz.com/assets/images/empty-box.webp', + imageHeight = 60, + text, +}: EmptyListPlaceholderProps) => { + return ( + {text}} + /> + ); +}; + +export default EmptyListPlaceholder; diff --git a/worklenz-frontend/src/components/ErrorBoundary.tsx b/worklenz-frontend/src/components/ErrorBoundary.tsx new file mode 100644 index 00000000..f89bdaad --- /dev/null +++ b/worklenz-frontend/src/components/ErrorBoundary.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { Button, Result } from 'antd'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import logger from '@/utils/errorLogger'; + +interface Props { + children: React.ReactNode; +} + +interface State { + hasError: boolean; + error?: Error; +} + +class ErrorBoundary extends React.Component { + constructor(props: Props) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + logger.error('Error caught by ErrorBoundary:', { + error: error.message, + stack: error.stack, + componentStack: errorInfo.componentStack + }); + console.error('Error caught by ErrorBoundary:', error); + } + + render() { + if (this.state.hasError) { + return ; + } + + return this.props.children; + } +} + +const ErrorFallback: React.FC<{ error?: Error }> = ({ error }) => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + const handleRetry = () => { + window.location.reload(); + }; + + const handleGoHome = () => { + navigate('/worklenz/home'); + window.location.reload(); + }; + + return ( + + {t('error.retry', 'Try Again')} + , + , + ]} + /> + ); +}; + +export default ErrorBoundary; \ No newline at end of file diff --git a/worklenz-frontend/src/components/PinRouteToNavbarButton.tsx b/worklenz-frontend/src/components/PinRouteToNavbarButton.tsx new file mode 100644 index 00000000..c7e99c1d --- /dev/null +++ b/worklenz-frontend/src/components/PinRouteToNavbarButton.tsx @@ -0,0 +1,62 @@ +import React, { useState } from 'react'; +import { getJSONFromLocalStorage, saveJSONToLocalStorage } from '../utils/localStorageFunctions'; +import { Button, ConfigProvider, Tooltip } from 'antd'; +import { PushpinFilled, PushpinOutlined } from '@ant-design/icons'; +import { colors } from '../styles/colors'; +import { navRoutes, NavRoutesType } from '../features/navbar/navRoutes'; + +// this component pin the given path to navbar +const PinRouteToNavbarButton = ({ name, path }: NavRoutesType) => { + const navRoutesList: NavRoutesType[] = getJSONFromLocalStorage('navRoutes') || navRoutes; + + const [isPinned, setIsPinned] = useState( + // this function check the current name is available in local storage's navRoutes list if it's available then isPinned state will be true + navRoutesList.filter(item => item.name === name).length && true + ); + + // this function handle pin to the navbar + const handlePinToNavbar = (name: string, path: string) => { + let newNavRoutesList; + + const route: NavRoutesType = { name, path }; + + if (isPinned) { + newNavRoutesList = navRoutesList.filter(item => item.name !== route.name); + } else { + newNavRoutesList = [...navRoutesList, route]; + } + + setIsPinned(prev => !prev); + saveJSONToLocalStorage('navRoutes', newNavRoutesList); + }; + + return ( + + + +
      + + + ); +}; + +export default MembersStep; diff --git a/worklenz-frontend/src/components/account-setup/organization-step.tsx b/worklenz-frontend/src/components/account-setup/organization-step.tsx new file mode 100644 index 00000000..c61d1eee --- /dev/null +++ b/worklenz-frontend/src/components/account-setup/organization-step.tsx @@ -0,0 +1,64 @@ +import React, { useEffect, useRef } from 'react'; +import { Form, Input, InputRef, Typography } from 'antd'; +import { useDispatch, useSelector } from 'react-redux'; +import { useTranslation } from 'react-i18next'; +import { setOrganizationName } from '@/features/account-setup/account-setup.slice'; +import { RootState } from '@/app/store'; +import { sanitizeInput } from '@/utils/sanitizeInput'; +import './admin-center-common.css'; + +const { Title } = Typography; + +interface Props { + onEnter: () => void; + styles: any; + organizationNamePlaceholder: string; +} + +export const OrganizationStep: React.FC = ({ + onEnter, + styles, + organizationNamePlaceholder, +}) => { + const { t } = useTranslation('account-setup'); + const dispatch = useDispatch(); + const { organizationName } = useSelector((state: RootState) => state.accountSetupReducer); + const inputRef = useRef(null); + + useEffect(() => { + setTimeout(() => inputRef.current?.focus(), 300); + }, []); + + const onPressEnter = () => { + if (!organizationName.trim()) return; + onEnter(); + }; + + const handleOrgNameChange = (e: React.ChangeEvent) => { + const sanitizedValue = sanitizeInput(e.target.value); + dispatch(setOrganizationName(sanitizedValue)); + }; + + return ( +
      + + + {t('organizationStepTitle')} + + + {t('organizationStepLabel')}} + > + + +
      + ); +}; diff --git a/worklenz-frontend/src/components/account-setup/project-step.tsx b/worklenz-frontend/src/components/account-setup/project-step.tsx new file mode 100644 index 00000000..ec42b8d0 --- /dev/null +++ b/worklenz-frontend/src/components/account-setup/project-step.tsx @@ -0,0 +1,155 @@ +import React, { startTransition, useEffect, useRef, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; + +import { Button, Drawer, Form, Input, InputRef, Select, Typography } from 'antd'; +import TemplateDrawer from '../common/template-drawer/template-drawer'; + +import { RootState } from '@/app/store'; +import { setProjectName, setTemplateId } from '@/features/account-setup/account-setup.slice'; +import { sanitizeInput } from '@/utils/sanitizeInput'; + +import { projectTemplatesApiService } from '@/api/project-templates/project-templates.api.service'; +import logger from '@/utils/errorLogger'; + +import { IAccountSetupRequest } from '@/types/project-templates/project-templates.types'; + +import { evt_account_setup_template_complete } from '@/shared/worklenz-analytics-events'; +import { useMixpanelTracking } from '@/hooks/useMixpanelTracking'; +import { createPortal } from 'react-dom'; + +const { Title } = Typography; + +interface Props { + onEnter: () => void; + styles: any; + isDarkMode: boolean; +} + +export const ProjectStep: React.FC = ({ onEnter, styles, isDarkMode = false }) => { + const { t } = useTranslation('account-setup'); + const dispatch = useDispatch(); + const navigate = useNavigate(); + const { trackMixpanelEvent } = useMixpanelTracking(); + + const inputRef = useRef(null); + + useEffect(() => { + setTimeout(() => inputRef.current?.focus(), 200); + }, []); + + const { projectName, templateId, organizationName } = useSelector( + (state: RootState) => state.accountSetupReducer + ); + const [open, setOpen] = useState(false); + const [creatingFromTemplate, setCreatingFromTemplate] = useState(false); + + const handleTemplateSelected = (templateId: string) => { + if (!templateId) return; + dispatch(setTemplateId(templateId)); + }; + + const toggleTemplateSelector = (isOpen: boolean) => { + startTransition(() => setOpen(isOpen)); + }; + + const createFromTemplate = async () => { + setCreatingFromTemplate(true); + if (!templateId) return; + try { + const model: IAccountSetupRequest = { + team_name: organizationName, + project_name: null, + template_id: templateId || null, + tasks: [], + team_members: [], + }; + const res = await projectTemplatesApiService.setupAccount(model); + if (res.done && res.body.id) { + toggleTemplateSelector(false); + trackMixpanelEvent(evt_account_setup_template_complete); + navigate(`/worklenz/projects/${res.body.id}?tab=tasks-list&pinned_tab=tasks-list`); + } + } catch (error) { + logger.error('createFromTemplate', error); + } + }; + + const onPressEnter = () => { + if (!projectName.trim()) return; + onEnter(); + }; + + const handleProjectNameChange = (e: React.ChangeEvent) => { + const sanitizedValue = sanitizeInput(e.target.value); + dispatch(setProjectName(sanitizedValue)); + }; + + return ( +
      +
      + + + {t('projectStepTitle')} + + + {t('projectStepLabel')}} + > + + +
      +
      + + {t('or')} + +
      +
      + +
      + +
      + {createPortal( + toggleTemplateSelector(false)} + open={open} + footer={ +
      + + +
      + } + > + {}} + /> +
      , + document.body, + 'template-drawer' + )} +
      + ); +}; diff --git a/worklenz-frontend/src/components/account-setup/tasks-step.tsx b/worklenz-frontend/src/components/account-setup/tasks-step.tsx new file mode 100644 index 00000000..472654fc --- /dev/null +++ b/worklenz-frontend/src/components/account-setup/tasks-step.tsx @@ -0,0 +1,132 @@ +import React, { useEffect, useRef } from 'react'; +import { Form, Input, Button, Typography, List, InputRef } from 'antd'; +import { PlusOutlined, DeleteOutlined, CloseCircleOutlined } from '@ant-design/icons'; +import { useDispatch, useSelector } from 'react-redux'; +import { useTranslation } from 'react-i18next'; +import { RootState } from '@/app/store'; +import { setTasks } from '@/features/account-setup/account-setup.slice'; +import { sanitizeInput } from '@/utils/sanitizeInput'; + +const { Title } = Typography; + +interface Props { + onEnter: () => void; + styles: any; + isDarkMode: boolean; +} + +export const TasksStep: React.FC = ({ onEnter, styles, isDarkMode }) => { + const { t } = useTranslation('account-setup'); + const dispatch = useDispatch(); + const { tasks, projectName } = useSelector((state: RootState) => state.accountSetupReducer); + const inputRefs = useRef<(InputRef | null)[]>([]); + + const addTask = () => { + if (tasks.length == 5) return; + + const newId = tasks.length > 0 ? Math.max(...tasks.map(t => t.id)) + 1 : 0; + dispatch(setTasks([...tasks, { id: newId, value: '' }])); + setTimeout(() => { + inputRefs.current[newId]?.focus(); + }, 0); + }; + + const removeTask = (id: number) => { + if (tasks.length > 1) { + dispatch(setTasks(tasks.filter(task => task.id !== id))); + } + }; + + const updateTask = (id: number, value: string) => { + const sanitizedValue = sanitizeInput(value); + dispatch(setTasks(tasks.map(task => (task.id === id ? { ...task, value: sanitizedValue } : task)))); + }; + + const handleKeyPress = (e: React.KeyboardEvent) => { + const input = e.currentTarget as HTMLInputElement; + if (!input.value.trim()) return; + e.preventDefault(); + addTask(); + }; + + useEffect(() => { + setTimeout(() => inputRefs.current[0]?.focus(), 200); + }, []); + + // Function to set ref that doesn't return anything (void) + const setInputRef = (index: number) => (el: InputRef | null) => { + inputRefs.current[index] = el; + }; + + return ( +
      + + + {t('tasksStepTitle')} + + + + {t('tasksStepLabel')} "{projectName}". {t('maxTasks')} + + } + > + ( + +
      + updateTask(task.id, e.target.value)} + onPressEnter={handleKeyPress} + ref={setInputRef(index)} + /> +
      +
      + )} + /> + +
      +
      +
      + ); +}; diff --git a/worklenz-frontend/src/components/add-members-dropdown-v2/add-members-dropdown.css b/worklenz-frontend/src/components/add-members-dropdown-v2/add-members-dropdown.css new file mode 100644 index 00000000..6900d3da --- /dev/null +++ b/worklenz-frontend/src/components/add-members-dropdown-v2/add-members-dropdown.css @@ -0,0 +1,13 @@ +[data-theme="dark"] { + --border-color: 1px solid black; +} + +[data-theme="default"] { + --border-color: 1px solid #dee2e6; +} + +.custom-dropdown-menu .ant-dropdown-menu { + box-shadow: none !important; + border-top: var(--border-color) !important; + border-bottom: var(--border-color) !important; +} diff --git a/worklenz-frontend/src/components/add-members-dropdown-v2/add-members-dropdown.tsx b/worklenz-frontend/src/components/add-members-dropdown-v2/add-members-dropdown.tsx new file mode 100644 index 00000000..6bfd98e7 --- /dev/null +++ b/worklenz-frontend/src/components/add-members-dropdown-v2/add-members-dropdown.tsx @@ -0,0 +1,137 @@ +import React, { useState } from 'react'; +import { Avatar, Button, Checkbox, Dropdown, Input, Menu, Typography } from 'antd'; +import { UserAddOutlined, UsergroupAddOutlined } from '@ant-design/icons'; +import './add-members-dropdown.css'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import { AvatarNamesMap } from '@/shared/constants'; + +const AddMembersDropdown: React.FC = () => { + const [checkedMembers, setCheckedMembers] = useState([]); + + const handleCheck = (member: string) => { + setCheckedMembers(prevChecked => + prevChecked.includes(member) + ? prevChecked.filter(m => m !== member) + : [...prevChecked, member] + ); + }; + + const themeMode = useAppSelector(state => state.themeReducer.mode); + + const inviteItems = [ + { + key: '1', + label: ( + e.stopPropagation()} + onChange={() => handleCheck('Invite Member 1')} + > +
      + + R + +
      + Raveesha Dilanka + + raveeshadilanka1999@gmail.com + +
      +
      +
      + ), + }, + ]; + + // Define menu items with header and footer + const menu = ( +
      + {/* Header */} +
      + +
      + + {/* Invite Items */} + + + + + {/* Footer */} +
      + +
      +
      + ); + + return ( + menu} + overlayClassName="custom-dropdown-menu" + overlayStyle={{ + width: '300px', + boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)', + }} + > + + + ); +}; + +export default AddMembersDropdown; diff --git a/worklenz-frontend/src/components/add-members-dropdown/add-members-dropdown.css b/worklenz-frontend/src/components/add-members-dropdown/add-members-dropdown.css new file mode 100644 index 00000000..6ea7b4cf --- /dev/null +++ b/worklenz-frontend/src/components/add-members-dropdown/add-members-dropdown.css @@ -0,0 +1,5 @@ +.custom-dropdown-menu .ant-dropdown-menu { + box-shadow: none !important; + border-top: 1px solid #dee2e6 !important; + border-bottom: 1px solid #dee2e6 !important; +} diff --git a/worklenz-frontend/src/components/add-members-dropdown/add-members-dropdown.tsx b/worklenz-frontend/src/components/add-members-dropdown/add-members-dropdown.tsx new file mode 100644 index 00000000..b2ab8128 --- /dev/null +++ b/worklenz-frontend/src/components/add-members-dropdown/add-members-dropdown.tsx @@ -0,0 +1,132 @@ +import React, { useState } from 'react'; +import { Avatar, Button, Checkbox, Dropdown, Input, Menu, Typography } from 'antd'; +import { PlusOutlined, UsergroupAddOutlined } from '@ant-design/icons'; +import './add-members-dropdown.css'; +import { AvatarNamesMap } from '../../shared/constants'; +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { useAppSelector } from '@/hooks/useAppSelector'; + +const AddMembersDropdown: React.FC = () => { + const [checkedMembers, setCheckedMembers] = useState([]); + const dispatch = useAppDispatch(); + const teamMembers = useAppSelector(state => state.teamMembersReducer.teamMembers); + + const handleCheck = (member: string) => { + setCheckedMembers(prevChecked => + prevChecked.includes(member) + ? prevChecked.filter(m => m !== member) + : [...prevChecked, member] + ); + }; + + const inviteItems = [ + { + key: '1', + label: ( + e.stopPropagation()} // Prevent dropdown from closing + onChange={() => handleCheck('Invite Member 1')} + > +
      + + R + +
      + Raveesha Dilanka + + raveeshadilanka1999@gmail.com + +
      +
      +
      + ), + }, + ]; + + // Define menu items with header and footer + const menu = ( +
      + {/* Header */} +
      + +
      + + {/* Invite Items */} + + + + + {/* Footer */} +
      + +
      +
      + ); + + return ( + menu} + overlayClassName="custom-dropdown-menu" + overlayStyle={{ + width: '300px', + boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)', + }} + > + + + ); +}; + +export default AddMembersDropdown; diff --git a/worklenz-frontend/src/components/admin-center/billing/account-storage/account-storage.tsx b/worklenz-frontend/src/components/admin-center/billing/account-storage/account-storage.tsx new file mode 100644 index 00000000..facd237d --- /dev/null +++ b/worklenz-frontend/src/components/admin-center/billing/account-storage/account-storage.tsx @@ -0,0 +1,95 @@ +import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service'; +import { fetchStorageInfo } from '@/features/admin-center/admin-center.slice'; +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import { SUBSCRIPTION_STATUS } from '@/shared/constants'; +import { IBillingAccountStorage } from '@/types/admin-center/admin-center.types'; +import logger from '@/utils/errorLogger'; +import { Card, Progress, Typography } from 'antd/es'; +import { useEffect, useState, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; + +interface IAccountStorageProps { + themeMode: string; +} + +const AccountStorage = ({ themeMode }: IAccountStorageProps) => { + const { t } = useTranslation('admin-center/current-bill'); + const dispatch = useAppDispatch(); + const [subscriptionType, setSubscriptionType] = useState(SUBSCRIPTION_STATUS.TRIALING); + + const { loadingBillingInfo, billingInfo, storageInfo } = useAppSelector(state => state.adminCenterReducer); + + const formatBytes = useMemo( + () => + (bytes = 0, decimals = 2) => { + if (!+bytes) return '0 MB'; + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + const formattedValue = parseFloat((bytes / Math.pow(k, i)).toFixed(dm)); + + return `${formattedValue} ${subscriptionType !== SUBSCRIPTION_STATUS.FREE ? sizes[i] : 'MB'}`; + }, + [subscriptionType] + ); + + useEffect(() => { + dispatch(fetchStorageInfo()); + }, []); + + useEffect(() => { + setSubscriptionType(billingInfo?.status ?? SUBSCRIPTION_STATUS.TRIALING); + }, [billingInfo?.status]); + + const textColor = themeMode === 'dark' ? '#ffffffd9' : '#000000d9'; + + return ( + + {t('accountStorage')} + + } + > +
      +
      + {percent}% Used} + /> +
      +
      + + {t('used')} {formatBytes(storageInfo?.used ?? 0, 1)} + + + {t('remaining')} {formatBytes(storageInfo?.remaining ?? 0, 1)} + +
      +
      +
      + ); +}; + +export default AccountStorage; diff --git a/worklenz-frontend/src/components/admin-center/billing/billing-tables/charges-table.tsx b/worklenz-frontend/src/components/admin-center/billing/billing-tables/charges-table.tsx new file mode 100644 index 00000000..1ce40a72 --- /dev/null +++ b/worklenz-frontend/src/components/admin-center/billing/billing-tables/charges-table.tsx @@ -0,0 +1,99 @@ +import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service'; +import { IBillingCharge, IBillingChargesResponse } from '@/types/admin-center/admin-center.types'; +import logger from '@/utils/errorLogger'; +import { formatDate } from '@/utils/timeUtils'; +import { Table, TableProps, Tag } from 'antd'; +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +const ChargesTable: React.FC = () => { + const { t } = useTranslation('admin-center/current-bill'); + const [charges, setCharges] = useState({}); + const [loadingCharges, setLoadingCharges] = useState(false); + + const fetchCharges = async () => { + try { + setLoadingCharges(true); + const res = await adminCenterApiService.getCharges(); + if (res.done) { + setCharges(res.body); + } + } catch (error) { + logger.error('Error fetching charges:', error); + } finally { + setLoadingCharges(false); + } + }; + + const columns: TableProps['columns'] = [ + { + title: t('description'), + key: 'name', + dataIndex: 'name', + }, + { + title: t('billingPeriod'), + key: 'billingPeriod', + render: record => { + return `${formatDate(new Date(record.start_date))} - ${formatDate(new Date(record.end_date))}`; + }, + }, + { + title: t('billStatus'), + key: 'status', + dataIndex: 'status', + render: (_, record) => { + return ( + + {record.status?.toUpperCase()} + + ); + }, + }, + { + title: t('perUserValue'), + key: 'perUserValue', + dataIndex: 'perUserValue', + render: (_, record) => ( + + {record.currency} {record.unit_price} + + ), + }, + { + title: t('users'), + key: 'quantity', + dataIndex: 'quantity', + }, + { + title: t('amount'), + key: 'amount', + dataIndex: 'amount', + render: (_, record) => ( + + {record.currency} {record.amount} + + ), + }, + ]; + + useEffect(() => { + fetchCharges(); + }, []); + + return ( + + columns={columns} + dataSource={charges.plan_charges} + pagination={false} + loading={loadingCharges} + rowKey="id" + /> + ); +}; + +export default ChargesTable; diff --git a/worklenz-frontend/src/components/admin-center/billing/billing-tables/invoices-table.tsx b/worklenz-frontend/src/components/admin-center/billing/billing-tables/invoices-table.tsx new file mode 100644 index 00000000..56290ed3 --- /dev/null +++ b/worklenz-frontend/src/components/admin-center/billing/billing-tables/invoices-table.tsx @@ -0,0 +1,105 @@ +import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service'; +import { IBillingTransaction } from '@/types/admin-center/admin-center.types'; +import logger from '@/utils/errorLogger'; +import { formatDate } from '@/utils/timeUtils'; +import { ContainerOutlined } from '@ant-design/icons'; +import { Button, Table, TableProps, Tag, Tooltip } from 'antd'; +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +const InvoicesTable: React.FC = () => { + const { t } = useTranslation('admin-center/current-bill'); + + const [transactions, setTransactions] = useState([]); + const [loadingTransactions, setLoadingTransactions] = useState(false); + + const fetchTransactions = async () => { + try { + setLoadingTransactions(true); + const res = await adminCenterApiService.getTransactions(); + if (res.done) { + setTransactions(res.body); + } + } catch (error) { + logger.error('Error fetching transactions:', error); + } finally { + setLoadingTransactions(false); + } + }; + + const handleInvoiceViewClick = (record: IBillingTransaction) => { + if (!record.receipt_url) return; + window.open(record.receipt_url, '_blank'); + }; + + useEffect(() => { + fetchTransactions(); + }, []); + + const columns: TableProps['columns'] = [ + { + title: t('transactionId'), + key: 'transactionId', + dataIndex: 'subscription_payment_id', + }, + { + title: t('transactionDate'), + key: 'transactionDate', + render: record => `${formatDate(new Date(record.event_time))}`, + }, + + { + title: t('billingPeriod'), + key: 'billingPeriod', + render: record => { + return `${formatDate(new Date(record.event_time))} - ${formatDate(new Date(record.next_bill_date))}`; + }, + }, + + { + title: t('paymentMethod'), + key: 'paymentMethod', + dataIndex: 'payment_method', + }, + { + title: t('status'), + key: 'status', + dataIndex: 'status', + render: (_, record) => ( + + {record.payment_status?.toUpperCase()} + + ), + }, + { + key: 'button', + render: (_, record) => ( + + + + ), + }, + ]; + + return ( + + ); +}; + +export default InvoicesTable; diff --git a/worklenz-frontend/src/components/admin-center/billing/current-bill.css b/worklenz-frontend/src/components/admin-center/billing/current-bill.css new file mode 100644 index 00000000..8f6d8d71 --- /dev/null +++ b/worklenz-frontend/src/components/admin-center/billing/current-bill.css @@ -0,0 +1,12 @@ +.current-billing .ant-card-head-wrapper { + padding: 16px 0; +} + +.current-billing .ant-progress-inner { + width: 75px !important; + height: 75px !important; +} + +:where(.css-dev-only-do-not-override-ezht69).ant-modal .ant-modal-footer { + margin-top: 0 !important; +} diff --git a/worklenz-frontend/src/components/admin-center/billing/current-bill.tsx b/worklenz-frontend/src/components/admin-center/billing/current-bill.tsx new file mode 100644 index 00000000..7eacbae2 --- /dev/null +++ b/worklenz-frontend/src/components/admin-center/billing/current-bill.tsx @@ -0,0 +1,141 @@ +import { Button, Card, Col, Modal, Row, Tooltip, Typography } from 'antd'; +import React, { useEffect } from 'react'; +import './current-bill.css'; +import { InfoCircleTwoTone } from '@ant-design/icons'; +import ChargesTable from './billing-tables/charges-table'; +import InvoicesTable from './billing-tables/invoices-table'; +import UpgradePlansLKR from './drawers/upgrade-plans-lkr/upgrade-plans-lkr'; +import UpgradePlans from './drawers/upgrade-plans/upgrade-plans'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { useMediaQuery } from 'react-responsive'; +import { useTranslation } from 'react-i18next'; +import { + toggleDrawer, + toggleUpgradeModal, +} from '@/features/admin-center/billing/billing.slice'; +import { fetchBillingInfo, fetchFreePlanSettings } from '@/features/admin-center/admin-center.slice'; +import RedeemCodeDrawer from './drawers/redeem-code-drawer/redeem-code-drawer'; +import CurrentPlanDetails from './current-plan-details/current-plan-details'; +import AccountStorage from './account-storage/account-storage'; +import { useAuthService } from '@/hooks/useAuth'; +import { ISUBSCRIPTION_TYPE } from '@/shared/constants'; + +const CurrentBill: React.FC = () => { + const dispatch = useAppDispatch(); + const { t } = useTranslation('admin-center/current-bill'); + const themeMode = useAppSelector(state => state.themeReducer.mode); + const { isUpgradeModalOpen } = useAppSelector(state => state.adminCenterReducer); + const isTablet = useMediaQuery({ query: '(min-width: 1025px)' }); + const browserTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; + const currentSession = useAuthService().getCurrentSession(); + + useEffect(() => { + dispatch(fetchBillingInfo()); + dispatch(fetchFreePlanSettings()); + }, [dispatch]); + + const titleStyle = { + color: themeMode === 'dark' ? '#ffffffd9' : '#000000d9', + fontWeight: 500, + fontSize: '16px', + display: 'flex', + gap: '4px', + }; + + const renderMobileView = () => ( +
      +
      + {t('currentPlanDetails')}} + extra={ +
      + + dispatch(toggleUpgradeModal())} + width={1000} + centered + okButtonProps={{ hidden: true }} + cancelButtonProps={{ hidden: true }} + > + {browserTimeZone === 'Asia/Colombo' ? : } + +
      + } + > +
      +
      + {t('cardBodyText01')} + {t('cardBodyText02')} +
      + + +
      +
      + + + + + + + ); + + const renderChargesAndInvoices = () => ( + <> +
      + + {t('charges')} + + + + + } + style={{ marginTop: '16px' }} + > + + +
      + +
      + {t('invoices')}} + style={{ marginTop: '16px' }} + > + + +
      + + ); + + return ( +
      + {isTablet ? ( + +
      + + + + + + + ) : ( + renderMobileView() + )} + {currentSession?.subscription_type === ISUBSCRIPTION_TYPE.PADDLE && renderChargesAndInvoices()} + + ); +}; + +export default CurrentBill; diff --git a/worklenz-frontend/src/components/admin-center/billing/current-plan-details/current-plan-details.tsx b/worklenz-frontend/src/components/admin-center/billing/current-plan-details/current-plan-details.tsx new file mode 100644 index 00000000..42e1a449 --- /dev/null +++ b/worklenz-frontend/src/components/admin-center/billing/current-plan-details/current-plan-details.tsx @@ -0,0 +1,437 @@ +import React, { useState } from 'react'; +import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service'; +import { + evt_billing_pause_plan, + evt_billing_resume_plan, + evt_billing_add_more_seats, +} from '@/shared/worklenz-analytics-events'; +import { useMixpanelTracking } from '@/hooks/useMixpanelTracking'; +import logger from '@/utils/errorLogger'; +import { Button, Card, Flex, Modal, Space, Tooltip, Typography, Statistic, Select, Form, Row, Col } from 'antd/es'; +import RedeemCodeDrawer from '../drawers/redeem-code-drawer/redeem-code-drawer'; +import { + fetchBillingInfo, + toggleRedeemCodeDrawer, + toggleUpgradeModal, +} from '@/features/admin-center/admin-center.slice'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { useTranslation } from 'react-i18next'; +import { WarningTwoTone, PlusOutlined } from '@ant-design/icons'; +import { calculateTimeGap } from '@/utils/calculate-time-gap'; +import { formatDate } from '@/utils/timeUtils'; +import UpgradePlansLKR from '../drawers/upgrade-plans-lkr/upgrade-plans-lkr'; +import UpgradePlans from '../drawers/upgrade-plans/upgrade-plans'; +import { ISUBSCRIPTION_TYPE, SUBSCRIPTION_STATUS } from '@/shared/constants'; +import { billingApiService } from '@/api/admin-center/billing.api.service'; + +const CurrentPlanDetails = () => { + const dispatch = useAppDispatch(); + const { t } = useTranslation('admin-center/current-bill'); + const { trackMixpanelEvent } = useMixpanelTracking(); + + const [pausingPlan, setPausingPlan] = useState(false); + const [cancellingPlan, setCancellingPlan] = useState(false); + const [addingSeats, setAddingSeats] = useState(false); + const [isMoreSeatsModalVisible, setIsMoreSeatsModalVisible] = useState(false); + const [selectedSeatCount, setSelectedSeatCount] = useState(5); + + const themeMode = useAppSelector(state => state.themeReducer.mode); + const { loadingBillingInfo, billingInfo, freePlanSettings, isUpgradeModalOpen } = useAppSelector( + state => state.adminCenterReducer + ); + + const browserTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; + + type SeatOption = { label: string; value: number | string }; + const seatCountOptions: SeatOption[] = [1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90] + .map(value => ({ label: value.toString(), value })); + seatCountOptions.push({ label: '100+', value: '100+' }); + + const handleSubscriptionAction = async (action: 'pause' | 'resume') => { + const isResume = action === 'resume'; + const setLoadingState = isResume ? setCancellingPlan : setPausingPlan; + const apiMethod = isResume + ? adminCenterApiService.resumeSubscription + : adminCenterApiService.pauseSubscription; + const eventType = isResume ? evt_billing_resume_plan : evt_billing_pause_plan; + + try { + setLoadingState(true); + const res = await apiMethod(); + if (res.done) { + setTimeout(() => { + setLoadingState(false); + dispatch(fetchBillingInfo()); + trackMixpanelEvent(eventType); + }, 8000); + return; // Exit function to prevent finally block from executing + } + } catch (error) { + logger.error(`Error ${action}ing subscription`, error); + setLoadingState(false); // Only set to false on error + } + }; + + const handleAddMoreSeats = () => { + setIsMoreSeatsModalVisible(true); + }; + + const handlePurchaseMoreSeats = async () => { + if (selectedSeatCount.toString() === '100+' || !billingInfo?.total_seats) return; + + try { + setAddingSeats(true); + const totalSeats = Number(selectedSeatCount) + (billingInfo?.total_seats || 0); + const res = await billingApiService.purchaseMoreSeats(totalSeats); + if (res.done) { + setIsMoreSeatsModalVisible(false); + dispatch(fetchBillingInfo()); + trackMixpanelEvent(evt_billing_add_more_seats); + } + } catch (error) { + logger.error('Error adding more seats', error); + } finally { + setAddingSeats(false); + } + }; + + const calculateRemainingSeats = () => { + if (billingInfo?.total_seats && billingInfo?.total_used) { + return billingInfo.total_seats - billingInfo.total_used; + } + return 0; + }; + + const checkSubscriptionStatus = (allowedStatuses: any[]) => { + if (!billingInfo?.status || billingInfo.is_ltd_user) return false; + return allowedStatuses.includes(billingInfo.status); + }; + + const shouldShowRedeemButton = () => { + if (billingInfo?.trial_in_progress) return true; + return billingInfo?.ltd_users ? billingInfo.ltd_users < 50 : false; + }; + + const showChangeButton = () => { + return checkSubscriptionStatus([SUBSCRIPTION_STATUS.ACTIVE, SUBSCRIPTION_STATUS.PASTDUE]); + }; + + const showPausePlanButton = () => { + return checkSubscriptionStatus([SUBSCRIPTION_STATUS.ACTIVE, SUBSCRIPTION_STATUS.PASTDUE]); + }; + + const showResumePlanButton = () => { + return checkSubscriptionStatus([SUBSCRIPTION_STATUS.PAUSED]); + }; + + const shouldShowAddSeats = () => { + if (!billingInfo) return false; + return billingInfo.subscription_type === ISUBSCRIPTION_TYPE.PADDLE && + billingInfo.status === SUBSCRIPTION_STATUS.ACTIVE; + }; + + const renderExtra = () => { + if (!billingInfo || billingInfo.is_custom) return null; + + return ( + + {showPausePlanButton() && ( + + )} + + {showResumePlanButton() && ( + + )} + + {billingInfo.trial_in_progress && ( + + )} + + {showChangeButton() && ( + + )} + + ); + }; + + const renderLtdDetails = () => { + if (!billingInfo || billingInfo.is_custom) return null; + return ( + + {billingInfo.plan_name} + {t('ltdUsers', { ltd_users: billingInfo.ltd_users })} + + ); + }; + + const renderTrialDetails = () => { + const checkIfTrialExpired = () => { + if (!billingInfo?.trial_expire_date) return false; + const today = new Date(); + today.setHours(0, 0, 0, 0); // Set to start of day for comparison + const trialExpireDate = new Date(billingInfo.trial_expire_date); + trialExpireDate.setHours(0, 0, 0, 0); // Set to start of day for comparison + return today > trialExpireDate; + }; + + const getExpirationMessage = (expireDate: string) => { + const today = new Date(); + today.setHours(0, 0, 0, 0); // Set to start of day for comparison + + const tomorrow = new Date(today); + tomorrow.setDate(tomorrow.getDate() + 1); + + const expDate = new Date(expireDate); + expDate.setHours(0, 0, 0, 0); // Set to start of day for comparison + + if (expDate.getTime() === today.getTime()) { + return t('expirestoday', 'today'); + } else if (expDate.getTime() === tomorrow.getTime()) { + return t('expirestomorrow', 'tomorrow'); + } else if (expDate < today) { + const diffTime = Math.abs(today.getTime() - expDate.getTime()); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + return t('expiredDaysAgo', '{{days}} days ago', { days: diffDays }); + } else { + return calculateTimeGap(expireDate); + } + }; + + const isExpired = checkIfTrialExpired(); + const trialExpireDate = billingInfo?.trial_expire_date || ''; + + return ( + + + {t('trialPlan')} + {isExpired && } + + + + {isExpired + ? t('trialExpired', { + trial_expire_string: getExpirationMessage(trialExpireDate) + }) + : t('trialInProgress', { + trial_expire_string: getExpirationMessage(trialExpireDate) + }) + } + + + + ); + }; + + const renderFreePlan = () => ( + + Free Plan + +
      -{' '} + {freePlanSettings?.team_member_limit === 0 + ? t('unlimitedTeamMembers') + : `${freePlanSettings?.team_member_limit} ${t('teamMembers')}`} +
      - {freePlanSettings?.projects_limit} {t('projects')} +
      - {freePlanSettings?.free_tier_storage} MB {t('storage')} +
      +
      + ); + + const renderPaddleSubscriptionInfo = () => { + return ( + + {billingInfo?.plan_name} + + {billingInfo?.default_currency}  + + {billingInfo?.billing_type === 'year' + ? billingInfo.unit_price_per_month + : billingInfo?.unit_price} + +  {t('perMonthPerUser')} + + + + {shouldShowAddSeats() && billingInfo?.total_seats && ( +
      + +
      + + + + + + + + + + + )} + + ); + }; + + const renderCreditSubscriptionInfo = () => { + return + Credit Plan + + }; + + const renderCustomSubscriptionInfo = () => { + return + Custom Plan + Your plan is valid till {billingInfo?.valid_till_date} + + }; + + return ( + + {t('currentPlanDetails')} + + } + loading={loadingBillingInfo} + extra={renderExtra()} + > + +
      + {billingInfo?.subscription_type === ISUBSCRIPTION_TYPE.LIFE_TIME_DEAL && renderLtdDetails()} + {billingInfo?.subscription_type === ISUBSCRIPTION_TYPE.TRIAL && renderTrialDetails()} + {billingInfo?.subscription_type === ISUBSCRIPTION_TYPE.FREE && renderFreePlan()} + {billingInfo?.subscription_type === ISUBSCRIPTION_TYPE.PADDLE && renderPaddleSubscriptionInfo()} + {billingInfo?.subscription_type === ISUBSCRIPTION_TYPE.CREDIT && renderCreditSubscriptionInfo()} + {billingInfo?.subscription_type === ISUBSCRIPTION_TYPE.CUSTOM && renderCustomSubscriptionInfo()} +
      + + {shouldShowRedeemButton() && ( + <> + + + + )} + dispatch(toggleUpgradeModal())} + width={1000} + centered + okButtonProps={{ hidden: true }} + cancelButtonProps={{ hidden: true }} + > + {browserTimeZone === 'Asia/Colombo' ? : } + + + setIsMoreSeatsModalVisible(false)} + footer={null} + width={500} + centered + > + + + To continue, you'll need to purchase additional seats. + + + + You currently have {billingInfo?.total_seats} seats available. + + + + Please select the number of additional seats to purchase. + + +
      + * + Seats: + setRedeemCode(e.target.value.toUpperCase())} + count={{ show: true, max: 10 }} + value={redeemCode} + /> + + + + + + + +
      + ); +}; + +export default RedeemCodeDrawer; diff --git a/worklenz-frontend/src/components/admin-center/billing/drawers/upgrade-plans-lkr/upgrade-plans-lkr.css b/worklenz-frontend/src/components/admin-center/billing/drawers/upgrade-plans-lkr/upgrade-plans-lkr.css new file mode 100644 index 00000000..7baac17d --- /dev/null +++ b/worklenz-frontend/src/components/admin-center/billing/drawers/upgrade-plans-lkr/upgrade-plans-lkr.css @@ -0,0 +1,3 @@ +.upgrade-plans .ant-card-head-wrapper { + padding: 16px 0; +} diff --git a/worklenz-frontend/src/components/admin-center/billing/drawers/upgrade-plans-lkr/upgrade-plans-lkr.tsx b/worklenz-frontend/src/components/admin-center/billing/drawers/upgrade-plans-lkr/upgrade-plans-lkr.tsx new file mode 100644 index 00000000..2a0595c9 --- /dev/null +++ b/worklenz-frontend/src/components/admin-center/billing/drawers/upgrade-plans-lkr/upgrade-plans-lkr.tsx @@ -0,0 +1,235 @@ +import { Button, Card, Col, Form, Input, notification, Row, Tag, Typography } from 'antd'; +import React, { useState } from 'react'; +import './upgrade-plans-lkr.css'; +import { CheckCircleFilled } from '@ant-design/icons'; +import { RootState } from '@/app/store'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import { useTranslation } from 'react-i18next'; +import { timeZoneCurrencyMap } from '@/utils/timeZoneCurrencyMap'; +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { toggleUpgradeModal, fetchBillingInfo } from '@features/admin-center/admin-center.slice'; +import { useAuthService } from '@/hooks/useAuth'; +import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service'; +import logger from '@/utils/errorLogger'; +import { setSession } from '@/utils/session-helper'; +import { authApiService } from '@/api/auth/auth.api.service'; +import { setUser } from '@/features/user/userSlice'; + +const UpgradePlansLKR: React.FC = () => { + const dispatch = useAppDispatch(); + const themeMode = useAppSelector((state: RootState) => state.themeReducer.mode); + const [selectedPlan, setSelectedPlan] = useState(2); + const { t } = useTranslation('admin-center/current-bill'); + const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; + const userCurrency = timeZoneCurrencyMap[userTimeZone] || 'USD'; + const [switchingToFreePlan, setSwitchingToFreePlan] = useState(false); + const currentSession = useAuthService().getCurrentSession(); + + const cardStyles = { + title: { + color: themeMode === 'dark' ? '#ffffffd9' : '#000000d9', + fontWeight: 500, + fontSize: '16px', + display: 'flex', + gap: '4px', + justifyContent: 'center', + }, + priceContainer: { + display: 'grid', + gridTemplateColumns: 'auto', + rowGap: '10px', + padding: '20px 30px 0', + }, + featureList: { + display: 'grid', + gridTemplateRows: 'auto auto auto', + gridTemplateColumns: '200px', + rowGap: '7px', + padding: '10px', + justifyItems: 'start', + alignItems: 'start', + }, + checkIcon: { color: '#52c41a' }, + }; + + const handlePlanSelect = (planIndex: number) => { + setSelectedPlan(planIndex); + }; + + const handleSeatsChange = (values: { seats: number }) => { + if (values.seats <= 15) { + setSelectedPlan(2); + } else if (values.seats > 15 && values.seats <= 200) { + setSelectedPlan(3); + } else if (values.seats > 200) { + setSelectedPlan(4); + } + }; + + const isSelected = (planIndex: number) => + selectedPlan === planIndex ? { border: '2px solid #1890ff' } : {}; + + const handleSubmit = () => { + notification.success({ + message: t('submitSuccess'), + description: t('submitSuccessDescription'), + placement: 'topRight', + }); + dispatch(toggleUpgradeModal()); + }; + + const renderFeature = (text: string) => ( +
      + +   + {text} +
      + ); + + const renderPlanCard = ( + planIndex: number, + title: string, + price: string | number, + subtitle: string, + users: string, + features: string[], + tag?: string + ) => ( +
      + + {title} + {tag && {tag}} + + } + onClick={() => handlePlanSelect(planIndex)} + > +
      + + {userCurrency} {price} + + {subtitle} + {users} +
      + +
      + {features.map((feature, index) => renderFeature(t(feature)))} +
      +
      + + ); + + const switchToFreePlan = async () => { + const teamId = currentSession?.team_id; + if (!teamId) return; + + try { + setSwitchingToFreePlan(true); + const res = await adminCenterApiService.switchToFreePlan(teamId); + if (res.done) { + dispatch(fetchBillingInfo()); + dispatch(toggleUpgradeModal()); + const authorizeResponse = await authApiService.verify(); + if (authorizeResponse.authenticated) { + setSession(authorizeResponse.user); + dispatch(setUser(authorizeResponse.user)); + window.location.href = '/worklenz/admin-center/billing'; + } + } + } catch (error) { + logger.error('Error switching to free plan', error); + } finally { + setSwitchingToFreePlan(false); + } + }; + + return ( +
      + {t('modalTitle')} + + {selectedPlan !== 1 && ( + +
      + + + + +
      + )} + + + {renderPlanCard(1, t('freePlan'), 0.0, t('freeSubtitle'), t('freeUsers'), [ + 'freeText01', + 'freeText02', + 'freeText03', + ])} + + {renderPlanCard(2, t('startup'), 4990, t('startupSubtitle'), t('startupUsers'), [ + 'startupText01', + 'startupText02', + 'startupText03', + 'startupText04', + 'startupText05', + ])} + + {renderPlanCard( + 3, + t('business'), + 300, + t('businessSubtitle'), + '16 - 200 users', + ['startupText01', 'startupText02', 'startupText03', 'startupText04', 'startupText05'], + t('tag') + )} + + {renderPlanCard(4, t('enterprise'), 250, t('businessSubtitle'), t('enterpriseUsers'), [ + 'startupText01', + 'startupText02', + 'startupText03', + 'startupText04', + 'startupText05', + ])} + + + {selectedPlan === 1 ? ( + + + + ) : ( +
      + {t('footerTitle')} +
      + + + + + + + + + +
      + )} +
      + ); +}; + +export default UpgradePlansLKR; diff --git a/worklenz-frontend/src/components/admin-center/billing/drawers/upgrade-plans/upgrade-plans.tsx b/worklenz-frontend/src/components/admin-center/billing/drawers/upgrade-plans/upgrade-plans.tsx new file mode 100644 index 00000000..f390896a --- /dev/null +++ b/worklenz-frontend/src/components/admin-center/billing/drawers/upgrade-plans/upgrade-plans.tsx @@ -0,0 +1,523 @@ +import { useEffect, useState } from 'react'; +import { Button, Card, Col, Flex, Form, Row, Select, Tag, Tooltip, Typography, message } from 'antd/es'; +import { useTranslation } from 'react-i18next'; + +import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service'; +import { + IPricingPlans, + IUpgradeSubscriptionPlanResponse, +} from '@/types/admin-center/admin-center.types'; +import logger from '@/utils/errorLogger'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import { IPaddlePlans, SUBSCRIPTION_STATUS } from '@/shared/constants'; +import { CheckCircleFilled, InfoCircleOutlined } from '@ant-design/icons'; +import { useAuthService } from '@/hooks/useAuth'; +import { fetchBillingInfo, toggleUpgradeModal } from '@/features/admin-center/admin-center.slice'; +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { billingApiService } from '@/api/admin-center/billing.api.service'; +import { authApiService } from '@/api/auth/auth.api.service'; +import { setUser } from '@/features/user/userSlice'; +import { setSession } from '@/utils/session-helper'; + +// Extend Window interface to include Paddle +declare global { + interface Window { + Paddle?: { + Environment: { set: (env: string) => void }; + Setup: (config: { vendor: number; eventCallback: (data: any) => void }) => void; + Checkout: { open: (params: any) => void }; + }; + } +} + +declare const Paddle: any; + +const UpgradePlans = () => { + const dispatch = useAppDispatch(); + const { t } = useTranslation('admin-center/current-bill'); + const [plans, setPlans] = useState({}); + const [selectedPlan, setSelectedCard] = useState(IPaddlePlans.ANNUAL); + const [selectedSeatCount, setSelectedSeatCount] = useState(5); + const [seatCountOptions, setSeatCountOptions] = useState([]); + const [switchingToFreePlan, setSwitchingToFreePlan] = useState(false); + + const [switchingToPaddlePlan, setSwitchingToPaddlePlan] = useState(false); + const [form] = Form.useForm(); + const currentSession = useAuthService().getCurrentSession(); + const paddlePlans = IPaddlePlans; + + const { billingInfo } = useAppSelector(state => state.adminCenterReducer); + const themeMode = useAppSelector(state => state.themeReducer.mode); + + const [paddleLoading, setPaddleLoading] = useState(false); + const [paddleError, setPaddleError] = useState(null); + + const populateSeatCountOptions = (currentSeats: number) => { + if (!currentSeats) return []; + + const step = 5; + const maxSeats = 90; + const minValue = Math.min(currentSeats + 1); + const rangeStart = Math.ceil(minValue / step) * step; + const range = Array.from( + { length: Math.floor((maxSeats - rangeStart) / step) + 1 }, + (_, i) => rangeStart + i * step + ); + + return currentSeats < step + ? [...Array.from({ length: rangeStart - minValue }, (_, i) => minValue + i), ...range] + : range; + }; + + const fetchPricingPlans = async () => { + try { + const res = await adminCenterApiService.getPlans(); + if (res.done) { + setPlans(res.body); + } + } catch (error) { + logger.error('Error fetching pricing plans', error); + } + }; + + const switchToFreePlan = async () => { + const teamId = currentSession?.team_id; + if (!teamId) return; + + try { + setSwitchingToFreePlan(true); + const res = await adminCenterApiService.switchToFreePlan(teamId); + if (res.done) { + dispatch(fetchBillingInfo()); + dispatch(toggleUpgradeModal()); + const authorizeResponse = await authApiService.verify(); + if (authorizeResponse.authenticated) { + setSession(authorizeResponse.user); + dispatch(setUser(authorizeResponse.user)); + window.location.href = '/worklenz/admin-center/billing'; + } + } + } catch (error) { + logger.error('Error switching to free plan', error); + } finally { + setSwitchingToFreePlan(false); + } + }; + + const handlePaddleCallback = (data: any) => { + console.log('Paddle event:', data); + + switch (data.event) { + case 'Checkout.Loaded': + setSwitchingToPaddlePlan(false); + setPaddleLoading(false); + break; + case 'Checkout.Complete': + message.success('Subscription updated successfully!'); + setPaddleLoading(true); + setTimeout(() => { + dispatch(fetchBillingInfo()); + dispatch(toggleUpgradeModal()); + setSwitchingToPaddlePlan(false); + setPaddleLoading(false); + }, 10000); + break; + case 'Checkout.Close': + setSwitchingToPaddlePlan(false); + setPaddleLoading(false); + // User closed the checkout without completing + // message.info('Checkout was closed without completing the subscription'); + break; + case 'Checkout.Error': + setSwitchingToPaddlePlan(false); + setPaddleLoading(false); + setPaddleError(data.error?.message || 'An error occurred during checkout'); + message.error('Error during checkout: ' + (data.error?.message || 'Unknown error')); + logger.error('Paddle checkout error', data.error); + break; + default: + // Handle other events if needed + break; + } + }; + + const initializePaddle = (data: IUpgradeSubscriptionPlanResponse) => { + setPaddleLoading(true); + setPaddleError(null); + + // Check if Paddle is already loaded + if (window.Paddle) { + configurePaddle(data); + return; + } + + const script = document.createElement('script'); + script.src = 'https://cdn.paddle.com/paddle/paddle.js'; + script.type = 'text/javascript'; + script.async = true; + + script.onload = () => { + configurePaddle(data); + }; + + script.onerror = () => { + setPaddleLoading(false); + setPaddleError('Failed to load Paddle checkout'); + message.error('Failed to load payment processor'); + logger.error('Failed to load Paddle script'); + }; + + document.getElementsByTagName('head')[0].appendChild(script); + }; + + const configurePaddle = (data: IUpgradeSubscriptionPlanResponse) => { + try { + if (data.sandbox) Paddle.Environment.set('sandbox'); + Paddle.Setup({ + vendor: parseInt(data.vendor_id), + eventCallback: (eventData: any) => { + void handlePaddleCallback(eventData); + }, + }); + Paddle.Checkout.open(data.params); + } catch (error) { + setPaddleLoading(false); + setPaddleError('Failed to initialize checkout'); + message.error('Failed to initialize checkout'); + logger.error('Error initializing Paddle', error); + } + }; + + const upgradeToPaddlePlan = async (planId: string) => { + try { + setSwitchingToPaddlePlan(true); + setPaddleLoading(true); + setPaddleError(null); + + if (billingInfo?.trial_in_progress && billingInfo.status === SUBSCRIPTION_STATUS.TRIALING) { + const res = await billingApiService.upgradeToPaidPlan(planId, selectedSeatCount); + if (res.done) { + initializePaddle(res.body); + } else { + setSwitchingToPaddlePlan(false); + setPaddleLoading(false); + setPaddleError('Failed to prepare checkout'); + message.error('Failed to prepare checkout'); + } + } else if (billingInfo?.status === SUBSCRIPTION_STATUS.ACTIVE) { + // For existing subscriptions, use changePlan endpoint + const res = await adminCenterApiService.changePlan(planId); + if (res.done) { + message.success('Subscription plan changed successfully!'); + dispatch(fetchBillingInfo()); + dispatch(toggleUpgradeModal()); + setSwitchingToPaddlePlan(false); + setPaddleLoading(false); + } else { + setSwitchingToPaddlePlan(false); + setPaddleLoading(false); + setPaddleError('Failed to change plan'); + message.error('Failed to change subscription plan'); + } + } + } catch (error) { + setSwitchingToPaddlePlan(false); + setPaddleLoading(false); + setPaddleError('Error upgrading to paid plan'); + message.error('Failed to upgrade to paid plan'); + logger.error('Error upgrading to paddle plan', error); + } + }; + + const continueWithPaddlePlan = async () => { + if (selectedPlan && selectedSeatCount.toString() === '100+') { + message.info('Please contact sales for custom pricing on large teams'); + return; + } + + try { + setSwitchingToPaddlePlan(true); + setPaddleError(null); + let planId: string | null = null; + + if (selectedPlan === paddlePlans.ANNUAL && plans.annual_plan_id) { + planId = plans.annual_plan_id; + } else if (selectedPlan === paddlePlans.MONTHLY && plans.monthly_plan_id) { + planId = plans.monthly_plan_id; + } + + if (planId) { + upgradeToPaddlePlan(planId); + } else { + setSwitchingToPaddlePlan(false); + setPaddleError('Invalid plan selected'); + message.error('Invalid plan selected'); + } + } catch (error) { + setSwitchingToPaddlePlan(false); + setPaddleError('Error processing request'); + message.error('Error processing request'); + logger.error('Error upgrading to paddle plan', error); + } + }; + + const isSelected = (cardIndex: IPaddlePlans) => + selectedPlan === cardIndex ? { border: '2px solid #1890ff' } : {}; + + + const cardStyles = { + title: { + color: themeMode === 'dark' ? '#ffffffd9' : '#000000d9', + fontWeight: 500, + fontSize: '16px', + display: 'flex', + gap: '4px', + justifyContent: 'center', + }, + priceContainer: { + display: 'grid', + gridTemplateColumns: 'auto', + rowGap: '10px', + padding: '20px 20px 0', + }, + featureList: { + display: 'grid', + gridTemplateRows: 'auto auto auto', + gridTemplateColumns: '200px', + rowGap: '7px', + padding: '10px', + justifyItems: 'start', + alignItems: 'start', + }, + checkIcon: { color: '#52c41a' }, + }; + + const calculateAnnualTotal = (price: string | undefined) => { + if (!price) return; + return (12 * parseFloat(price) * selectedSeatCount).toFixed(2); + }; + + const calculateMonthlyTotal = (price: string | undefined) => { + if (!price) return; + return (parseFloat(price) * selectedSeatCount).toFixed(2); + }; + + useEffect(() => { + fetchPricingPlans(); + if (billingInfo?.total_used) { + setSeatCountOptions(populateSeatCountOptions(billingInfo.total_used)); + form.setFieldsValue({ seatCount: selectedSeatCount }); + } + }, [billingInfo]); + + const renderFeature = (text: string) => ( +
      + +  {text} +
      + ); + + useEffect(() => { + // Cleanup Paddle script when component unmounts + return () => { + const paddleScript = document.querySelector('script[src*="paddle.js"]'); + if (paddleScript) { + paddleScript.remove(); + } + }; + }, []); + + return ( +
      + + + {billingInfo?.status === SUBSCRIPTION_STATUS.TRIALING + ? t('selectPlan') + : t('changeSubscriptionPlan')} + + + + +
      + + + + +
      + + + + + + + { + const input = e.target as HTMLInputElement; // Type assertion to access 'value' + input.value = input.value.replace(/[^0-9]/g, ''); // Restrict non-numeric input + }} + /> + + + + + + + Company Details + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Configuration; diff --git a/worklenz-frontend/src/components/admin-center/overview/organization-admins-table/organization-admins-table.tsx b/worklenz-frontend/src/components/admin-center/overview/organization-admins-table/organization-admins-table.tsx new file mode 100644 index 00000000..4e757d28 --- /dev/null +++ b/worklenz-frontend/src/components/admin-center/overview/organization-admins-table/organization-admins-table.tsx @@ -0,0 +1,60 @@ +import { Table, TableProps, Typography } from 'antd'; +import React, { useMemo } from 'react'; +import { IOrganizationAdmin } from '@/types/admin-center/admin-center.types'; + +interface OrganizationAdminsTableProps { + organizationAdmins: IOrganizationAdmin[] | null; + loading: boolean; + themeMode: string; +} + +const { Text } = Typography; + +const OrganizationAdminsTable: React.FC = ({ + organizationAdmins, + loading, + themeMode, +}) => { + const columns = useMemo['columns']>( + () => [ + { + title: Name, + dataIndex: 'name', + key: 'name', + render: (text, record) => ( +
      + + {text} + {record.is_owner && (Owner)} + +
      + ), + }, + { + title: Email, + dataIndex: 'email', + key: 'email', + render: text => {text}, + }, + ], + [] + ); + + return ( + + className="organization-admins-table" + columns={columns} + dataSource={organizationAdmins || []} + loading={loading} + showHeader={false} + pagination={{ + size: 'small', + pageSize: 10, + hideOnSinglePage: true, + }} + rowKey="email" + /> + ); +}; + +export default OrganizationAdminsTable; diff --git a/worklenz-frontend/src/components/admin-center/overview/organization-name/organization-name.tsx b/worklenz-frontend/src/components/admin-center/overview/organization-name/organization-name.tsx new file mode 100644 index 00000000..0a4a6b46 --- /dev/null +++ b/worklenz-frontend/src/components/admin-center/overview/organization-name/organization-name.tsx @@ -0,0 +1,122 @@ +import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service'; +import logger from '@/utils/errorLogger'; +import { EnterOutlined, EditOutlined } from '@ant-design/icons'; +import { Card, Button, Tooltip, Typography } from 'antd'; +import TextArea from 'antd/es/input/TextArea'; +import Paragraph from 'antd/es/typography/Paragraph'; +import { TFunction } from 'i18next'; +import { useState, useEffect } from 'react'; + +interface OrganizationNameProps { + themeMode: string; + name: string; + t: TFunction; + refetch: () => void; +} + +const OrganizationName = ({ themeMode, name, t, refetch }: OrganizationNameProps) => { + const [isEditable, setIsEditable] = useState(false); + const [newName, setNewName] = useState(name); + + useEffect(() => { + setNewName(name); + }, [name]); + + const handleBlur = () => { + if (newName.trim() === '') { + setNewName(name); + setIsEditable(false); + return; + } + if (newName !== name) { + updateOrganizationName(); + } + setIsEditable(false); + }; + + const handleNameChange = (e: React.ChangeEvent) => { + setNewName(e.target.value); + }; + + const updateOrganizationName = async () => { + try { + const trimmedName = newName.trim(); + const res = await adminCenterApiService.updateOrganizationName({ name: trimmedName }); + if (res.done) { + refetch(); + } + } catch (error) { + logger.error('Error updating organization name', error); + setNewName(name); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Escape') { + setNewName(name); + setIsEditable(false); + } + }; + + return ( + + + {t('name')} + +
      +
      + {isEditable ? ( +
      + ",v.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",v.option=!!ce.lastChild;var ge={thead:[1,"
      ","
      "],col:[2,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],_default:[0,"",""]};function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),v.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
      ",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(v.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return B(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=_e(v.pixelPosition,function(e,t){if(t)return t=Be(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return B(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 this value - triggerChar: '@', // @keyword-to-mention - - /** - * Function for mention data processing - * @param mode - * @param keyword - * @param onDataRequestCompleteCallback - */ - onDataRequest: function (mode, keyword, onDataRequestCompleteCallback) { - - }, - - /** - * Addition keyboard event handle for old and new input - * Why we need this: - * • Because some original js was binded to textarea, we need to bind it to contenteditable too. - * • Useful when you wanna passing some event trigger of old element to new editable content - * • Old input element already be trigger some event, then you need to pass some needed event to new editable element - * @param event - * @param oldInputEle - * @param newEditableEle - */ - onKeyPress: function (event, oldInputEle, newEditableEle) { - oldInputEle.trigger(event); - }, - onKeyUp: function (event, oldInputEle, newEditableEle) { - oldInputEle.trigger(event); - }, - onBlur: function (event, oldInputEle, newEditableEle) { - oldInputEle.trigger(event); - }, - onPaste: function (event, oldInputEle, newEditableEle) { - oldInputEle.trigger(event); - }, - onInput: function (oldInputEle, newEditableEle) { - - }, - - // adjust popover relative position with its parent. - popoverOffset: { - x: -30, - y: 0 - }, - - templates: { - container: '
      ', - content: '
      ', - popover: '
      ', - list: '
        ', - listItem: '

        FJhX+}wH5K&qVdvFg_yG1qku-c095U;oKX})L2H2}xm8BM8>S%O4`jA?`L$j*h%p9ymA%`cYR%uhqKr(4NN^ADe30oeI#Qp*FS6U z)pgJK7dr1(5Se;mi@7dB^0dX>BSw<-6AS8Uu_jP0IH`VhK!#8JsBeCbjm`p%{fOcL z)+M^a?M+#E0Ud_ZkRtFy_)0Dr7wWeYX8?crOWAC{tkm~Fy&h;keqE5Gnye*Jwp%53 zwMUh|`Zbih7ygB}2R;R|#?Bv~eCgIkC~Qg)LRS7;j+Iltuzy-+dQc2lMP>cGVfUcdEkqUwl`Jjw&#yZg8Ya5MQm|Y-CO8pv#TiNfr!`^?yKOCLK!0&N_&6DG@+tt3y+XKeY3pD z&1~zwZQK&38{R%Q&y5D_k#q-GThCT6?MmyaR(NO}Wh?Jz{$8|FYh7RMxsw3J0}R>0 zDq;JG5p~;b6=V48#(X7{|Bf-*^ptSOb|N?J`DzCZJEUAKoFgzN*87& zj6a1t8_qf9U#_q!%N4ZHE_!Kc*l{1}%c6WHF(6*YhaB1vsrTKNd9UHFAoz_El9gxC z)24dsKNzQAJY9?H{_Ph8Ino`FN9zzT`*~P<=K8{qoeC7Nl8iDyOS!wP^m@tRq^iqo-`Bpl;Lp&ZLZy) z210-YdsMw4UjSj*Q~5P;SiIDq>f80L;v^OCnKqn>o2p~D&v(Gf?AGQyENpTjO>P*Fl9b8GmEmZaPYfCs1VRvy7Hz1Iho>E60M8>^!Vs_S2PYl>6cG!M` zOSUrbbkEFZ6Ao>%)i3S8>3J^fkHU^C;O(qELsojB=XQ`eLe{XUSM5~X{oh}{ibLl~ z@lm!xrk-*=N?1Iu~)goF3_cnzYvss(>J~m{F(|e;$X#LC!%nLMk!B8avD)q)N zKln#Kp-02NTz)Pav(U%&gJvpb;O3dgQ0rG`3C92HDSb;qvg~c@ig)t7Jw2YbpMs$v znKgR5E52NdmgGN6fBS8lIp8Lkpww3S5pf?Dzl1#L#k}T8o#_o#y#am^YAD4#k$xGVE#Q{-1QAV9!L3G_+ao$SlHlK&(wdqY3!dKI zhjkOpAo&eRc-;m!N??CZ4N_H{4XH@IZqIdJR(T2ub*B9crBb6TKnD_$7iuu_SqnqF z%#o&5ZGKO8(RN6-G0+2%y2(YbH=+er)C@j$#T-2wyqYANxz+)1PAJdPK(TD|p@u(l zIdyX5I`_c*@xb$$<4|0W& z00pQnAytBAlG+;RZ;i^s;sr-KEW^U(9gyTh_OR2U^M4RQUFyilIlu)RwX(MC)pd4} zpBnrcGx?IYm2&)H8ra+9)4&*lvx_f0TsG9jc&5GqO5>!Uo9ov@*dc()v zA@1p84V05wD-0;pSMPy*QIGmPGDoQ`HQN1%evh2X4q-qm*Dez|P<-2gdwD9>ITdg+JvFeslkhGEkH3(xkEN zr0P9Wxkwb^#&G5qk&mONAUN?M| zlheUiVP89E zEvqI^uUQseA~~|;P&{Um1(t}u1LZ`CM^m7){iy$<#AVHvcvz^eJsF zRa>-827RHeki}idjw8f7ksvt(?xtmSyxSpkgRWj7h(0IL06SAckz7_$g4e`wu2}wD zBB=h2JldQF0jYY_mysQ~0?M`SV8ddCOXvvllL&&NgN3%w#~Xic-A08ZW!yiOO>XmO z6c;j*Rgn;kW(T?EAsEGb%Z7$$#u;kh3rR7*2yiQ&q8LtY-)A!{$lZiPG)rwfI!;_M zo)UG$4L=Hodi<`=cfnv<_Y^lp_3(4+)mZNJMPXARn^bV2Fyd0O}fJbhb2b8t|MsiQ=>({T$+QX%% z0uPoZ`D?_jz}V@9c>j*lQvKU*2hhv-fKKKM{GD(H07F>lm0S?}IWIO?N;KUVDY_GHG%uL7NU;I0XSQnQ z4NMiou({t;OOCp#)FF+bhRHKq7(XFUY}%H;x9Q~Wa^~%wu%NW+IRj{HD^TCzaE4ki*{{HJjhrbLt#YcQas! zOdSHpGRUdNZ0Duwd@%T1LABDGKb)cqS`a5?UWya?J%d`cvfI!Ay8~&wRFlLTFCZp* zToXXoqm62)aJIi}t0&PM4!~&KTpZ*%- z#)q%zo7j-GMSDCn>CFyIeS64yRTncoQvjA=f+MP;_L`?h$V^Xx{`!o zYd2&bapzk{U!C4=5v~e-%CzD4%kpAnsRb-sJt;at@9|2mpMOhC9s^H-x1Qd_XHeY<2bae%2bYE2FA9@Z}5L zOj&^sIrPrvjJ&I>>kg?;I5dfjS@&9C&XawHO-RG>ddu_{IXu;djOh+4T z(`QwdrGBvj=+%8mikvLnjr(8DiuWg!G9gss236$!$6nQArl8;mDy5-I>r_DTKh2g=(X6PE9@$7%!^IZheTwM4Zlh@44Z2y=B5 zITt568lE3Lpl$*99%&gFO|bBpYVrjN&ANa;>GK0HAOu$vmy}E|S)Rh-aLZ|FX}$#a zO0QbIRv;z zVpW;ewTC`o833A2Bb07B9T9_`z8oqo6@&5pcmAW|Kn+?#O*I}`THoa$zPIGIqIvNn zTJ~5i5ss<932(XwpKnFr!ywuj0TjUA?X1jtGL;#xZ5-r$_%4V|NEF{@6=I1`T=+b)%m)5p2kET-Wk%eSm}&nUDI)&(hg3hE{Tkd zJWZi=f7)qb3U0I`k!V2=GkYEw1y9_WAxgduUhFHsAxxC|R`cM83Pb3* zpmds?ndLVK&r;f!IN{Ycj@7S{g6=)&K>MG<8E*HJNt!hG%FI9o3?zRFrQ6{->|~lW zFr2|)(-NAg5T}vGw@4!?Kx;@fVn&rc{?0kE@f(@SQ;MkrpA#S&+k)@FJ12f#=f5!N z30H_-)w?9f&CYZ2huCrd(z0FCjj-R+5Bw=OzJC(0ru9W^eCEMidnED5l?vmsKn_e3 z_25UpDs5SN$tT(24VMVGvPn=~OMY3xGdn(al~4wEt`}=RAX85UM9Zi(T(IcOh~7?1 z_5*V@DTl;0nHB59kaw>t#)S#~M4=nx3LFhQETR}uqJO#9COiIK2u~y0CJ>4r2ePgG zXJiFrAiDKDm-@PJ4Z;6_8gZjcuHcUXNxib@%0&y_0;h7u)H(M%`VY1{x-0HzU5wHY z|F;~5Xhc8nJqOOqS>QP61~0Zj3ZfY?1c4xkV4H$P+wurgoen6qcHWW;f(v65`H*l( zD7RI?RGh`LmI@_&$hJXwY7-=shU5-^Z_oXE?F__u*Yg+`0s;%#QxAT9`Gh3NoGCOE zxC?1yZTeS>2!p#)$Y!7;txblm_rh--Q&G0tES&}6wd?}(wdS{=E#AicSa!F%LU5J= z%eogx8M>tZsLI!~AiPAd^{d9lz0iy{zqB^L7)zzps(0ASCKG|W&p!=r3bNUu3#c{{%@V=|;;AKG`Mv!z1 z`R4eZ5Q8ZhCjT8M;FD}G@U)7|z%6U*r4BkCzr=;;t3nU>=IbOHH$R{V67i;=MkYw~ z1VaGFw+g|Ac|OEbzgHQx%`1#+{w9kZpN56#GY)l+N}Q~nlCOvB;Skc-gVvp` zMNlwZDi*B{IpK@0`j;&d8PNfqm0iQ13n0yBsD&2ub3FY1CqJf)z)zyJ2FP0t;$?SV zzq@==kW-o?_8e-CCJWq}>NR|DP!Uf6`8rNB+66Oy6QBMaU7wTm@6as7j>Y*kcsr7q{D0ARhk2f#H2*3=J{4 ztjqEpGdl(WyqP)9&Q26#;cWCL*(eDYLaJ=1=$=H1n4D{g`-2n8a*ZPeJo@sq55;8n zhV69%o@9=-$!}*=iPD@6?l2&bg+l3}fk+4MksOfb>wLm!g$AJ+BAJ-QA4^!i?p_RL z>sq^F^kLY38X`j zb$~8TZ(WJ3g+Hez>+efy+b&J5@MMqQ7av{WgJnNvwR#rDyr+r^%VD3iCAJc{x?O?h zR*#d?o|5}C;{2X~x5IRS{?+SP^x!PCi%_>;NRYKC;)iL2xl4{%Km~wBDx$UmL{ra| z53SCkOXGvQK7fKxnW6mEA5kJXF8{x4LGPFt7|x`cq`y?_6*H08DQ1x=yR^7yo^esp zv}{ZgMDfhE-Ut7!lrLk!cfHVzHwWj;-9y~&^}6AC1e_Y>Dchq-x->gsvEcX_9r#Gr zNHVxPIqMZ6SuMOuY{+oCdrM1e&YikFkBm<%V^v!wQudaf$`hP`ILcs4jCJ&Y*x=TD zYrByvfMNQHoPk&rT7p@TTwJ%Z_-hK3zY7|1{WZSX9LtRHBDeDhlUR^Xl~reO*y@Rh z%Yzlko1qV}mwGkI6^*oht>*T?N#U>{$Yd_?3jg(DZ$3Sh`px-zyd{CM=7^^`#USgV zMc%V}uL|~JK?AFs=>4@qs=<1=?E(&$QCPjHcPE$OHm%Blqz?1{y(`^0i_@R9JW7TWJJgp*? zhq%(nQ<|15k0eGc5^?d!}1-4;3^g-U@G}xk`n%&ZlO><+wC1YV>;m37DyIxISMtc~gRuMHN z&z(1a?sIxtJJw-i_uquj-b1r%QYp}!tE9$Jjvo*hpBjkMGY)N!8iy!8bPIdC@P0&b z2|L{@se3luq*$T)&AOo^w<+0ul|HFg@@;RjJ%iXW7M7MTA4U0q=Z0@6mfk@f?9!*S z{(7*$!gJzn^gi=;%P~*#H#5io(I>higp$A$GUSW6@R0I+S7pXXP7J2IVf_=_`*M(( zmggci+0CvO{^*1imKsJ0V+W}!N6j#+Uw7lTaYyv9un194Mb!%wG{h^k z18rh&5-R>Q&Ny5V9PwSFR7^ILkMpjq7zLGojir(s_DmqzPJ#O1X&u5i# zhUtiE+dl5OAuivk;M1AOzO~=^nZuT%vHfE^ zYEb6?!1oTnJEvdTN_GU>p!t?$od}Qan>>rCwl5v}1juKPTito1Ajjsy7Z2pseIyD5P& z_%07QP5>qDlE!me{NHOI_BN4y=>!QE1vHl}lCxv*a|i~_{kdW5-GkamAf+l6N+UgM z9E7M-oNxkv06ZQEkYgBFv+=(4#68S?MPfLLWla$81=1>K1nZY)cY#|mXZT^EK1C4D-(c(3}Z)4R=J$O7`&x`9*r|WFwwz1x(O^;@N zctV9PG?}R{?|~E<@NTm=QU~$V<%r6}dh|S|AVenfC+I1Dbyy;;lg*Mz_DJX|t%%7D zv0(-({3X^rz?*>Yw&0y0L0XJLY zqAW#d{LnjR1A4hC6R)*PWsE=1&hWh$R}as?<6V`f=8covI*pis9iOSA_&%50$oK1| zk=H%XXeHy6sND7IK>wK?jtU%|#)*LoZFCOx)>HjXqIDt-BIEnj@?|sMP#-X8kz>0Y zDzy%@OYcE5D~h$jDMxl)rom>ZrcyQ>oe!DS_d4YLHhyq4G>WH+vFSe5ge4DbUU`qh~2#1oc{Nc{K+Hymr9Sp_xLt}LcT3|Xd2%x4>l4LrVp zXwHe#^ZEeYb&R~x1>RAEj~PG;{FU-7eWyY$g!Cp2e!LL#YHpHxD!Ib=>$k=?dc%}N zZK7S|df`l-%tNv)%zi$S(DNl0JEOF$3a)|=5p~vhD*e0wTHZv0ztNX@29!_~KT@N< z>9w}NqtZ%f6;j^%5YrtaI5HXF$e>c?Xn2(2)Bl-cUG*#^UP!-oW|^^e@@}Ugf7#68 zSaESN8%Q~qVq#)4-rfEY%n}Xy%}R8ah?!V;blRBj^$*Gxw^E*&#Yhk(g1jA8x0o&M zV(W&Q_Q|4K^{Ml^xae$qSgFC%^sf^uH_4sa)8K@`F+=w-@(MxI;s@VNgj=xJT-(yTFdaKN6XY;z9ilmpm=+)^uUmaL#>ZNFIFnmnJf4W^PL zr#?l6cX@86yE&#Yazg7pI57=2x}z*aA-6OSi*iTbpmu0@%HpQe7SLyxG9l-O*EdfAy8LO@C z!8~Q3L7&wL*~P_0cJl!D)$hWAqr3mbyt=x${26Te{ID+A)878|D`yLdTg8IuOoJ@% z?rdM7ACtn4G^8$^+zh%eOzS@dLJyc`$oX=^c%yowog~h$9YoYr?>u%pE+DxIS6IDZ zXpWt#b_2i;E z83y$CE<54bO{)ANjbi7t*Wy8O6-TMWn*yMrpD2mSxKzxY^Jx_yV+QnjdhpGBaaJj) z4qM_h!NpWW2#Bn#d?9;OAcbBQ9NS_2fE;!3_wd_dTI&b8l}=mgKXvdl!7?a6^$BE> zS^o=?D+sbDnwj&WEL;w3NAfH?E_tm(*MRp5P>`8-?wrVcyP&&Va<%Tstd6cOCoiw? zQ{2%}OVq5~U^KI4%?~FB2L~50mC+{h;y^ee@Hc2cs3qUbk?7?_INfgF-Rx(UfrSZ` zgU&_0Gbc4*ttjSs@s=c+)SDjQQ2Sg;uaL@M&Sc*qo9^|5u$S1`TR(fjn;j%Nu8e6TA;jn&AA4g~;CHd?_Oxvw>Eh^hblku4F5DqjF+{ZOKH7`CJ>Dknc@N!& z0gBI29=u+w!wCB)wBkXQ$aukS(_T5(BgKt%LNJ%8dTNDe1l8g6dhcRfpmw=_y`A!& z2hSt``m4gQD$cLjE1sz%DOWSS!Y*5C@%AgNY;HaGk-7NpTD49@7FzF&*$U$ z7^oQQz`|Qt4N9x)t~)D)mRar=cpUS07J6H5&NjIrAtbxxJIa(;wa!76*) z%-Nixn^%m^WfXKsz)Y|BN>8`UlVb`hxsSfD&I@AY9w`J}z9JKC$2*%B@5x^tEFEKx zTf+CI4$sNu2uuW#08)yb=wsV3gIKY_2uj#!>MloSymA-y2{ZAWJTevGm6F8r?JCg} zfXMVE|Do;wQiEVgUYD4s90FS=rW7 zs7Usq+^4~cSOPq&0VaTd-~Z&dxMg|O{b)ZD2hE+nP7lQ4Wg>TtyFr)%jMIQeJ7Nn2 z>F|=A!I2U|zp}uH3ujl=r#rTgCb=H`$%X2YALPvv@b{L#N%9!#y)hm)W7u&}?wJyp z(WO#LzfipQ(ctd(>0j`-1Nx$|`&P(mHDyK;|B$1J&;*X0-+s1z|GpaWTn^7GiwK8% zTRo$G{7kH!=EC41mjc*3bSwGO%`U5}UmduSB-6a9`@?0{hZ1lRMZb*M|8-^a=S7^| zOSZW+#_r9X?Pn^J!h`0#CZ4Op|RkspFG|<-L5bZQ2z(|;Hpzt6nNqMYOgN_aN{24{j z_y(to=g<@BBYJcF9vPQuz%Ks=Mr5qWGnQFxLO8Ck%0mDBk`iFxTnbBP{eIQ|Qo(_C z%Aku2g)#J;{DcV}^K79Z&4+qdlVHV{YMqX1p++;Wdjrumfa1+FAVm~s{ z_ndHzkIvI}K9Y*cPdZ{wKm3Hz>bpJv8_hPL3#l?2=U072{ylR;0>b^O5$Jp@>h0aa ztam6|9#nHy>>p&`Z)^+LoN4~Y(EAaP{=a8sF@je)Zf@Qo`2+R=S## zpe1vOZ=x*LfU@M4jEz(IgP3nCJTFhxb6fBI&P#l%t~bTK-o1@$?ppw<*ZU5>5qJDz zP;*Z?Sf0m#V8g_xp7!}*?rQEN1^kM(|yn@!>`@;UbNZm3FW>b;|WR-4tM#?NA z&}#eD7fmQBZrcT32P!=Gb)097`hg^>PcBPw+;?LE{ut4onDy5H&9_Nm;*h0R-_zh$Wg~Px#o1HMchB3YJtSIu-%=nx`S&r ze+8{77vq3$odDjp|Md55OIHK@;PXtVL`&D3cv(Sz8=XcxZ2r44VfPL`4 zs&8P`eNUP4#2xSwUcK5)xQXi~S#%P^-Ay;2?Pau$-Ff7dF7hrt)OMxW?`=|2yp~cN zlJ)9)Pl9l*ivs9QIK@dMNZc{@z9NMW2s|~anp%HrlM8~QD3lU?7@q0W>iuU9|Ui~(x(k})>S3F#3*w1LWPpFReXz>e=I6&{UO!A1pO1vX5Q(f{UagEo0C z&@fb1-G?(sK}m)e{>qZ$9<(n7g+Al=^JWdn=%s4solJ~a5q;M zshZF~rUVPiZhZkS?S=v`%fRL1aelF^hW7xuJS@WVZ^1pBfJwXQH?;d1=x}xT^FE#dPp{{wdu`2u}(*_-GgNP;vDl+f#QVg zkoJ#p_NL?$ALrehqS1OOs}Q37|4!P6r+qYvIYh=+BwN!D(mfbv_!6AltUD!4I9X3p z5JwT+JkWjHyPDAk>CMC${jI4K0MkxSs_9D;Z+`FCV{UA@ z<@hdao6W+)!okkYPI;@-;T{+f$yO=vR2{J}N5n@f-JyFBL~H|6BeNBbs! z|G@7g=Syw<+@Lg32`OJP01QXWSlAJ10d$7%*tAu#CXrlKT6Gm0N6`_rn($e{u^`7) zvM;fTzMPPKXH~zE`V|n3Yd(uwR`^sE8_&C?%L&=bz5>D(**7|29D*-KtOhvRyEB9K zx2~{GK{bPQz#>^;o0YJL@yD-X;+rqB_dr>sc+AL20k~bTL&p7@eQL!1R@uF;F4~khU+8;{(T=CW<*U3JIq8RLm=f? z+YsOL?eO`%T^}ALeVEN7i3CiRM^^&GvQ?+ijyT!g|_n1q)^1;(Wr z@xv{E3jX-metk#d3)}?hP42xFNjF6#L4 zTGAbvPbs3kmaUg-?FJEV-@R?x0sfJs2gTu8_5pN<;>4SxmZq+a_t5e8HVdRHWbecr z#~iq*ukf4ku*DQX&(p+a#t(r$`++=aV|@wdKw6}>^s{pr2FCit@epekac?tEadX~$ z$0MAs)l4US(XQ#o27EF|x3r6MQLPfu>KR~(Yh<(V;8*$|9Q%>0n(D$n&|*2l-C?5Mjb(A31G9N3x))5g3`b`Y=^ z=$z$VKM$Thm*sDfc|uE_->dNu=$%q4!-RlnKwxR zhk;l(H6lNp7kwRIKojai7@hCiPNz?1r0;2!+f4=2gRWQLEyLT zU7yW9RX26A4jO{&Ep{+1-YS#?4L9^wtE(LYA(81=rszKw;>yevi^ zwPqMyv3i@Xq`~rLTuS*JefJ@ zJF52^Exxmz)KNj>O%qbK_sEHy3`||C6*%^qk3K>dd&dNfT|6E1e|H_?U-O7Yt5$2d zHr+)rlUpM&avn*;H5l}Ksx>%L4D!sfwf5lx58)I?`rE#oO&V?Y9_X%9eQ;$DB-Y~e z!AqElaa0#+*(V#evqWQ)SQdfIp>#8wViJ9XRC(CsFHV4K4$o>>tgH% zG8+)sm>>UID^U6NNexzpsPTS%(Nh4__(0*2lf6YY#08+3U-0(%%=IGg4=%V$TS6@`nnbVp}guxqfjJR0c zOi)n3bp%Y+?^QIdv743HOKpg@o2Qn9Pja~o+d$$LVB9{czmV=|ShsYwxnq_*#txJE zv}HL4Zn@M&{@}d}1;`3QzFl|uXGaoAeuD7n5-4a+>#U1R=^QPWrEyGc+Ap@3$>LWe zJ)wkeKW0xC0rTa&5z1-U9UVFLpy`LYgyp~rIWK7>T}KjPUMDN=`K|Z;e}A9aww|AV ze9?aPvI+a_7B$BLOBPQHJTDcWxuTlt!Mgq?f)1X^mh8qji~sHEsNyTqpx+)7Zq2JL z$SdVY-9B2Rw(h<>dYd%UrJgfg{e?s9p2I;&7liR+5k(9DeG_YkvjX%m9o1|A=;;)V zZ(seQZI1l|C0;{PzPxyE{{DavJyCD2%pcD6hl5-4pm3|AwAnO|5N%yH{;CM% z%sjY%e`P>)cxAVd1px1DYEj4`k5ewdijY%^A?xTqWt@t~jZ?=2dKEuYl%fJ*;qgKj zSVo8vwyn>~@qf443*S0y&w^d_3CE~GvpP5_BI)5y6ZsCohK^28vmGoj{z37SkH+i? zmk>vlfkyudNF$Rrzt)3thdPqFIuU*#yD92W7$(aEzbx={b`DQBb8q5}o>eo*pBGWp-E-0Hyl{DBV~#;LVw| z!k^;7m}hY)=`RnuzF7`@$A57AjD<+yDXty89+R}J$qXkRA*YH20)$u4d-sf8BA{lz zR8uZ*4O_GYjT54SfhX`889ouIaqXEmo;+c(6t`o~LH4578W(gN77rByFueth%iES< zC)&9NqbaXXuU@?(01LOY^tbh=NR4&kx{5s1dz zm`4}R0<>AJ2sHP%hfmD86vtl~wA_hSuX%0D$)U%X1|~{okD+7Vw@%Ma!oO%mEsQv; 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 diff --git a/worklenz-frontend/src/assets/images/logo.png b/worklenz-frontend/src/assets/images/logo.png index f5e1ab4224fcafc0354ee268098a9ac1b844563d..28d60ae4ba1683012813adfca0469a24a5813469 100644 GIT binary patch literal 2006 zcmX|CdpHwn7+)sMHR07H5P4UVd3l?=Q;25e!utkd*Ao>Jm2&E^O3zgk0{Ei$pHWWMfam_-jX^m zIhG)x1hZ#HB?!p%Li!?(oJHE%*=Ct6auemAhBzC*7SN;Llo_y^smGcBmRIS#*9__3J{_a`nE@kTNT$?qCLgwi%1>bd02#Ks+d3~R}ds)=(_iG!@iv@TUn6!YyVf*^y2KFju+4B~L#+VigooE~) zA0}g4u;*O9imT0ovnS$Cw^Hr9-^8?hAKQft`4(S=1;ZYqzL)fg;~p`zH-5d0c$^ptz9UWO^Xw|sZ>)AT6FQKswfq^q$l>(u@@2F0}#v^nri-jSwS>;T!3 z(CbrlE3!u%c8>jyJo#{UWSttlV}h%*4npaF+Xas1KSWX|*huH!{CJvkm&bWr^gx97 zw0=S7_#{{F5er|28+qe4W#Cj2Yl{zVKZX<-C^pbA-{NHs|0GXZ0GvrSjjCToAB@ z!Soy{u(Bedi$c95@M{cm&N}z3g}V(p^7Jbxi8s=4?!iitBs{N24G`m$C3%j} zkrcCz!v?aP92e`a%w-km#p2I)+}lz@j~_qb8qZr^=I_s-Y5kadyt4a}pIg}2gmQiK zE2@L*qGn2Yc}+NV)%plq=-LXgx0Cwu=7yn(k!_itOTIqaGN@vp&!nSN=G4dM=yHcK z;f!wpX9q^hkRxKv;#;0$n=;1|PK+88l%VV% ziwitaH)hvOtMDmQ?qy2FB)YJf=$H9zc*QVf?}ZxRmiex`o=%o%ly6S@`i^!w@kL|voxG^d)OJj#%i64~$jM(s%olOeT4P8Y@WU?vZwKU9i>{d)Y29!OH+E9gz z!v7EJ2|#l`NY}U?ZW5-^VB}}mgBC~eiEsmbXRYK@ zZYpb$CRw2oY}KylT^R1#w0)G!G;4-93mix=2$_m#I)IC+=O(WB;BV$0y{nqcyLotvPFiwko*nfRxh5drNIjx`sdjC_H>`~uv#aOOIA;7JaVV(zO&6b8U)ZSf zsi@}~!j*Ivk>J{*I=F)cCK{Tz$Fl$c literal 28062 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 UR7Lym7t|iydwjR@j@8Tm523I%S^xk5 diff --git a/worklenz-frontend/src/assets/images/magnifying-glass-solid.svg b/worklenz-frontend/src/assets/images/magnifying-glass-solid.svg deleted file mode 100644 index 82745f63..00000000 --- a/worklenz-frontend/src/assets/images/magnifying-glass-solid.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - diff --git a/worklenz-frontend/src/assets/images/noimage.png b/worklenz-frontend/src/assets/images/noimage.png deleted file mode 100644 index 60de08e47575a67f3989891c74ac22da63233689..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15076 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 literal 0 HcmV?d00001 diff --git a/worklenz-frontend/src/assets/images/open-icon.svg b/worklenz-frontend/src/assets/images/open-icon.svg deleted file mode 100644 index a2e676a2..00000000 --- a/worklenz-frontend/src/assets/images/open-icon.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - diff --git a/worklenz-frontend/src/assets/images/progress.png b/worklenz-frontend/src/assets/images/progress.png deleted file mode 100644 index 7c91a6331529c16f8959dc54834a81bca4ba8fc3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24159 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 OX2yo57wG4&-upi)4hTg6 diff --git a/worklenz-frontend/src/assets/images/project-management.png b/worklenz-frontend/src/assets/images/project-management.png deleted file mode 100644 index 236def32953ee6c28fcab5463f466bd3ecdb06d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19755 zcmdtK2UJtrw>KI^(2Jt<-b(_~dnfc}0R(B%TLL1z1nD(^BE3or zp(8?s(0k|Yz&YpMH}1XPyYIdKH^v>`9z&DCUVE*%SDEEEe>1_#ic+Lksjq@SAW|9W z7pfr8Md0g2(2dK$N5=0zs)3LD#?q<^Adn|32;~0`1Udlz1EtXFWA6DFE}sd@viP<7Gq{>SgXW3&%vi=5!HjCoR3pnX0Ln~7KeD5xTh}t{0YSg z>h3og8+Bdr+KGWxH|U=n(?G5Z?59{6Yb}q?2~vf`N5UpqOlE%?d9C^g*{g<8&*<*2 z;);s&C*Y`Ww?^*P-Pd;Oj2o9R<&IcDdz6|db#KWlhU1|R^jo|)e+++R)5!T!>A0}^ zmIyz1CA_P%)Ved)8G+A%hg}(UP*gl=Co$Ue8?D2F%~O4+uIW`&g$Mj>(QQwN>`oHR zkqe{#>b9IDo+C+WItrO@PPmI%2-iGW6E}~pw$|Fm$-$!ayqW@^@uj$)`fx8{X9eR8 zR6sA<_I9Rr(9W8T8>!f+}(pQUvYpl*1HvQj&k(NUpV6WYpo}jiruuG%u8r-#A zT@?b<2fcn)xepf#KX7Xz5qaPHGhsN6hmefRRt@&48LKt{)y_l=3GA;8=zX5LJUfah z`(WPdiXi`}@@3X|cl%JN8QGT|;nAVqFNA@9-WE)A%}!krG*BsJ959fB*gUpgzJ=Z! zwcdHx7`66xw#6e$<0zq6>}Xn$;ZG)$($Z)Kv3--xOmvGduS1$hGtykoZP7${0#!1U zSKXs!63S{}&Tk`S5vJJPakcF6wn8c{m7JTiRt(~{VRH3n+xtmk^Cve;uvm7yZrNjs zZ+c6IU^9#4JS)?;mr>5uW)h>HlbxLvw84WsA|kwZ#h&gSVgCf{O^Z7`a7j;U6zdhV)%h!ok;-qc(} zr&JnvtH_n>rIy*R3ThSqkbOI3GF+f)K!4Y=VFe2o*dKQW^HkWo(OnC`Ppay4NAV&3 zd{2D6YCV+@R>I}O?d4X$P|(n7Nx7X>KBYw>H5L|@GRR3)**$X6--36pYPIRol)Uzj zIrd($+?}+R9Oc^CVw9j9*2+`c*j;1br0LJ6uJkO=0C&m3YTQ{rdFfX6iaH}WbX7o< zA0xZ#tlH`}d%%`8ypK(GZ07QkM&mf~fS^PAol9v{`qH*^etIp%eBAjPNEU>5@xyM`?Zs+o?=hqc z2sVY{v0xK06)QQz(>6FQE!}Dfi#@aXxYM<3rxII-G(R2qoL}w*P#Mr-xvYPG!9Jpt zLrqPt$++r{KN-j_T@!msyctv4q(1y%Ap6vdUgg%Y<9p{CSUQ%wzU|v--`!qsd;Nw( zyVbmccfZE$>alQoTDl@5S44(XbQB-~_3Xk`A5|3eM6y)Q*}#LvB;NPwUdCuS3>N~S z1O+@=O_&ba|D^J{Osm^gbtn(D86%vP%cZD5ie(`sDH2@a$5=YJU*-B~!hK!1px)9F zXz;Y9vr0fZj{>LyZ8TG3 zoM5#z;cuBt8H(d4+)|xlvhqJq|7^Kw)?4P5&~DsnmSJvAdJmc#g%{=*CcdU6^j#66 zL`E|&s3fE2zKn%k!E4Uej z`h$D!kMt9vuW(X`z?^zFDt_8AP(fIAUPHDgHIRdJ)mYN!jnn7JJs3{0;3lx5z%shaJEqD=p& z`$bajW+xoN_P3GZfm}22@{D37YkvXc)YuOmWXHn7$jGrwCg!qPxiwBusVoij;L$7* zE_X(TtvdWBuW!GrTKN7W$9l{KTimXu_EK_I8SJ}v93=^4&gOasug!z}`nEBTyYQU?hI@af?io9?Yx%PE zPDxOD-x8+02A^O|@ApUuU*Y$?h0uYqS8_*3u4*$p+`>?)Pn|tPjGTWl!DnW!j*JpA z#!36fJAurcOV#QI8~hI*nT4VU)p78&jJ;VO`~_RpqLAA#T<{&&OfDV`ce)3UfIa#h znMUJsRf~hmhD5(;%O*E}BwEvHga*5*lh9Qv7`OjOpsQOjU`p%L7)2GUy~w7nY63Yt zc68l0r%#)78eaZu+Q<6GD}+W>)!Q@gW15Ka+ymJI5ubk%6~A!y#h-l5W19tLiq8Jv zxA%1i%)nufA5N!f)6XgebUI)cTVC=jcMWQ=qYts>74b5fp1!_@Q+0{24y}ymI!3ed zbg^l#@!iBHsr{ZZF9R|UXejP_?|m}=X`We$AI!}OltDy$ zNN%k0P4*edc+d&eOxjHMc0s%fU>{>*tZtc^jz2Y)G@SBn}gqQs!v6RE%ltB!qy1HODc0?RWT*u06s6m z9=5Rw!+o8)n_2Gan#dESs7!$|lobVk-5DB;<5Y_`hbUNX?acxvq)2DsrBJoLhzw$I z3ZC@)W<7TPlHw6gOpOA(Dr4z_H3iz(cVVDHX&M|d8s+`)ga?t?@h)Zy{kfe$i~SJH z>z{Ud19X{7wO`ZLnPcGy*1YSOT`Fi~jc}<|hh;O)@m2Doc`Xy47LT6%*hDRh*Jt#r zR zkI9(YSHFz1(ZX2z|1oyH_sw#Wmp#X9QM1!U_po*-@C&9{?84#{3ZY)7zq?MQy+?)g zhUKt2IwmnSSbhmlEo5y)}y3dUnh{KJVV)$6+NzQptX3M8CD|1aBpbWG43(^9g?TTJORF(`Ri!c+ zR;+hu2|dzV(qi2lP}+X2uVoa^brsJs6wD6Erav3#hD?i^fCwFpwJ;)WeWDgu3BG-C zJDyVxmVjld9v;kPtlj$jm2C(G8=1YMd*Mb&B90jb#}~3rq}-_UZVq`v+^`;#_S87s z_tIH+>Aej2c@OHeWY4X~_dHrj{9wi3@E>)A_P^2CKPqqxcUSWM`ts0MX2MstdRQ&1 zZQ!06v=pcxl}^f2vIMCboYn|J@`L26^3Pq@J&5hE{u2nks`oYn_<|dS>Rq(>D)#v z-ezVVWP>f~N6tD&&3FAU-!Vh`w6mbt&d#3Sx<-E|UaG$3NqE?JuZC?5G zbm|U>(b~4{@+tkgzPHq|{5la3;8w?HE*}&X|g98;90o+l|19zK5p)5sC?aI>$ zKcRO_pps;q4K+kQ(KW6D>RXLpKA)_p^{tHMzTD^-5eRNMBctJP|Lq;nE;61lUEtoM z<@}pWQ2V=Q)3Rr8mVs6S3$IS*mzG8u-=ZMjaX!@m$)}3?cw$grU+qbD4~NS{hnlAj z@4f2VzIXWgPOk+_J^AqTk$`{+ z)ktJmF#FyK199U9id2%1+HXi>G%u(;SA_}l`b>_-88A}NTDd7uwc1vI!F5S{?N}@c z1Rk^NE2FV@q1z(Az>5j26XxYzr{0_isu{Gyz&}tWzo_jgz?QKenND{&L=cfcTOYkL z8%<($>E_i%3AwBiQ&sg)aN0;~*wl*pFgs_@|C+1rdD~znX`R{by=ORw&6Rxw8wFgk zoB}-786iYm&bDOVZVeF*q{E5#eg}D?_H@qemmz;gK`O zacx&VdCU%sR2Vws)T`-H?Z-w^=N#AjL{)$7smF6+Jk~H5IbCd~>qoT5+ILFdvdTLk z*mOrCJ#^&mJc=y)#wwo-_=&I2ohsSbtA3XKLphq9?Mr1d!-d__LaMC@FT1Y-`zU8w zE8~^AnZT)&gT?$O_ZF}^5|_2|%2xZ=?BEud9ury5QNy|&T{9B&+Vif``MT-Bm>5H* zpSuyLa^K@r-#-Bdp6Q4PVRt&Sllqz$#RZlcgbg?-Pc($Wm{Tu zo!xt_Av4STBob;o&Oc{KLEQKQ%rJ`Kwb))9+@$!)QxCXmD|Oi=4Kz;X*@~JoR2S0$ z->u@KPTK3$dVJ!Z&OP#m{Vcrjo;71KT&Kisy*cW|1?30x3&|<8z2AE~+NR-YRfo&U z`2Nf=EiMkWfQxBg63Ta;p{u+-tlqwJkSsLX=HJ_`ahYgnxL1M|3A`?P;?6#PzfY92 zo8T^EV7P}F_cHM|Z{@h@HoK)NuTL6$dfru^;gBu9Q;nlmD&n|HL$e@$=7G>y6uumt z$X0s5JJTF$%-}OPbm?rbIWP+kJoN1vVepM)4qL_!2r=cG%XX!|oXM;xpEzk_YEgUB z9~!KkcCPTfXyQdV_kL94E;A36Xd&E?O#*^bPpNqymNCyXZ|OcdI_vWXW7(sVfU>$^ z3Kmc0KVkIPNp3UfY^)f_-Kl5^BZ~5A3I&2}66dvdI)dimZynDBe9ylbh3?m<+J}F+ zmo4&oHedZpf{G{1Kr`pYz5@55*%A&v{L&YCc5?sZZa`$k*wwq*X0(>S-HPi*DKjcN zXf#Tb>@DATKT*ph!gbb;%bj&bsCQ}f_JU(rwn_IRjfc~o=Soui$LF&ap#Q`vy59l^ z(bD~OB&3&Pu-rNntdv-BWJV&Y^3L8=sCm|W#?gVbl~q<$^tIESLOkiElMW-es(x#D zZf+tXD%$qP1dK|q!x>%Ncxq;P5%2>&t*p$bs zk5+@zK~m@P{I6VS<$r4u@crmjmZiRLJ)ZW*x4@WOO6$t*ll70r+@|_+q5cBhsNPw@ znE;kyXhBh8@2$_CCjwugz6Jrk!i7|cn5b{wCLDR9MPmWKpB}dzH)P}-y9X?VRnFn4 z>LTC>HmmQs==z_I%GwV=SA#|Lk-j5!SVG9L!dKK`GqIQmX#opQ%Uu=Hrn_kBK}&hZ zE6WGdGg9>fPDY#LKtjsHS$#@XF&(|bq8HZsEZFgl>teM+GPL#m9kUYAe$4o(T*%m} z#qHDYzs8|5RMtz}4FoONl{;#NTdz|k4BD&MeZ0tlVN93wu^r}`FU@$eI<~)4H`hss zl3w@BdxMkrA3fuW*!LKENX%=%;p7AjVqdV4URZRR7C+%MWs5mD7_$dF$xp@bvH``P z9{FEJ{Bqu~J_Z6WobK1dk>{H!ws)%D?mr?#K`qeRyM5!;A!;G|So9b?DXJ$UFg_7% z;5Pxh5ItDIZOm;CO;_%pWG$huzc-Y^=CoVC8II@Ye~OfWE+-;{;7Njuar=Ph%DylE zQqN}v+WRzl;hw&hTu4<=Q!DXe{dOE_V)9Ug+J$hWz^l>q8r_YAfP+vV)+}Lk&Q!#f zbBr6VMi+0-cRK?LSQ$iltGCQ*E)nfDWMF`=TAu|vF78NXCY>nB-0B)P*!+p~XaVpJ zWRm$MuD#5ut%ba-lf3;2Hz3H>o{$gmaPFY&P4PMK;kLp46eJ^yKtGpgG94Iz;U4VE z8yreY2I49Cg(X(4L^y4a=DX+R<`OtkeE~CbE59aOD^VlgO3W_lC}ppM1MoH-Vkanf zmt)u=COrd^iljoStTsi1%jBYaW9(lKcWZ%#1J)~%)4KC}Hw{>lcPrGXe!m5}GEK-Qi1PZf)&2b(&f$3>T&vKsm{2R<9JfU8dcBG<~-(2mp71@(}_a2~!hYJaq7_Cs*##^tQ zNyi|?AzY2`3KZiJg1+;xsC2%oI6Fp!)73Fm6qw@Ql(bjwCrWjx(Fx@h)brdEJ6#gg za@1H8v=r@Ah5w!qQU^1}K;k8kay5NT!2>+H@`#6)NX@Xj&u*^qJ;aN^j+)4KDqhhg zkLFTj``;$L>i;K?Hx!STB;DW9^NKh76&4)9?QT2^bgJSYt=?E#xRkRpArjpua_maB z9ST9p*hD#Y8b`>O)haHG6u%;`#6z1UDG(;~dutNxezJR?-o3k7*W;&0qA{#YXP@Hrx4q>{$H$f}kQE%(HacE9pDTxA}Gh zdS>>|kRRW`g}RwT1~r3ZJm4mYTNJj^1C%Fw1aR>hP5&R=dnbpD{cGNvx0Jg(p} z+){z43Q+|IpL^CkL9yNMuk-oPppg3`OQFhT?cUgI4ZiD=S4EG-K+DdM z2|_hH{BL+z$HG@Edx~uE-eQ$^wTbce48BNiuKq)(&mw!0h@VH=cDkUOpK4YYY9xVA zj>N9&E7>6BoZCvl(2gcA|F*i2_y_fRNzz&NXO=2!M{9={Tge6v53FV-qiuUi?S0l> z+nUnk9S!CpWqu=7;*I+3B^A4S=!emUO-E!iqx;2`lX>?>^NPkQF=nKLE0SR`pP#~D z^>z4^fXr#YQ=-ebJmq7)W0j(6)=Oqr9&x!Lna9-q_`Bj~U+vmqxZ;br-JK|XcD$g= z7YzlYDx(N`h)Pl7JLlfmr!PvaFpx8FOB*8)G0c$7FPa7}_u4cQYJ?UBruxxWjnt=Q z14yQ@TQAme0_lfzPB}L1;$yI-^HvSG3iuftZ)WuDo`9G2eXzOF^?dl4sj^(xvFl+d zATQJNO?54o%k0g`CvHBQFFqO)t3AnVmg;-MRQh|fg91}uGFB3{(^z_Z6rmkCxk}JWK=|8N+ad z0CBu+<-W{Dj1^s|ovIob935Twq4>ho7sllu-PP4m$RQmiVa8@rx=Q%S(y;XB-@XR}Qe5?_?iP5}WX~Lsh`Cl=zUSKDN1z>hpo8ffAhv z7BpV~G6b`QE<_29fPhA!y2b%j!NL>I?lGNG;N@^9=*IB>4%oZ-R{1oFz#fZjWd8381k|4yMt;K9PNp6rVZ7}4Vyoxfx~dXZIHtrFzU~Yq?SvSKx7u~__(5Y z!qAw2QT=n*hr`n|b$DmEooI^1KMq3^!OHqy6mm)a)dfz&k!bV5&9 z6jIBiZw!94S}zrru?V3lU#a{(HdfqxO%FUrm_m$$$+$b|ReU2?rrYndt5@@;vJ_Cx ze~W(qqK$Nf_3Cipyvk-^F2DO1^_8Hi&}-cC)%-KrTl*|-J@;@!n4a|WKe9NW;yrdNiCyttCoRy zR_EzP=`g)7;^gW-v?{uecd5R)UjL(Pq)tG)ldHs3vq;qAXxH3lcbqNN1wULk7JBc; z=@U=m`h5%hbo4{@k+G^R%dZAKB7A*<|(N{O{{v zlzH!aQmCGR#gLXkBC|pTTm9M_yodPut;wv>`Q{H?Jjke_Qrj`pZD%1E{(JVCWc zty8M*Z2W3f64kjX7XrQTie8Pq*=9`BVps0-%IPZJ&hvrlm@4f*Ir8l>tx_`?PnB|L zwxH8wkPe2~$9H>3Q&UjHiP--*C@x8WLn~`C?hEtrZ6x>lsmJRA8JUJSE?WsrRwkf@ zNyI=&gy_}ds4C_rhlcN`@t#KW-+rm-mGwm&$PvD#6bfF&WdlIR@v5^T1w(z?@YT%MB*{L->R|sSE?V~_{U|+(Y?~E9C zFC`EOkc%7^_;l%8RRVBcMlaNyD1>Hq!8Nu}Qz5DhH=n#I@me0dm6iOwsWgyv)oYR6 zayO?c=wN4Co@O@j_z;F87!C)A`s&N#ZN5iX5s`c?zKF|W-S?_3KJqL-I<8nFad;n^ zeJS&v@n4AcL%zpsmMh~Vy4-oryKJT%f2oM+^mcMtMBnrZ|m(2!X*k1 z6@U5|>G{B62iw4P)mL8--~?bifRRD8&_OB?M;6k-;ByDxcv`II=#C(c7USV>VIo}k zBL$DTh6U2W2(OudbM?7ruVMfikz2MMg}m~|)A}Y9I@=U2b^RonjWaI!`vU!mJ+ z@`)m`Ms^Q(@$0R%=Sw@4UIB{SSbf^Akzb>DVNYd}_7H9d4WmX82t%Mf!e|!}L`B%x zuW1BfhICnZqy4DA63c|wn@`RJmafMUTSIKzmTgTVON&<1~DFc%T!8DR4v( zwi4i60sHX5tCrtFr7r!<=UW}*z`l6RzoVO@XmrT;+a(&F+3u>_98lcZ5RCyj+@OCV zyc=Nt)BeWaiU@U0gT&BOKUW078w2@Eha6l?Yg}_IETZB4HD0qVU1~z zo2$sCL(E6-_WqhX-WmGUp0qobYAF$?AU6ps9DS`IKt+tq(Nem4y{ln6!5E4=w%@r4 z{`n~=G~1SXs6Zz}l9xL-cz+F5`;9|kcTJ!(z7|P*|Cap7Fkf_!h!zt(`XK!7%-N}M zVD{Dn(aLwsgGFVF%HjE;Nz!_ivQaHnirZ5F4E@Vwd4w@niaBET@e|fo{RG4^0Xt?A zI~my)27SCyg3V^J7oNZSc};E62+&hXkm z*p9@)<5v(blD@Cz`}Zk&`+@9QA!G=HpCBQN{RHPO(TZovhj06BSIRFn1787v zlqOMOql0+@tW*dMux{cm+G)LmpY-_Hk5m==8%}ut&Tq%ywE=0H0ck<^^X{G{%a&|Q zD;soA>Vp_u31=*=Dh+7yg2lOrOEcQAEoda2YiV{IN>%g0fTLkQ~6)Bf9`%X=Lg*jjsm2fu^a6#e+@jF<(-Boq#YUL~KCU zfZor$jq3{|O;hU>5V%YMNsUhz>Ao@A8dUWGO5nlDdD85Qdbe>;H&(s6gbM zYD?Ha7?54mwjJ_H0bbP)*O-N-a3Z-kD+oyM&p`=dQUB){tn`biuYU^qOBYbu12pn6KwOy|uqT)V#X+Sv%tAJu0ke0^6rG9?njS0fhW5u7(1CAv@D&YM=n<6TX-#zEwU*5)RD7h#@L{|Bp;m!~un_Ecto*}B zP6HJSq<#Do!=w*pn;G<@JFZ`gPKa8mJxM1o2LBY0$X6xhj_-quNK427xYq4-XQ(SN z%IfIE{Iw1We1dJ8rScKn!?G%DcoA>Do$@}0XS9^dumXPgm#<)e10oB@&wJAQTf})9 z3re<{%+ctw1Bt(-vbmv;`QOUM&))yOrfla8QGrmW=$gSS6+>9qI$!qp;!KHpx+zv7whWICIx zVIoE5(&ux0a14pS7%;4pMu01=XHJgujA<5+;P@^F=adCrYt4mR68bLdwQ=WvyyTa{ zS~umAlL@)xhZ;`ys!^7{0}3Xwl!NcN`Pbk&6W#hb2?->AGVV|4h6%OFNQ(vhcid5YkfC@3)2F!W;?mP z{vlXW_zRumhuBeR@|2{c$VjAJDE^B0I?u-5%TzMo3Yq95{jW#UdfOoYjFx+w0UrX_h(iCd({=y~yYlcED>rpZI-f zq1@fz!)fo3Uq~UWhG0NszRksLiRV!En!FMo|?>UPtTuZ{H-p-C3@bfCyQ?`jDpqYrUwv zMcAO?Tjz@SirckRi#NutHQ}4P1+k%5-2V8L^P|eqZ>g9#q#vR&)Nz@#3o{chw@fQC z3a16Vj3B@>FN$vKnv$frP39cPkz9wV6(RBbB+jd1rNhkVsl3>A8!@5x5+yeci+<~S zX%^`NB#D`6jaE9GAen!<1i=QU>#vL+4J^l+BNqf)zy4C{H*t1c?+$vfdw3K4`p#>x zagyueuYcD%tr#tP+iK7OfO6!+b#f#L{NxLM*SO2OZLB)u0MGq_Ai;XxUkuP1y|P&6 zQ;Vl1F{U0}nUA6qg5vxe5-SH~vWN2K1WEo8U=7B88r3W2hL}y8Ckd1RumZu$-dwFN z8u?ZHm?t1gI)c-1qufT?+IBUTL#j+kp59q@ofBl$gQ-j1MW5E6Cw`jY^ni^@2ik&E z9z~iN1(G@=ey)!Hjp!s%X^hc2uc8T&PCCWe8VW)(ILd#mw~7obZf@y<&1-s`bDnqV ziloNzSw{8za4q!R)*u8@KLQ9-9pf!YQ3j^1V2rjJ4!g(HZ2vwPQ(KAGmioddSu(7z zZs#=K2~fXLNp@z!>+kUDK$XOeSwY~zMnjJLBKekHvHUPB<%#ETL1TpjMe=SV5tID$ zuhl1#YOo+-r!$2sAkg3g*gnQ&Uc=vxy`mgg^CraMb~G~@TaO0N!(olQUxM7gaY_S$ zn6Z)iLsdihESmvnT$CIX_sr3lLFI5~IV{_Dej=EH31V|C7Ng@lKf=)^vNJ2{1MPi2 zQ%s&P(j`A@nZ1#x9z2-yx;xKkjD`7^5|5iz55Q42zaW*h)Iro6XfRO&2QpI3q2;x% zGEqrYiW*zO6b*hZsY;?B2Vo7z>@pqFy*<=CRjQdjG7yuCAH9S>q z9{l`(8UVeg2f#Sy%>>sxKRdfcu~~CdlLDPtLGlnPdz)bvnN<5p8`IwYg^x5C{ZCi2 zT9G2i;MCZ9HqVv#++EXUrAb68H}-m1_;}TRw3FMM!v2X@Z;A-Mo~o+D^NMCS-2W#t zdo_VY-*A#=B69$6|2&lWfeZ#;=3tIiZ-=x3Ec?O@g(^ZN3vm4#%kn?CaQ~5r`9Hb| z!Q%uD{+sq-JVmk(4l!Ar7%U@G>qZEwNhr7q5j%&0q+$5`pmc(!YGmkNep_QT@_0oAFmAr1+izov^1TXe za4W(KKY&;TrE=>HyYE22M?FGJ5UYZUMKs#WHYCwP(fij;Oti9gmvKdX;Y4mU1kc)x z@OC1Yn+c;yk6F_8L5))TdPfm*GM}^TMW4OR9&)U*hUvNdx{SZK%bV%!ta(^Qzs}2^kE_0!G$zmfl#;FV0Cp8h^zK3 z=c1#ay%L5p$M?Z(`-2pUeP%vcWFI*|%!vFUZuGCjJsC>_`BWefPdV76_n8&E?*B8; znlK;0Z2d1N$N!^u`hPr_->ZOdUYz;+buun)AK6UVg@tQWtSoZ?T>thbqrHQJhy0vu z>_+Fp_ai#g!2Qv%{rS&@ZmOZY|^w#`b-4ha|=k12Dk6JkgyG@F8QCg9Y5 zHYs;z~W%m7jQ(X2~lU$zgE8E{YUF+>)_M;|u5)(bGzh3mpS~kXrp4OS)sxco# zfLq~;4QXd<8UFmNkicckR@FUBz;H=>mR$$Tp%G zL#r5W$LL$1uq4W0^l3_Th@!cnU+%m5PL}zjPYw*J5Y>iTZ%ApNxNuD<0c-{FU(D45 zv$0Hx*`=)8CZ5-2WT_<#sOHc- ziUzGRv?v4X`{>E+BMS2mnT325-FLU4FWc$FSs6a-&;&i+=z$!Q!?vh`9 zLUL}@M*wiR5nJ(_WLao|xfHY1S#8mF1LK%ZdUo0!{%~3e%g^5Q+=T!{J?;~9p3se( zK=83c0yC=x&308-qrjO8^MV8t+kDwp^tP6m`vYSUlP`6UX;K+L(<^>%R* zfH-dunl{T$?iNF;rgQ;_h>~tIv!0hJj?)=I!ih4tEA=|fg=PaT zcC{1WZ1@^Me(Uh&s5Alepl5)`_5OiZ2-aAuVJ8W|H03ICe>Ponr;VZ9r*ZPSWZn9` zs^`Mq$3kPPF$e+lJRepb4UIM!y?ZunZ`pX{$>tK)%H;Cc>km|b(tn{&)Lroz3fSC?~dIb(F*HPPciIM;N}ARo?B)zvjEkllPY z#aZ#|@i_bu$iKny2?a>JcsfzDuz!&ob}(l6V||@5S%Yo=8yOw*MD8^qH;7V_O}9N( zRj3SRKU`o*l2=9_76wO*(H*ATl8uKThbmvdwU>aqjz3^*D@f5(Km2&`V2p6zkA&A>4cx>?@9+x8^E#w z;4o|V&X3MaS$3_VK?42()E7MfDHl*eFFkJ1Qid*4tmRMjgzbdJh@LK2vasMQ#ZC+B zGQ6NTmrk(GK(sGufK6KkkF%;86EBL6`b2TFC3IarU54)?O{F&V7K1@=JhRWvp_Wp0 zcuVz?@3X+Fk&n6s||D)9SKUN2T zn?ZhYyFLY^E0x3Uh8O}81kFc5OgjSsPCz?JmXaIY<&!&c2#a`LE1)#5kPn*u^n|o9?t(wl%M1QS0ZhQa;_JqtCWYZc zG*+8UJaP4Q-mmY+!0+7K;{O}h@nZG$8;w8+C9__=+~TriA(51|*2>^q z2*7v>XYM6P{OKQXB?|$;qhY;8F3T=G`v8J-GEnNd{i-m*+-Qu?&Sq7a{7|Jk3FUmk zHpckafDh(dJy}eZjI*m^G$LY4=u|&mu2eAFv#njI#zA|DPLk05#5g-@!lC-{+qrDgJ4ww(Z_20zk)3n+5?uwsfPmcbq)2I7} z0F*I;Xm4tXN2JED0Z(~mZ2^zJxbejVik^nArsu*D_v;oh;>6FjR~ z8_J%XWdQeH%IjBIrH!$rwpqM!4Qv98sox!aOTVX6-bclM*~Y79EBs=(uU1z{j;6t* zlo%nIPW{!`*?e4pPTwz zFP-!i>Q&l-&3@qkV9oSkaLlfhQK(lPy)OAT#d7}!OGkq1@ZlU44NtFS%Y@sQ>-y)P zE9k|Cf018dtsgWADipXL)C)%^q(IiHanD_!skSMm(!19@?>#ObE59~HE6U-ZJ62&< z;h`siA1aL>#c0$@lD20oB#0Mde^>ye6fIA!vxJ2-f-~i$te`Ziw@LNtp*(JjOsBr# zXph#Fq51A)Tat+)EDc23tXfCZubxsA!eu-BsFzuBt$wUpt^blE%a=c;0F?+Z@cj4D zZ?4vm0b==!xo3r~%YS{o6UH|vm!H8m8Oc1sDI?elZ#iYzUI(}mm5&TiVNru+mcJw` z;w{W8?T|9sh{+Xv5v*6@N#908ebXDUpCJlhKs^K3-y!Dzh-hBC@hmULB;r7*QonHZ zk0UXUsn*fyleQ@a!VXHysn#W-DmWx!Rbb2MuJ`#!c4K!f%G&8rEC0@dqe@-#U?=Z> zW|$-5Z2hhp3~!-f`C1WMIjpdv{5hn^g;-4mbh)#62GOrA@|ElH0&_9a5g&er5o`_+ z|8pnbs#_UUV-mG05zND)U>7u=nB#vj_=UQFHp^|P-7~E*(h=vG$ZPYR;`bwO0%Zdp z`eVx+cnrx>*p3vs+Q~G7Xe3M5)}Vg9C9=Eb5qs{lx6%;=au^U}y<6%Zmci5T_S5X# z(ZdJQ{3%k8*1y(m&7pPqB78f_!xUR0P4XBS#apV*zF9DzEM?%k|BpBRkzh4}UQ~cz zSXheW)EgF#)*4FL-RSJV1X$goqFSJReR=*K7^r*`clVioM5_Iz5gS@i%8=)luPL;8 zU6EbGbY(oU+uK7qK8Y7Gch=eTgHTaXAt6&(p|0xQR>~7++~Ro^6LjuMQ87b4{tfYk zT+_}G&s3kRbE;O4*>63KC%q}~IOgWE`Hmc@C^{o?pRB#b$Gn{TyAZ4? zBB1nXmK>FyhL_DMP_J0p=VSqAvs#+CN*%!nGZu(#|l zo|w^>&1p1fa-QemmkHc0-2!`vT}&74#5xe`0HmcpDpKmZy;+O#9kEOhvwv3_e0)-O z<)|lgST@XZSl+`m@0MJA_+S+z;~{kg!1`Solmf`@0H29#Go!;o-lo9SqIR-CM~%Sk zk~*0^KG8fUJZEak{YHp7^reYi_4gt-*`Zgh)+s&W=OsN_n~NvF?0dql_o=aw{X=t& z7fR(Qwx{2ck6!h%Y8Damt~yV1SNFJOocLV*n0VXUp@VW3D{7`1M0TD)zj@|B%}ht{ zA8%45@iCkajr=~He+JcfEopN&wkh6gY9rT(9OQH->7&rX4w0t3NHkI zX!)}7Z{!>R!s|GiU{qXBk+S$G0>6=9|5EtRRlXXxqD8#u@nZh9db+*d)N7)a^jC16 zNJ7y9x7ZY+>&U`lT}Q0xJkBX%UN{oDBG~ikDwY^xVr;Y#h?}^(vT=YvD>RxmC&s&b zsq9&*4Q!rlPMzk1?mAi@5#Qs8;{;D(gEW@cTVeJf|F&X{p%+?Ps|F4?DKH)<`uk)g zQC{2qo^;B!14?cP0<7uKev~#e%Hx=BPB4C_%~YrRtFPZ4ON~9)Nw1pj`vf^FR|0Bp zE+}t>W;=NbLLlCS-CbVk=1M@1NS^^(=K%#2Aw2?8ybZm+z2|_aQVSv%8;PJR1Fd!4 zYXJ+nO3!CEvWs{=$l2HZ7(MG*3!+3jjOZKoi0wi#0t99$P@q}G?_A_*>0^|BmQwY< zMohI!W<$KRYxzuNVm4|x|HS!-vvMSoC)Nm%k678I zi(>v!d4WbJJpAn#+GeV9D8DnW=;7s)9gpjPc<&!J7|e+|#B-Wj-rsY4tgTN1b zFnIDi>PmjdRU_X%W!OQ2dzno#aA6GGxk-r=`SBO6P^2ul*>9jD5idj!gejgI$WFY9 ziqdP2xOa#z%FEDPJerLlq!K_NAeb(b;=|2fw&Q8dUkKy#WUY6UoVY?kw(Aoe}u~Ya~*y_>r8~Kzve#Qr1aM-%YU{PBAGgb?V+tWiR=rp4b@&F)}pf|sq z|7^@3!zK0>b1$g^kYTF{^(Xtdh^?LQgK?eS@5xW*k9RZ2H=R0xx=vEd!UObC2|oCu z!zJp(uddTSn?GICRZy&T@1eywo+x&?d1zd83!}>?B)ADbeM2xglp%6mBGkM6a+y{w zm$rZ2GY=h!*;78aeT|BKxjQMn18>{0rOH+R`_Dse_M@V*r-eT<%`BcjH!&w|WMnrU z)me=$`)|l~Y`npO4ozI{&Ik zQxyOmC6nd`vlhW@KmvE+?IMAsGTmJ=m! zJRA+;P><|FzZMD?VCCoG=03U`w0F@j*IK?W(AjB2DQ;ulZu~c3N_M%A_sucIf7%{w zEezDUl3v!*$o1Nl5yKQDdKoU!>?~08y3+#|w((;d13d*rRg-Re_#a{w#!|USPlOas zVYRA|)O8il!a}{j$Oynv{!6a3kv%cmJN|x;12u<%4q04YYP*;ix|j+YJDCC>AfCrO zV9v)+IiEaMf6ODu%P+|Dl;iPZ!N-qRQ{4RjZx7hon^>B8{MQd4zxEP{CJ+7@!NtiMfT{}=u&lLP<& diff --git a/worklenz-frontend/src/assets/images/team-members.svg b/worklenz-frontend/src/assets/images/team-members.svg deleted file mode 100644 index 520b76e6..00000000 --- a/worklenz-frontend/src/assets/images/team-members.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/worklenz-frontend/src/assets/images/to-do-list.png b/worklenz-frontend/src/assets/images/to-do-list.png deleted file mode 100644 index 1fa91ea72ef8e27a870f35aeb57b04c25c941ac6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29396 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 BcMAXj diff --git a/worklenz-frontend/src/assets/images/user.png b/worklenz-frontend/src/assets/images/user.png deleted file mode 100644 index 6f7a0cbcd62946b9ce24510f8d664cbd5128f2c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22004 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 a{7-xO{C%Hl{Rl~H6pRhb^b2&Uq5lL&%Xkm~ diff --git a/worklenz-frontend/src/components/AuthPageHeader.tsx b/worklenz-frontend/src/components/AuthPageHeader.tsx new file mode 100644 index 00000000..a94d5fa5 --- /dev/null +++ b/worklenz-frontend/src/components/AuthPageHeader.tsx @@ -0,0 +1,27 @@ +import { Flex, Typography } from 'antd'; +import logo from '../assets/images/logo.png'; +import logoDark from '@/assets/images/logo-dark-mode.png'; +import { useAppSelector } from '@/hooks/useAppSelector'; + +type AuthPageHeaderProp = { + description: string; +}; + +// this page header used in only in auth pages +const AuthPageHeader = ({ description }: AuthPageHeaderProp) => { + const themeMode = useAppSelector(state => state.themeReducer.mode); + return ( + + worklenz logo + + {description} + + + ); +}; + +export default AuthPageHeader; diff --git a/worklenz-frontend/src/components/CustomAvatar.tsx b/worklenz-frontend/src/components/CustomAvatar.tsx new file mode 100644 index 00000000..309e2cb7 --- /dev/null +++ b/worklenz-frontend/src/components/CustomAvatar.tsx @@ -0,0 +1,25 @@ +import Tooltip from 'antd/es/tooltip'; +import Avatar from 'antd/es/avatar'; + +import { AvatarNamesMap } from '../shared/constants'; + +const CustomAvatar = ({ avatarName, size = 32 }: { avatarName: string; size?: number }) => { + const avatarCharacter = avatarName[0].toUpperCase(); + + return ( + + + {avatarCharacter} + + + ); +}; + +export default CustomAvatar; diff --git a/worklenz-frontend/src/components/CustomSearchbar.tsx b/worklenz-frontend/src/components/CustomSearchbar.tsx new file mode 100644 index 00000000..002a052b --- /dev/null +++ b/worklenz-frontend/src/components/CustomSearchbar.tsx @@ -0,0 +1,37 @@ +import { SearchOutlined } from '@ant-design/icons'; +import { Input } from 'antd'; + +type CustomSearchbarProps = { + placeholderText: string; + searchQuery: string; + setSearchQuery: (searchText: string) => void; +}; + +const CustomSearchbar = ({ + placeholderText, + searchQuery, + setSearchQuery, +}: CustomSearchbarProps) => { + return ( +

      • ' + - '
        ' + - '
        ' + - '' + - '
        ' + - '
        ' + - '

        Company name

        ' + - '

        Addition information

        ' + - '
        ' + - '
        ' + - '
      • ', - normalText: ' ', - highlight: '', - highlightContent: '[TEXT]', - } - }; - - if (typeof method === 'object' || !method) { - options = method; - } - - var settings = $.extend({}, defaults, options); - - return this.each(function () { - var instance = $.data(this, 'mentiony') || $.data(this, 'mentiony', new MentionsInput(settings)); - - if (typeof instance[method] === 'function') { - return instance[method].apply(this, Array.prototype.slice.call(outerArguments, 1)); - } else if (typeof method === 'object' || !method) { - return instance.init.call(this, this); - } else { - $.error('Method ' + method + ' does not exist'); - } - }); - }; - - var MentionsInput = function (settings) { - var elmInputBoxContainer, elmInputBoxContent, elmInputBox, - elmInputBoxInitialWidth, elmInputBoxInitialHeight, - editableContentLineHeightPx, - popoverEle, list, elmInputBoxId, - elmInputBoxContentAbsPosition = {top: 0, left: 0}, - dropDownShowing = false, - events = { - keyDown: false, - keyPress: false, - input: false, - keyup: false, - }, - currentMention = { - keyword: '', - jqueryDomNode: null, // represent jQuery dom data - mentionItemDataSet: [], // list item json data was store here - lastActiveNode: 0, - charAtFound: false, // tracking @ char appear or not - } - ; - var needMention = false; // Mention state - var inputId = Math.random().toString(36).substr(2, 6); // generate 6 character rand string - - var onDataRequestCompleteCallback = function (responseData) { - populateDropdown(currentMention.keyword, responseData); - }; - - function initTextArea(ele) { - elmInputBox = $(ele); - - if (elmInputBox.attr('data-mentions-input') == 'true') { - return; - } else { - elmInputBox.attr('data-mentions-input', 'true'); - - if (elmInputBox.attr('id').length == 0) { - elmInputBoxId = 'mentiony-input-' + inputId; - elmInputBox.attr('id', elmInputBoxId); - } else { - elmInputBoxId = elmInputBox.attr('id'); - } - } - - - // Initial UI information - elmInputBoxInitialWidth = elmInputBox.prop('scrollWidth'); - elmInputBoxInitialHeight = elmInputBox.prop('scrollHeight'); - - // Container - elmInputBoxContainer = $(settings.templates.container.replace('[ID]', inputId)); - elmInputBoxContent = $(settings.templates.content.replace('[ID]', inputId)); - - // Make UI and hide the textarea - var placeholderText = elmInputBox.attr('placeholder'); - if (typeof placeholderText === 'undefined') { - placeholderText = elmInputBox.text(); - } - elmInputBoxContent.attr('data-placeholder', placeholderText); - - elmInputBoxContainer.append(elmInputBox.clone().addClass('mention-input-hidden')); - elmInputBoxContainer.append(elmInputBoxContent); - elmInputBox.replaceWith(elmInputBoxContainer); - - popoverEle = $(settings.templates.popover.replace('[ID]', inputId)); - list = $(settings.templates.list.replace('[ID]', inputId)); - elmInputBoxContainer.append(popoverEle); - popoverEle.append(list); - - // Reset the input - elmInputBox = $('#' + elmInputBoxId); - - // Update initial UI - var containerPadding = parseInt(elmInputBoxContainer.css('padding')); - - if (settings.applyInitialSize) { - elmInputBoxContainer.addClass('initial-size'); - elmInputBoxContainer.css({width: (elmInputBoxInitialWidth) + 'px'}); - elmInputBoxContent.width((elmInputBoxInitialWidth - 2 * containerPadding) + 'px'); - } else { - elmInputBoxContainer.addClass('auto-size'); - } - - elmInputBoxContent.css({minHeight: elmInputBoxInitialHeight + 'px'}); - - elmInputBoxContentAbsPosition = elmInputBoxContent.offset(); - editableContentLineHeightPx = parseInt($(elmInputBoxContent.css('line-height')).selector); - - // This event occured from top to down. - // When press a key: onInputBoxKeyDown --> onInputBoxKeyPress --> onInputBoxInput --> onInputBoxKeyUp - elmInputBoxContent.bind('keydown', onInputBoxKeyDown); - elmInputBoxContent.bind('keypress', onInputBoxKeyPress); - elmInputBoxContent.bind('input', onInputBoxInput); - elmInputBoxContent.bind('keyup', onInputBoxKeyUp); - elmInputBoxContent.bind('click', onInputBoxClick); - elmInputBoxContent.bind('blur', onInputBoxBlur); - elmInputBoxContent.bind('paste', onInputBoxPaste); - } - - /** - * Put all special key handle here - * @param e - */ - function onInputBoxKeyDown(e) { - // log('onInputBoxKeyDown'); - - // reset events tracking - events = { - keyDown: true, - keyPress: false, - input: false, - keyup: false, - }; - - - if (dropDownShowing) { - return handleUserChooseOption(e); - } - } - - /** - * Character was entered was handled here - * This event occur when a printable was pressed. Or combined multi key was handle here, up/down can not read combined key - * NOTE: Delete key is not be triggered here - * @param e - */ - function onInputBoxKeyPress(e) { - // log('onInputBoxKeyPress'); - events.keyPress = true; - - if (!needMention) { - // Try to check if need mention - needMention = (e.keyCode === KEY.AT || e.which === KEY.AT); - // log(needMention, 'needMention', 'info'); - } - - // force focus on element so it triggers on IE - content.click(); - - settings.onKeyPress.call(this, e, elmInputBox, elmInputBoxContent); - } - - - /** - * When input value was change, with any key effect the input value - * Delete was trigger here - */ - function onInputBoxInput(e) { - // log('onInputBoxInput'); - events.input = true; - - // convert android character to codes so it could be matched - var converted = e.originalEvent.data.charCodeAt(0); - - // trigger mentiony on mobile devices - if (!needMention) { - needMention = ((e.keyCode === KEY.AT || e.which === KEY.AT) || (converted === KEY.AT)); - } - - // force focus on element so it triggers on IE - content.click(); - - settings.onInput.call(this, elmInputBox, elmInputBoxContent); - } - - /** - * Put all special key handle here - * @param e - */ - function onInputBoxKeyUp(e) { - // log('onInputBoxKeyUp'); - events.keyup = true; - // log(events, 'events'); - - if (events.input) { - updateDataInputData(e); - } - - if (needMention) { - // Update mention keyword only inputing(not enter), left, right - if ((e.keyCode !== KEY.RETURN || e.which === KEY.RETURN) && (events.input || (e.keyCode === KEY.LEFT || e.which === KEY.LEFT) || (e.keyCode === KEY.RIGHT || e.which === KEY.RIGHT))) { - updateMentionKeyword(e); - doSearchAndShow(); - } - } - - settings.onKeyUp.call(this, e, elmInputBox, elmInputBoxContent); - } - - function onInputBoxClick(e) { - // log('onInputBoxClick'); - - if (needMention) { - updateMentionKeyword(e); - doSearchAndShow(); - } - } - - function onInputBoxBlur(e) { - // log('onInputBoxBlur'); - - settings.onBlur.call(this, e, elmInputBox, elmInputBoxContent); - } - - function onInputBoxPaste(e) { - // log('onInputBoxPaste'); - - settings.onPaste.call(this, e, elmInputBox, elmInputBoxContent); - } - - function onListItemClick(e) { - //$(this) is the clicked listItem - setSelectedMention($(this)); - choseMentionOptions(true); - } - - - /** - * Save content to original textarea element, using for form submit to server-side - * @param e - */ - function updateDataInputData(e) { - var elmInputBoxText = elmInputBoxContent.html(); - elmInputBox.val(convertSpace(elmInputBoxText)); - log(elmInputBox.val(), 'elmInputBoxText : '); - tmpEle = elmInputBox; - } - - /** - * Trim space and   from string - * @param text - * @returns {*|void|string|XML} - */ - function trimSpace(text) { - return text.replace(/^( | |\s)+|( | |\s)+$/g, ''); - } - - /** - * Convert   to space - * @param text - * @returns {*|void|string|XML} - */ - function convertSpace(text) { - return text.replace(/( )+/g, ' '); - } - - /** - * Handle User search action with timeout, and show mention if needed - */ - function doSearchAndShow() { - - if (settings.timeOut > 0) { - if (settings.globalTimeout !== null) { - clearTimeout(settings.globalTimeout); - } - settings.globalTimeout = setTimeout(function () { - settings.globalTimeout = null; - - settings.onDataRequest.call(this, 'search', currentMention.keyword, onDataRequestCompleteCallback); - - }, settings.timeOut); - - } else { - settings.onDataRequest.call(this, 'search', currentMention.keyword, onDataRequestCompleteCallback); - } - } - - /** - * Build and handle dropdown popover content show/hide - * @param keyword - * @param responseData - */ - function populateDropdown(keyword, responseData) { - list.empty(); - currentMention.jqueryDomNode = null; - currentMention.mentionItemDataSet = responseData; - - if (responseData.length) { - if (currentMention.charAtFound === true) { - showDropDown(); - } - - responseData.forEach(function (item, index) { - var listItem = $(settings.templates.listItem); - listItem.attr('data-item-id', item.id); - listItem.find('img:first').attr('src', item.avatar); - listItem.find('p.title:first').html(item.name); - listItem.find('p.help-block:first').html(item.info); - listItem.bind('click', onListItemClick); - list.append(listItem); - }); - - } else { - hideDropDown(); - } - } - - /** - * Show popover box contain result item - */ - function showDropDown() { - var curAbsPos = getSelectionCoords(); - dropDownShowing = true; - popoverEle.css({ - display: 'block', - top: curAbsPos.y - (elmInputBoxContentAbsPosition.top - $(document).scrollTop()) + (editableContentLineHeightPx + 10), - left: curAbsPos.x - elmInputBoxContentAbsPosition.left - }); - } - - function hideDropDown() { - dropDownShowing = false; - popoverEle.css({display: 'none'}); - } - - - /** - * Handle the event when user choosing / chose. - * @param e - * @returns {boolean} Continue to run the rest of code or not. If choosing metion or choosed mention, will stop doing anything else. - */ - function handleUserChooseOption(e) { - if (!dropDownShowing) { - return true; - } - - if ((e.keyCode === KEY.UP || e.which === KEY.UP) || (e.keyCode === KEY.DOWN || e.which === KEY.DOWN)) { - choosingMentionOptions(e); - return false; - } - - // Try to exit mention state: Stop mention if @, Home, Enter, Tabs - if (((e.keyCode === KEY.HOME || e.which === KEY.HOME)) - || ((e.keyCode === KEY.RETURN || e.which === KEY.RETURN)) - || ((e.keyCode === KEY.TAB || e.which === KEY.TAB)) - ) { - choseMentionOptions(); - return false; - } - - return true; - } - - /** - * Update mention keyword on: Input / LEFT-RIGHT - */ - function updateMentionKeyword(e) { - if (document.selection) { - // var node = document.selection.createRange().parentElement(); // IE - var node = document.selection.createRange(); // IE - // TODO: Test on IE - } else { - // var node = window.getSelection().anchorNode.parentNode; // everyone else - var node = window.getSelection().anchorNode; // everyone else - } - - var textNodeData = node.data; - if (typeof textNodeData === 'undefined') { - textNodeData = ''; - } - var cursorPosition = getSelectionEndPositionInCurrentLine(); // passing the js DOM ele - - // Save current position for mouse click handling, because when you use mouse, no selection was found. - currentMention.lastActiveNode = node; - - // reset and set new mention keyword - currentMention.keyword = ''; - - var i = cursorPosition - 1; // NOTE: cursorPosition is Non-zero base - var next = true; - while (next) { - var charAt = textNodeData.charAt(i); - if (charAt === '' || charAt === settings.triggerChar) { - next = false; - } - i--; - } - - currentMention.keyword = textNodeData.substring(i + 1, cursorPosition); - if (currentMention.keyword.indexOf(settings.triggerChar) === -1) { - currentMention.keyword = ''; - currentMention.charAtFound = false; - - // NOTE: Still need mention but turn off dropdown now - hideDropDown(); - } else { - currentMention.keyword = currentMention.keyword.substring(1, cursorPosition); - currentMention.charAtFound = true; - } - - log(currentMention.keyword, 'currentMention.keyword'); - } - - function getMentionKeyword() { - return currentMention.keyword; - } - - /** - * Update UI, show the selected item - * @param item Jquery object represent the list-item - */ - function setSelectedMention(item) { - currentMention.jqueryDomNode = item; - updateSelectedMentionUI(item); - - log(item, 'setSelectedMention item: '); - } - - function updateSelectedMentionUI(selectedMentionItem) { - $.each(list.children(), function (i, listItem) { - $(listItem).removeClass('active'); - }); - selectedMentionItem.addClass('active'); - } - - /** - * Handle when user use keyboard to choose mention - * @param e - */ - function choosingMentionOptions(e) { - log('choosingMentionOptions'); - - // Get Selected mention Item - if (currentMention.jqueryDomNode === null) { - setSelectedMention(list.children().first()); - } - - var item = []; - - if (e.keyCode === KEY.DOWN || e.which === KEY.DOWN) { - item = currentMention.jqueryDomNode.next(); - } else if (e.keyCode === KEY.UP || e.which === KEY.UP) { - item = currentMention.jqueryDomNode.prev(); - } - - if (item.length === 0) { - item = currentMention.jqueryDomNode; - } - - setSelectedMention(item); - } - - /** - * Handle UI and data when user chose an mention option. - */ - function choseMentionOptions(chooseByMouse) { - if (chooseByMouse === 'undefined') { - chooseByMouse = false; - } - log('choosedMentionOptions by ' + (chooseByMouse ? 'Mouse' : 'Keyboard')); - - var currentMentionItemData = {}; - - var selectedId = currentMention.jqueryDomNode.attr('data-item-id'); - for (var i = 0, len = currentMention.mentionItemDataSet.length; i < len; i++) { - if (selectedId == currentMention.mentionItemDataSet[i].id) { - currentMentionItemData = currentMention.mentionItemDataSet[i]; - break; - } - } - - var highlightNode = $(settings.templates.highlight); - var highlightContentNode = $(settings.templates.highlightContent - .replace('[HREF]', currentMentionItemData.href) - .replace('[TEXT]', currentMentionItemData.name) - .replace('[ITEM_ID]', currentMentionItemData.id) - ); - highlightNode.append(highlightContentNode); - replaceTextInRange('@' + currentMention.keyword, highlightNode.prop('outerHTML'), chooseByMouse); - - - // Finish mention - log('Finish mention', '', 'warn'); - - needMention = false; // Reset mention state - currentMention.keyword = ''; // reset current Data if start with @ - - hideDropDown(); - updateDataInputData(); - } - - function log(msg, prefix, level) { - if (typeof level === 'undefined') { - level = 'log'; - } - if (settings.debug === 1) { - eval("console." + level + "(inputId, prefix ? prefix + ':' : '', msg);"); - } - } - - /** - * Intend to replace current @key-word with mention structured HTML - * NOTE: depend on jQuery - * @param fromString - * @param toTextHtml - * @param choosedByMouse - */ - function replaceTextInRange(fromString, toTextHtml, choosedByMouse) { - var positionInfo = { - startBefore: 0, - startAfter: 0, - stopBefore: 0, - stopAfter: 0, - }; - - var sel = window.getSelection(); - var range; - - - // Move caret to current caret in case of contentediable is not active --> no caret - - if (choosedByMouse !== 'undefined' && choosedByMouse === true) { - var lastActiveNode = currentMention.lastActiveNode; - try { - var offset = lastActiveNode.data.length; - } catch (e) { - log(e, 'lastActiveNode Error:'); - var offset = 0; - } - - range = document.createRange(); - range.setStart(lastActiveNode, offset); - range.collapse(true); - sel.removeAllRanges(); - sel.addRange(range); - - // TODO: Bug: Choose mention by mouse: @123456: IF user put caret between 2 & 3 THEN the highlight will replace only 3456 - } - - var isIE = false; - - var stopPos = sel.focusOffset; - var startPos = stopPos - fromString.length; - - if (window.getSelection) { - isIE = false; - sel = window.getSelection(); - if (sel.rangeCount > 0) { - range = sel.getRangeAt(0).cloneRange(); - range.collapse(true); - } - } else if ((sel = document.selection) && sel.type != "Control") { - range = sel.createRange(); - isIE = true; - } - - if (startPos !== stopPos) { - // replace / Remove content - range.setStart(sel.anchorNode, startPos); - range.setEnd(sel.anchorNode, stopPos); - range.deleteContents(); - } - - // insert - var node = document.createElement('span'); - node.setAttribute('class', 'mention-area'); - node.innerHTML = toTextHtml; - range.insertNode(node); - range.setEnd(sel.focusNode, range.endContainer.length); - - positionInfo.startBefore = startPos; - positionInfo.stopBefore = stopPos; - positionInfo.startAfter = startPos; - positionInfo.stopAfter = startPos + node.innerText.length; - - - // move cursor to end of keyword after replace - var stop = false; - node = $(sel.anchorNode); - while (!stop) { - if (node.next().text().length === 0) { - stop = true; - } else { - node = node.next(); - } - } - - // insert after list - var newElem = $(settings.templates.normalText).insertAfter(node); - - // move caret to after - range = document.createRange(); - range.setStartAfter(newElem.get(0)); - range.setEndAfter(newElem.get(0)); - sel.removeAllRanges(); - sel.addRange(range); - - return positionInfo; - } - - - // Public methods - return { - init: function (domTarget) { - initTextArea(domTarget); - }, - }; - }; - - - /** - * Get current caret / selection absolute position (relative to viewport , not to parent) - * Thank to Tim Down: http://stackoverflow.com/questions/6846230/coordinates-of-selected-text-in-browser-page - * NOTE: This is relative to viewport, not its parent - * @param win - * @returns {{x: number, y: number}} - */ - function getSelectionCoords(win) { - win = win || window; - var doc = win.document; - var sel = doc.selection, range, rects, rect; - var x = 0, y = 0; - if (sel) { - if (sel.type != "Control") { - range = sel.createRange(); - range.collapse(true); - x = range.boundingLeft; - y = range.boundingTop; - } - } else if (win.getSelection) { - sel = win.getSelection(); - if (sel.rangeCount) { - range = sel.getRangeAt(0).cloneRange(); - - if (range.getClientRects) { - range.collapse(true); - rects = range.getClientRects(); - if (rects.length > 0) { - rect = rects[0]; - } - x = rect.left; - y = rect.top; - } - // Fall back to inserting a temporary element - if (x == 0 && y == 0) { - var span = doc.createElement("span"); - if (span.getClientRects) { - // Ensure span has dimensions and position by - // adding a zero-width space character - span.appendChild(doc.createTextNode("\u200b")); - range.insertNode(span); - rect = span.getClientRects()[0]; - x = rect.left; - y = rect.top; - var spanParent = span.parentNode; - spanParent.removeChild(span); - - // Glue any broken text nodes back together - spanParent.normalize(); - } - } - } - } - return {x: x, y: y}; - } - - /** - * Get current caret position in current line (not the current contenteditable) - * @returns {number} - */ - function getSelectionEndPositionInCurrentLine() { - var selectionEndPos = 0; - if (window.getSelection) { - var sel = window.getSelection(); - selectionEndPos = sel.focusOffset; - } - - return selectionEndPos; - } - - // TODO: Add setting option: showMentionedItem :Filter mentioned item, we should not show a item if it already mentioned - // TODO: Elastic search in case of local data (in case of ajax --> depend on server-side) - // TODO: Use higlight - // TODO: Dropdown: scroll to see more item WHEN user press DOWN and reach the end of list. - // TODO: Change to A cross-browser JavaScript range and selection library.: https://github.com/timdown/rangy - -}(jQuery)); diff --git a/worklenz-frontend/src/res/js/smooth-scroll.js b/worklenz-frontend/src/res/js/smooth-scroll.js deleted file mode 100644 index 965dae55..00000000 --- a/worklenz-frontend/src/res/js/smooth-scroll.js +++ /dev/null @@ -1,306 +0,0 @@ -// SmoothScroll v0.9.9 -// Licensed under the terms of the MIT license. - -// People involved -// - Balazs Galambosi: maintainer (CHANGELOG.txt) -// - Patrick Brunner (patrickb1991@gmail.com) -// - Michael Herf: ssc_pulse Algorithm - -function ssc_init() { - if (!document.body) return; - var e = document.body; - var t = document.documentElement; - var n = window.innerHeight; - var r = e.scrollHeight; - ssc_root = document.compatMode.indexOf("CSS") >= 0 ? t : e; - ssc_activeElement = e; - ssc_initdone = true; - if (top != self) { - ssc_frame = true - } else if (r > n && (e.offsetHeight <= n || t.offsetHeight <= n)) { - ssc_root.style.height = "auto"; - if (ssc_root.offsetHeight <= n) { - var i = document.createElement("div"); - i.style.clear = "both"; - e.appendChild(i) - } - } - if (!ssc_fixedback) { - e.style.backgroundAttachment = "scroll"; - t.style.backgroundAttachment = "scroll" - } - if (ssc_keyboardsupport) { - ssc_addEvent("keydown", ssc_keydown) - } -} - -function ssc_scrollArray(e, t, n, r) { - r || (r = 1e3); - ssc_directionCheck(t, n); - ssc_que.push({ - x: t, - y: n, - lastX: t < 0 ? .99 : -.99, - lastY: n < 0 ? .99 : -.99, - start: +(new Date) - }); - if (ssc_pending) { - return - } - var i = function () { - var s = +(new Date); - var o = 0; - var u = 0; - for (var a = 0; a < ssc_que.length; a++) { - var f = ssc_que[a]; - var l = s - f.start; - var c = l >= ssc_animtime; - var h = c ? 1 : l / ssc_animtime; - if (ssc_pulseAlgorithm) { - h = ssc_pulse(h) - } - var p = f.x * h - f.lastX >> 0; - var d = f.y * h - f.lastY >> 0; - o += p; - u += d; - f.lastX += p; - f.lastY += d; - if (c) { - ssc_que.splice(a, 1); - a-- - } - } - if (t) { - var v = e.scrollLeft; - e.scrollLeft += o; - if (o && e.scrollLeft === v) { - t = 0 - } - } - if (n) { - var m = e.scrollTop; - e.scrollTop += u; - if (u && e.scrollTop === m) { - n = 0 - } - } - if (!t && !n) { - ssc_que = [] - } - if (ssc_que.length) { - setTimeout(i, r / ssc_framerate + 1) - } else { - ssc_pending = false - } - }; - setTimeout(i, 0); - ssc_pending = true -} - -function ssc_wheel(e) { - if (!ssc_initdone) { - ssc_init() - } - var t = e.target; - var n = ssc_overflowingAncestor(t); - if (!n || e.defaultPrevented || ssc_isNodeName(ssc_activeElement, "embed") || ssc_isNodeName(t, "embed") && /\.pdf/i.test(t.src)) { - return true - } - var r = e.wheelDeltaX || 0; - var i = e.wheelDeltaY || 0; - if (!r && !i) { - i = e.wheelDelta || 0 - } - if (Math.abs(r) > 1.2) { - r *= ssc_stepsize / 120 - } - if (Math.abs(i) > 1.2) { - i *= ssc_stepsize / 120 - } - ssc_scrollArray(n, -r, -i); - e.preventDefault() -} - -function ssc_keydown(e) { - var t = e.target; - var n = e.ctrlKey || e.altKey || e.metaKey; - if (/input|textarea|embed/i.test(t.nodeName) || t.isContentEditable || e.defaultPrevented || n) { - return true - } - if (ssc_isNodeName(t, "button") && e.keyCode === ssc_key.spacebar) { - return true - } - var r, i = 0, - s = 0; - var o = ssc_overflowingAncestor(ssc_activeElement); - if (o) { - var u = o.clientHeight; - } - if (o == document.body) { - u = window.innerHeight - } - switch (e.keyCode) { - case ssc_key.up: - s = -ssc_arrowscroll; - break; - case ssc_key.down: - s = ssc_arrowscroll; - break; - case ssc_key.spacebar: - r = e.shiftKey ? 1 : -1; - s = -r * u * .9; - break; - case ssc_key.pageup: - s = -u * .9; - break; - case ssc_key.pagedown: - s = u * .9; - break; - case ssc_key.home: - s = -o.scrollTop; - break; - case ssc_key.end: - var a = o.scrollHeight - o.scrollTop - u; - s = a > 0 ? a + 10 : 0; - break; - case ssc_key.left: - i = -ssc_arrowscroll; - break; - case ssc_key.right: - i = ssc_arrowscroll; - break; - default: - return true - } - ssc_scrollArray(o, i, s); - e.preventDefault() -} - -function ssc_mousedown(e) { - ssc_activeElement = e.target -} - -function ssc_setCache(e, t) { - for (var n = e.length; n--;) ssc_cache[ssc_uniqueID(e[n])] = t; - return t -} - -function ssc_overflowingAncestor(e) { - var t = []; - var n = ssc_root.scrollHeight; - do { - var r = ssc_cache[ssc_uniqueID(e)]; - if (r) { - return ssc_setCache(t, r) - } - t.push(e); - if (n === e.scrollHeight) { - if (!ssc_frame || ssc_root.clientHeight + 10 < n) { - return ssc_setCache(t, document.body) - } - } else if (e.clientHeight + 10 < e.scrollHeight) { - overflow = getComputedStyle(e, "").getPropertyValue("overflow"); - if (overflow === "scroll" || overflow === "auto") { - return ssc_setCache(t, e) - } - } - } while (e = e.parentNode) -} - -function ssc_addEvent(e, t, n) { - window.addEventListener(e, t, n || false) -} - -function ssc_removeEvent(e, t, n) { - window.removeEventListener(e, t, n || false) -} - -function ssc_isNodeName(e, t) { - return e.nodeName.toLowerCase() === t.toLowerCase() -} - -function ssc_directionCheck(e, t) { - e = e > 0 ? 1 : -1; - t = t > 0 ? 1 : -1; - if (ssc_direction.x !== e || ssc_direction.y !== t) { - ssc_direction.x = e; - ssc_direction.y = t; - ssc_que = [] - } -} - -function ssc_pulse_(e) { - var t, n, r; - e = e * ssc_pulseScale; - if (e < 1) { - t = e - (1 - Math.exp(-e)) - } else { - n = Math.exp(-1); - e -= 1; - r = 1 - Math.exp(-e); - t = n + r * (1 - n) - } - return t * ssc_pulseNormalize -} - -function ssc_pulse(e) { - if (e >= 1) return 1; - if (e <= 0) return 0; - if (ssc_pulseNormalize == 1) { - ssc_pulseNormalize /= ssc_pulse_(1) - } - return ssc_pulse_(e) -} - -var ssc_framerate = 150; -var ssc_animtime = 500; -var ssc_stepsize = 150; -var ssc_pulseAlgorithm = true; -var ssc_pulseScale = 6; -var ssc_pulseNormalize = 1; -var ssc_keyboardsupport = true; -var ssc_arrowscroll = 50; -var ssc_frame = false; -var ssc_direction = { - x: 0, - y: 0 -}; - -var ssc_initdone = false; -var ssc_fixedback = true; -var ssc_root = document.documentElement; -var ssc_activeElement; -var ssc_key = { - left: 37, - up: 38, - right: 39, - down: 40, - spacebar: 32, - pageup: 33, - pagedown: 34, - end: 35, - home: 36 -}; - -var ssc_que = []; -var ssc_pending = false; -var ssc_cache = {}; - -setInterval(function () { - ssc_cache = {} -}, 10 * 1e3); - -var ssc_uniqueID = function () { - var e = 0; - return function (t) { - return t.ssc_uniqueID || (t.ssc_uniqueID = e++) - } -}(); - -var ischrome = /chrome/.test(navigator.userAgent.toLowerCase()); - -if (ischrome) { - ssc_addEvent("mousedown", ssc_mousedown); - ssc_addEvent("mousewheel", ssc_wheel); - ssc_addEvent("load", ssc_init) -} diff --git a/worklenz-frontend/src/res/scss/antd-variables.scss b/worklenz-frontend/src/res/scss/antd-variables.scss deleted file mode 100644 index 6e311093..00000000 --- a/worklenz-frontend/src/res/scss/antd-variables.scss +++ /dev/null @@ -1,41 +0,0 @@ -:root { - --ant-primary-color: #1890ff; - --ant-primary-color-hover: #40a9ff; - --ant-primary-color-active: #096dd9; - --ant-primary-color-outline: rgba(24, 144, 255, .2); - --ant-primary-1: #e6f7ff; - --ant-primary-2: #bae7ff; - --ant-primary-3: #91d5ff; - --ant-primary-4: #69c0ff; - --ant-primary-5: #40a9ff; - --ant-primary-6: #1890ff; - --ant-primary-7: #096dd9; - --ant-primary-color-deprecated-l-35: #cbe6ff; - --ant-primary-color-deprecated-l-20: #7ec1ff; - --ant-primary-color-deprecated-t-20: #46a6ff; - --ant-primary-color-deprecated-t-50: #8cc8ff; - --ant-primary-color-deprecated-f-12: rgba(24, 144, 255, .12); - --ant-primary-color-active-deprecated-f-30: rgba(230, 247, 255, .3); - --ant-primary-color-active-deprecated-d-02: #dcf4ff; - --ant-success-color: #52c41a; - --ant-success-color-hover: #73d13d; - --ant-success-color-active: #389e0d; - --ant-success-color-outline: rgba(82, 196, 26, .2); - --ant-success-color-deprecated-bg: #f6ffed; - --ant-success-color-deprecated-border: #b7eb8f; - --ant-error-color: #ff4d4f; - --ant-error-color-hover: #ff7875; - --ant-error-color-active: #d9363e; - --ant-error-color-outline: rgba(255, 77, 79, .2); - --ant-error-color-deprecated-bg: #fff2f0; - --ant-error-color-deprecated-border: #ffccc7; - --ant-warning-color: #faad14; - --ant-warning-color-hover: #ffc53d; - --ant-warning-color-active: #d48806; - --ant-warning-color-outline: rgba(250, 173, 20, .2); - --ant-warning-color-deprecated-bg: #fffbe6; - --ant-warning-color-deprecated-border: #ffe58f; - --ant-info-color: #1890ff; - --ant-info-color-deprecated-bg: #e6f7ff; - --ant-info-color-deprecated-border: #91d5ff -} diff --git a/worklenz-frontend/src/res/scss/antd.scss b/worklenz-frontend/src/res/scss/antd.scss deleted file mode 100644 index 3c870b3a..00000000 --- a/worklenz-frontend/src/res/scss/antd.scss +++ /dev/null @@ -1,22275 +0,0 @@ -/*! - * - * antd v4.17.3 - * - * Copyright 2015-present, Alipay, Inc. - * All rights reserved. - * - */ -[class^=ant-]::-ms-clear, -[class*= ant-]::-ms-clear, -[class^=ant-] input::-ms-clear, -[class*= ant-] input::-ms-clear, -[class^=ant-] input::-ms-reveal, -[class*= ant-] input::-ms-reveal { - display: none; -} - -html, -body { - width: 100%; - height: 100%; -} - -input::-ms-clear, -input::-ms-reveal { - display: none; -} - -*, -*::before, -*::after { - box-sizing: border-box; -} - -html { - font-family: sans-serif; - line-height: 1.15; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; - -ms-overflow-style: scrollbar; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -} - -@-ms-viewport { - width: device-width; -} - -body { - margin: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; - font-variant: tabular-nums; - line-height: 1.5715; - background-color: #fff; - font-feature-settings: 'tnum'; -} - -[tabindex='-1']:focus { - outline: none !important; -} - -hr { - box-sizing: content-box; - height: 0; - overflow: visible; -} - -h1, -h2, -h3, -h4, -h5, -h6 { - margin-top: 0; - margin-bottom: 0.5em; - color: rgba(0, 0, 0, 0.85); - font-weight: 500; -} - -p { - margin-top: 0; - margin-bottom: 1em; -} - -abbr[title], -abbr[data-original-title] { - text-decoration: underline; - -webkit-text-decoration: underline dotted; - text-decoration: underline dotted; - border-bottom: 0; - cursor: help; -} - -address { - margin-bottom: 1em; - font-style: normal; - line-height: inherit; -} - -input[type='text'], -input[type='password'], -input[type='number'], -textarea { - -webkit-appearance: none; -} - -ol, -ul, -dl { - margin-top: 0; - margin-bottom: 1em; -} - -ol ol, -ul ul, -ol ul, -ul ol { - margin-bottom: 0; -} - -dt { - font-weight: 500; -} - -dd { - margin-bottom: 0.5em; - margin-left: 0; -} - -blockquote { - margin: 0 0 1em; -} - -dfn { - font-style: italic; -} - -b, -strong { - font-weight: bolder; -} - -small { - font-size: 80%; -} - -sub, -sup { - position: relative; - font-size: 75%; - line-height: 0; - vertical-align: baseline; -} - -sub { - bottom: -0.25em; -} - -sup { - top: -0.5em; -} - -a { - color: #1890ff; - text-decoration: none; - background-color: transparent; - outline: none; - cursor: pointer; - transition: color 0.3s; - -webkit-text-decoration-skip: objects; -} - -a:hover { - color: #40a9ff; -} - -a:active { - color: #096dd9; -} - -a:active, -a:hover { - text-decoration: none; - outline: 0; -} - -a:focus { - text-decoration: none; - outline: 0; -} - -a[disabled] { - color: rgba(0, 0, 0, 0.25); - cursor: not-allowed; -} - -pre, -code, -kbd, -samp { - font-size: 1em; - font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; -} - -pre { - margin-top: 0; - margin-bottom: 1em; - overflow: auto; -} - -figure { - margin: 0 0 1em; -} - -img { - vertical-align: middle; - border-style: none; -} - -svg:not(:root) { - overflow: hidden; -} - -a, -area, -button, -[role='button'], -input:not([type='range']), -label, -select, -summary, -textarea { - touch-action: manipulation; -} - -table { - border-collapse: collapse; -} - -caption { - padding-top: 0.75em; - padding-bottom: 0.3em; - color: rgba(0, 0, 0, 0.45); - text-align: left; - caption-side: bottom; -} - -input, -button, -select, -optgroup, -textarea { - margin: 0; - color: inherit; - font-size: inherit; - font-family: inherit; - line-height: inherit; -} - -button, -input { - overflow: visible; -} - -button, -select { - text-transform: none; -} - -button, -html [type="button"], -[type="reset"], -[type="submit"] { - -webkit-appearance: button; -} - -button::-moz-focus-inner, -[type='button']::-moz-focus-inner, -[type='reset']::-moz-focus-inner, -[type='submit']::-moz-focus-inner { - padding: 0; - border-style: none; -} - -input[type='radio'], -input[type='checkbox'] { - box-sizing: border-box; - padding: 0; -} - -input[type='date'], -input[type='time'], -input[type='datetime-local'], -input[type='month'] { - -webkit-appearance: listbox; -} - -textarea { - overflow: auto; - resize: vertical; -} - -fieldset { - min-width: 0; - margin: 0; - padding: 0; - border: 0; -} - -legend { - display: block; - width: 100%; - max-width: 100%; - margin-bottom: 0.5em; - padding: 0; - color: inherit; - font-size: 1.5em; - line-height: inherit; - white-space: normal; -} - -progress { - vertical-align: baseline; -} - -[type='number']::-webkit-inner-spin-button, -[type='number']::-webkit-outer-spin-button { - height: auto; -} - -[type='search'] { - outline-offset: -2px; - -webkit-appearance: none; -} - -[type='search']::-webkit-search-cancel-button, -[type='search']::-webkit-search-decoration { - -webkit-appearance: none; -} - -::-webkit-file-upload-button { - font: inherit; - -webkit-appearance: button; -} - -output { - display: inline-block; -} - -summary { - display: list-item; -} - -template { - display: none; -} - -[hidden] { - display: none !important; -} - -mark { - padding: 0.2em; - background-color: #feffe6; -} - -::-moz-selection { - color: #fff; - background: #1890ff; -} - -::selection { - color: #fff; - background: #1890ff; -} - -.clearfix::before { - display: table; - content: ''; -} - -.clearfix::after { - display: table; - clear: both; - content: ''; -} - -.anticon { - display: inline-block; - color: inherit; - font-style: normal; - line-height: 0; - text-align: center; - text-transform: none; - vertical-align: -0.125em; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.anticon > * { - line-height: 1; -} - -.anticon svg { - display: inline-block; -} - -.anticon::before { - display: none; -} - -.anticon .anticon-icon { - display: block; -} - -.anticon[tabindex] { - cursor: pointer; -} - -.anticon-spin::before { - display: inline-block; - -webkit-animation: loadingCircle 1s infinite linear; - animation: loadingCircle 1s infinite linear; -} - -.anticon-spin { - display: inline-block; - -webkit-animation: loadingCircle 1s infinite linear; - animation: loadingCircle 1s infinite linear; -} - -.ant-fade-enter, -.ant-fade-appear { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-fade-leave { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-fade-enter.ant-fade-enter-active, -.ant-fade-appear.ant-fade-appear-active { - -webkit-animation-name: antFadeIn; - animation-name: antFadeIn; - -webkit-animation-play-state: running; - animation-play-state: running; -} - -.ant-fade-leave.ant-fade-leave-active { - -webkit-animation-name: antFadeOut; - animation-name: antFadeOut; - -webkit-animation-play-state: running; - animation-play-state: running; - pointer-events: none; -} - -.ant-fade-enter, -.ant-fade-appear { - opacity: 0; - -webkit-animation-timing-function: linear; - animation-timing-function: linear; -} - -.ant-fade-leave { - -webkit-animation-timing-function: linear; - animation-timing-function: linear; -} - -@-webkit-keyframes antFadeIn { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - -@keyframes antFadeIn { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - -@-webkit-keyframes antFadeOut { - 0% { - opacity: 1; - } - 100% { - opacity: 0; - } -} - -@keyframes antFadeOut { - 0% { - opacity: 1; - } - 100% { - opacity: 0; - } -} - -.ant-move-up-enter, -.ant-move-up-appear { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-move-up-leave { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-move-up-enter.ant-move-up-enter-active, -.ant-move-up-appear.ant-move-up-appear-active { - -webkit-animation-name: antMoveUpIn; - animation-name: antMoveUpIn; - -webkit-animation-play-state: running; - animation-play-state: running; -} - -.ant-move-up-leave.ant-move-up-leave-active { - -webkit-animation-name: antMoveUpOut; - animation-name: antMoveUpOut; - -webkit-animation-play-state: running; - animation-play-state: running; - pointer-events: none; -} - -.ant-move-up-enter, -.ant-move-up-appear { - opacity: 0; - -webkit-animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); - animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); -} - -.ant-move-up-leave { - -webkit-animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); - animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); -} - -.ant-move-down-enter, -.ant-move-down-appear { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-move-down-leave { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-move-down-enter.ant-move-down-enter-active, -.ant-move-down-appear.ant-move-down-appear-active { - -webkit-animation-name: antMoveDownIn; - animation-name: antMoveDownIn; - -webkit-animation-play-state: running; - animation-play-state: running; -} - -.ant-move-down-leave.ant-move-down-leave-active { - -webkit-animation-name: antMoveDownOut; - animation-name: antMoveDownOut; - -webkit-animation-play-state: running; - animation-play-state: running; - pointer-events: none; -} - -.ant-move-down-enter, -.ant-move-down-appear { - opacity: 0; - -webkit-animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); - animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); -} - -.ant-move-down-leave { - -webkit-animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); - animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); -} - -.ant-move-left-enter, -.ant-move-left-appear { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-move-left-leave { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-move-left-enter.ant-move-left-enter-active, -.ant-move-left-appear.ant-move-left-appear-active { - -webkit-animation-name: antMoveLeftIn; - animation-name: antMoveLeftIn; - -webkit-animation-play-state: running; - animation-play-state: running; -} - -.ant-move-left-leave.ant-move-left-leave-active { - -webkit-animation-name: antMoveLeftOut; - animation-name: antMoveLeftOut; - -webkit-animation-play-state: running; - animation-play-state: running; - pointer-events: none; -} - -.ant-move-left-enter, -.ant-move-left-appear { - opacity: 0; - -webkit-animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); - animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); -} - -.ant-move-left-leave { - -webkit-animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); - animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); -} - -.ant-move-right-enter, -.ant-move-right-appear { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-move-right-leave { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-move-right-enter.ant-move-right-enter-active, -.ant-move-right-appear.ant-move-right-appear-active { - -webkit-animation-name: antMoveRightIn; - animation-name: antMoveRightIn; - -webkit-animation-play-state: running; - animation-play-state: running; -} - -.ant-move-right-leave.ant-move-right-leave-active { - -webkit-animation-name: antMoveRightOut; - animation-name: antMoveRightOut; - -webkit-animation-play-state: running; - animation-play-state: running; - pointer-events: none; -} - -.ant-move-right-enter, -.ant-move-right-appear { - opacity: 0; - -webkit-animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); - animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); -} - -.ant-move-right-leave { - -webkit-animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); - animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); -} - -@-webkit-keyframes antMoveDownIn { - 0% { - transform: translateY(100%); - transform-origin: 0 0; - opacity: 0; - } - 100% { - transform: translateY(0%); - transform-origin: 0 0; - opacity: 1; - } -} - -@keyframes antMoveDownIn { - 0% { - transform: translateY(100%); - transform-origin: 0 0; - opacity: 0; - } - 100% { - transform: translateY(0%); - transform-origin: 0 0; - opacity: 1; - } -} - -@-webkit-keyframes antMoveDownOut { - 0% { - transform: translateY(0%); - transform-origin: 0 0; - opacity: 1; - } - 100% { - transform: translateY(100%); - transform-origin: 0 0; - opacity: 0; - } -} - -@keyframes antMoveDownOut { - 0% { - transform: translateY(0%); - transform-origin: 0 0; - opacity: 1; - } - 100% { - transform: translateY(100%); - transform-origin: 0 0; - opacity: 0; - } -} - -@-webkit-keyframes antMoveLeftIn { - 0% { - transform: translateX(-100%); - transform-origin: 0 0; - opacity: 0; - } - 100% { - transform: translateX(0%); - transform-origin: 0 0; - opacity: 1; - } -} - -@keyframes antMoveLeftIn { - 0% { - transform: translateX(-100%); - transform-origin: 0 0; - opacity: 0; - } - 100% { - transform: translateX(0%); - transform-origin: 0 0; - opacity: 1; - } -} - -@-webkit-keyframes antMoveLeftOut { - 0% { - transform: translateX(0%); - transform-origin: 0 0; - opacity: 1; - } - 100% { - transform: translateX(-100%); - transform-origin: 0 0; - opacity: 0; - } -} - -@keyframes antMoveLeftOut { - 0% { - transform: translateX(0%); - transform-origin: 0 0; - opacity: 1; - } - 100% { - transform: translateX(-100%); - transform-origin: 0 0; - opacity: 0; - } -} - -@-webkit-keyframes antMoveRightIn { - 0% { - transform: translateX(100%); - transform-origin: 0 0; - opacity: 0; - } - 100% { - transform: translateX(0%); - transform-origin: 0 0; - opacity: 1; - } -} - -@keyframes antMoveRightIn { - 0% { - transform: translateX(100%); - transform-origin: 0 0; - opacity: 0; - } - 100% { - transform: translateX(0%); - transform-origin: 0 0; - opacity: 1; - } -} - -@-webkit-keyframes antMoveRightOut { - 0% { - transform: translateX(0%); - transform-origin: 0 0; - opacity: 1; - } - 100% { - transform: translateX(100%); - transform-origin: 0 0; - opacity: 0; - } -} - -@keyframes antMoveRightOut { - 0% { - transform: translateX(0%); - transform-origin: 0 0; - opacity: 1; - } - 100% { - transform: translateX(100%); - transform-origin: 0 0; - opacity: 0; - } -} - -@-webkit-keyframes antMoveUpIn { - 0% { - transform: translateY(-100%); - transform-origin: 0 0; - opacity: 0; - } - 100% { - transform: translateY(0%); - transform-origin: 0 0; - opacity: 1; - } -} - -@keyframes antMoveUpIn { - 0% { - transform: translateY(-100%); - transform-origin: 0 0; - opacity: 0; - } - 100% { - transform: translateY(0%); - transform-origin: 0 0; - opacity: 1; - } -} - -@-webkit-keyframes antMoveUpOut { - 0% { - transform: translateY(0%); - transform-origin: 0 0; - opacity: 1; - } - 100% { - transform: translateY(-100%); - transform-origin: 0 0; - opacity: 0; - } -} - -@keyframes antMoveUpOut { - 0% { - transform: translateY(0%); - transform-origin: 0 0; - opacity: 1; - } - 100% { - transform: translateY(-100%); - transform-origin: 0 0; - opacity: 0; - } -} - -@-webkit-keyframes loadingCircle { - 100% { - transform: rotate(360deg); - } -} - -@keyframes loadingCircle { - 100% { - transform: rotate(360deg); - } -} - -[ant-click-animating='true'], -[ant-click-animating-without-extra-node='true'] { - position: relative; -} - -html { - --antd-wave-shadow-color: #1890ff; - --scroll-bar: 0; -} - -[ant-click-animating-without-extra-node='true']::after, -.ant-click-animating-node { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - display: block; - border-radius: inherit; - box-shadow: 0 0 0 0 #1890ff; - box-shadow: 0 0 0 0 var(--antd-wave-shadow-color); - opacity: 0.2; - -webkit-animation: fadeEffect 2s cubic-bezier(0.08, 0.82, 0.17, 1), waveEffect 0.4s cubic-bezier(0.08, 0.82, 0.17, 1); - animation: fadeEffect 2s cubic-bezier(0.08, 0.82, 0.17, 1), waveEffect 0.4s cubic-bezier(0.08, 0.82, 0.17, 1); - -webkit-animation-fill-mode: forwards; - animation-fill-mode: forwards; - content: ''; - pointer-events: none; -} - -@-webkit-keyframes waveEffect { - 100% { - box-shadow: 0 0 0 #1890ff; - box-shadow: 0 0 0 6px var(--antd-wave-shadow-color); - } -} - -@keyframes waveEffect { - 100% { - box-shadow: 0 0 0 #1890ff; - box-shadow: 0 0 0 6px var(--antd-wave-shadow-color); - } -} - -@-webkit-keyframes fadeEffect { - 100% { - opacity: 0; - } -} - -@keyframes fadeEffect { - 100% { - opacity: 0; - } -} - -.ant-slide-up-enter, -.ant-slide-up-appear { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-slide-up-leave { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-slide-up-enter.ant-slide-up-enter-active, -.ant-slide-up-appear.ant-slide-up-appear-active { - -webkit-animation-name: antSlideUpIn; - animation-name: antSlideUpIn; - -webkit-animation-play-state: running; - animation-play-state: running; -} - -.ant-slide-up-leave.ant-slide-up-leave-active { - -webkit-animation-name: antSlideUpOut; - animation-name: antSlideUpOut; - -webkit-animation-play-state: running; - animation-play-state: running; - pointer-events: none; -} - -.ant-slide-up-enter, -.ant-slide-up-appear { - opacity: 0; - -webkit-animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); - animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); -} - -.ant-slide-up-leave { - -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); - animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); -} - -.ant-slide-down-enter, -.ant-slide-down-appear { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-slide-down-leave { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-slide-down-enter.ant-slide-down-enter-active, -.ant-slide-down-appear.ant-slide-down-appear-active { - -webkit-animation-name: antSlideDownIn; - animation-name: antSlideDownIn; - -webkit-animation-play-state: running; - animation-play-state: running; -} - -.ant-slide-down-leave.ant-slide-down-leave-active { - -webkit-animation-name: antSlideDownOut; - animation-name: antSlideDownOut; - -webkit-animation-play-state: running; - animation-play-state: running; - pointer-events: none; -} - -.ant-slide-down-enter, -.ant-slide-down-appear { - opacity: 0; - -webkit-animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); - animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); -} - -.ant-slide-down-leave { - -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); - animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); -} - -.ant-slide-left-enter, -.ant-slide-left-appear { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-slide-left-leave { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-slide-left-enter.ant-slide-left-enter-active, -.ant-slide-left-appear.ant-slide-left-appear-active { - -webkit-animation-name: antSlideLeftIn; - animation-name: antSlideLeftIn; - -webkit-animation-play-state: running; - animation-play-state: running; -} - -.ant-slide-left-leave.ant-slide-left-leave-active { - -webkit-animation-name: antSlideLeftOut; - animation-name: antSlideLeftOut; - -webkit-animation-play-state: running; - animation-play-state: running; - pointer-events: none; -} - -.ant-slide-left-enter, -.ant-slide-left-appear { - opacity: 0; - -webkit-animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); - animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); -} - -.ant-slide-left-leave { - -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); - animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); -} - -.ant-slide-right-enter, -.ant-slide-right-appear { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-slide-right-leave { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-slide-right-enter.ant-slide-right-enter-active, -.ant-slide-right-appear.ant-slide-right-appear-active { - -webkit-animation-name: antSlideRightIn; - animation-name: antSlideRightIn; - -webkit-animation-play-state: running; - animation-play-state: running; -} - -.ant-slide-right-leave.ant-slide-right-leave-active { - -webkit-animation-name: antSlideRightOut; - animation-name: antSlideRightOut; - -webkit-animation-play-state: running; - animation-play-state: running; - pointer-events: none; -} - -.ant-slide-right-enter, -.ant-slide-right-appear { - opacity: 0; - -webkit-animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); - animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); -} - -.ant-slide-right-leave { - -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); - animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); -} - -@-webkit-keyframes antSlideUpIn { - 0% { - transform: scaleY(0.8); - transform-origin: 0% 0%; - opacity: 0; - } - 100% { - transform: scaleY(1); - transform-origin: 0% 0%; - opacity: 1; - } -} - -@keyframes antSlideUpIn { - 0% { - transform: scaleY(0.8); - transform-origin: 0% 0%; - opacity: 0; - } - 100% { - transform: scaleY(1); - transform-origin: 0% 0%; - opacity: 1; - } -} - -@-webkit-keyframes antSlideUpOut { - 0% { - transform: scaleY(1); - transform-origin: 0% 0%; - opacity: 1; - } - 100% { - transform: scaleY(0.8); - transform-origin: 0% 0%; - opacity: 0; - } -} - -@keyframes antSlideUpOut { - 0% { - transform: scaleY(1); - transform-origin: 0% 0%; - opacity: 1; - } - 100% { - transform: scaleY(0.8); - transform-origin: 0% 0%; - opacity: 0; - } -} - -@-webkit-keyframes antSlideDownIn { - 0% { - transform: scaleY(0.8); - transform-origin: 100% 100%; - opacity: 0; - } - 100% { - transform: scaleY(1); - transform-origin: 100% 100%; - opacity: 1; - } -} - -@keyframes antSlideDownIn { - 0% { - transform: scaleY(0.8); - transform-origin: 100% 100%; - opacity: 0; - } - 100% { - transform: scaleY(1); - transform-origin: 100% 100%; - opacity: 1; - } -} - -@-webkit-keyframes antSlideDownOut { - 0% { - transform: scaleY(1); - transform-origin: 100% 100%; - opacity: 1; - } - 100% { - transform: scaleY(0.8); - transform-origin: 100% 100%; - opacity: 0; - } -} - -@keyframes antSlideDownOut { - 0% { - transform: scaleY(1); - transform-origin: 100% 100%; - opacity: 1; - } - 100% { - transform: scaleY(0.8); - transform-origin: 100% 100%; - opacity: 0; - } -} - -@-webkit-keyframes antSlideLeftIn { - 0% { - transform: scaleX(0.8); - transform-origin: 0% 0%; - opacity: 0; - } - 100% { - transform: scaleX(1); - transform-origin: 0% 0%; - opacity: 1; - } -} - -@keyframes antSlideLeftIn { - 0% { - transform: scaleX(0.8); - transform-origin: 0% 0%; - opacity: 0; - } - 100% { - transform: scaleX(1); - transform-origin: 0% 0%; - opacity: 1; - } -} - -@-webkit-keyframes antSlideLeftOut { - 0% { - transform: scaleX(1); - transform-origin: 0% 0%; - opacity: 1; - } - 100% { - transform: scaleX(0.8); - transform-origin: 0% 0%; - opacity: 0; - } -} - -@keyframes antSlideLeftOut { - 0% { - transform: scaleX(1); - transform-origin: 0% 0%; - opacity: 1; - } - 100% { - transform: scaleX(0.8); - transform-origin: 0% 0%; - opacity: 0; - } -} - -@-webkit-keyframes antSlideRightIn { - 0% { - transform: scaleX(0.8); - transform-origin: 100% 0%; - opacity: 0; - } - 100% { - transform: scaleX(1); - transform-origin: 100% 0%; - opacity: 1; - } -} - -@keyframes antSlideRightIn { - 0% { - transform: scaleX(0.8); - transform-origin: 100% 0%; - opacity: 0; - } - 100% { - transform: scaleX(1); - transform-origin: 100% 0%; - opacity: 1; - } -} - -@-webkit-keyframes antSlideRightOut { - 0% { - transform: scaleX(1); - transform-origin: 100% 0%; - opacity: 1; - } - 100% { - transform: scaleX(0.8); - transform-origin: 100% 0%; - opacity: 0; - } -} - -@keyframes antSlideRightOut { - 0% { - transform: scaleX(1); - transform-origin: 100% 0%; - opacity: 1; - } - 100% { - transform: scaleX(0.8); - transform-origin: 100% 0%; - opacity: 0; - } -} - -.ant-zoom-enter, -.ant-zoom-appear { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-zoom-leave { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-zoom-enter.ant-zoom-enter-active, -.ant-zoom-appear.ant-zoom-appear-active { - -webkit-animation-name: antZoomIn; - animation-name: antZoomIn; - -webkit-animation-play-state: running; - animation-play-state: running; -} - -.ant-zoom-leave.ant-zoom-leave-active { - -webkit-animation-name: antZoomOut; - animation-name: antZoomOut; - -webkit-animation-play-state: running; - animation-play-state: running; - pointer-events: none; -} - -.ant-zoom-enter, -.ant-zoom-appear { - transform: scale(0); - opacity: 0; - -webkit-animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); - animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); -} - -.ant-zoom-enter-prepare, -.ant-zoom-appear-prepare { - transform: none; -} - -.ant-zoom-leave { - -webkit-animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); - animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); -} - -.ant-zoom-big-enter, -.ant-zoom-big-appear { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-zoom-big-leave { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-zoom-big-enter.ant-zoom-big-enter-active, -.ant-zoom-big-appear.ant-zoom-big-appear-active { - -webkit-animation-name: antZoomBigIn; - animation-name: antZoomBigIn; - -webkit-animation-play-state: running; - animation-play-state: running; -} - -.ant-zoom-big-leave.ant-zoom-big-leave-active { - -webkit-animation-name: antZoomBigOut; - animation-name: antZoomBigOut; - -webkit-animation-play-state: running; - animation-play-state: running; - pointer-events: none; -} - -.ant-zoom-big-enter, -.ant-zoom-big-appear { - transform: scale(0); - opacity: 0; - -webkit-animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); - animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); -} - -.ant-zoom-big-enter-prepare, -.ant-zoom-big-appear-prepare { - transform: none; -} - -.ant-zoom-big-leave { - -webkit-animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); - animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); -} - -.ant-zoom-big-fast-enter, -.ant-zoom-big-fast-appear { - -webkit-animation-duration: 0.1s; - animation-duration: 0.1s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-zoom-big-fast-leave { - -webkit-animation-duration: 0.1s; - animation-duration: 0.1s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-zoom-big-fast-enter.ant-zoom-big-fast-enter-active, -.ant-zoom-big-fast-appear.ant-zoom-big-fast-appear-active { - -webkit-animation-name: antZoomBigIn; - animation-name: antZoomBigIn; - -webkit-animation-play-state: running; - animation-play-state: running; -} - -.ant-zoom-big-fast-leave.ant-zoom-big-fast-leave-active { - -webkit-animation-name: antZoomBigOut; - animation-name: antZoomBigOut; - -webkit-animation-play-state: running; - animation-play-state: running; - pointer-events: none; -} - -.ant-zoom-big-fast-enter, -.ant-zoom-big-fast-appear { - transform: scale(0); - opacity: 0; - -webkit-animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); - animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); -} - -.ant-zoom-big-fast-enter-prepare, -.ant-zoom-big-fast-appear-prepare { - transform: none; -} - -.ant-zoom-big-fast-leave { - -webkit-animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); - animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); -} - -.ant-zoom-up-enter, -.ant-zoom-up-appear { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-zoom-up-leave { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-zoom-up-enter.ant-zoom-up-enter-active, -.ant-zoom-up-appear.ant-zoom-up-appear-active { - -webkit-animation-name: antZoomUpIn; - animation-name: antZoomUpIn; - -webkit-animation-play-state: running; - animation-play-state: running; -} - -.ant-zoom-up-leave.ant-zoom-up-leave-active { - -webkit-animation-name: antZoomUpOut; - animation-name: antZoomUpOut; - -webkit-animation-play-state: running; - animation-play-state: running; - pointer-events: none; -} - -.ant-zoom-up-enter, -.ant-zoom-up-appear { - transform: scale(0); - opacity: 0; - -webkit-animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); - animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); -} - -.ant-zoom-up-enter-prepare, -.ant-zoom-up-appear-prepare { - transform: none; -} - -.ant-zoom-up-leave { - -webkit-animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); - animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); -} - -.ant-zoom-down-enter, -.ant-zoom-down-appear { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-zoom-down-leave { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-zoom-down-enter.ant-zoom-down-enter-active, -.ant-zoom-down-appear.ant-zoom-down-appear-active { - -webkit-animation-name: antZoomDownIn; - animation-name: antZoomDownIn; - -webkit-animation-play-state: running; - animation-play-state: running; -} - -.ant-zoom-down-leave.ant-zoom-down-leave-active { - -webkit-animation-name: antZoomDownOut; - animation-name: antZoomDownOut; - -webkit-animation-play-state: running; - animation-play-state: running; - pointer-events: none; -} - -.ant-zoom-down-enter, -.ant-zoom-down-appear { - transform: scale(0); - opacity: 0; - -webkit-animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); - animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); -} - -.ant-zoom-down-enter-prepare, -.ant-zoom-down-appear-prepare { - transform: none; -} - -.ant-zoom-down-leave { - -webkit-animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); - animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); -} - -.ant-zoom-left-enter, -.ant-zoom-left-appear { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-zoom-left-leave { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-zoom-left-enter.ant-zoom-left-enter-active, -.ant-zoom-left-appear.ant-zoom-left-appear-active { - -webkit-animation-name: antZoomLeftIn; - animation-name: antZoomLeftIn; - -webkit-animation-play-state: running; - animation-play-state: running; -} - -.ant-zoom-left-leave.ant-zoom-left-leave-active { - -webkit-animation-name: antZoomLeftOut; - animation-name: antZoomLeftOut; - -webkit-animation-play-state: running; - animation-play-state: running; - pointer-events: none; -} - -.ant-zoom-left-enter, -.ant-zoom-left-appear { - transform: scale(0); - opacity: 0; - -webkit-animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); - animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); -} - -.ant-zoom-left-enter-prepare, -.ant-zoom-left-appear-prepare { - transform: none; -} - -.ant-zoom-left-leave { - -webkit-animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); - animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); -} - -.ant-zoom-right-enter, -.ant-zoom-right-appear { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-zoom-right-leave { - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-zoom-right-enter.ant-zoom-right-enter-active, -.ant-zoom-right-appear.ant-zoom-right-appear-active { - -webkit-animation-name: antZoomRightIn; - animation-name: antZoomRightIn; - -webkit-animation-play-state: running; - animation-play-state: running; -} - -.ant-zoom-right-leave.ant-zoom-right-leave-active { - -webkit-animation-name: antZoomRightOut; - animation-name: antZoomRightOut; - -webkit-animation-play-state: running; - animation-play-state: running; - pointer-events: none; -} - -.ant-zoom-right-enter, -.ant-zoom-right-appear { - transform: scale(0); - opacity: 0; - -webkit-animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); - animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); -} - -.ant-zoom-right-enter-prepare, -.ant-zoom-right-appear-prepare { - transform: none; -} - -.ant-zoom-right-leave { - -webkit-animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); - animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); -} - -@-webkit-keyframes antZoomIn { - 0% { - transform: scale(0.2); - opacity: 0; - } - 100% { - transform: scale(1); - opacity: 1; - } -} - -@keyframes antZoomIn { - 0% { - transform: scale(0.2); - opacity: 0; - } - 100% { - transform: scale(1); - opacity: 1; - } -} - -@-webkit-keyframes antZoomOut { - 0% { - transform: scale(1); - } - 100% { - transform: scale(0.2); - opacity: 0; - } -} - -@keyframes antZoomOut { - 0% { - transform: scale(1); - } - 100% { - transform: scale(0.2); - opacity: 0; - } -} - -@-webkit-keyframes antZoomBigIn { - 0% { - transform: scale(0.8); - opacity: 0; - } - 100% { - transform: scale(1); - opacity: 1; - } -} - -@keyframes antZoomBigIn { - 0% { - transform: scale(0.8); - opacity: 0; - } - 100% { - transform: scale(1); - opacity: 1; - } -} - -@-webkit-keyframes antZoomBigOut { - 0% { - transform: scale(1); - } - 100% { - transform: scale(0.8); - opacity: 0; - } -} - -@keyframes antZoomBigOut { - 0% { - transform: scale(1); - } - 100% { - transform: scale(0.8); - opacity: 0; - } -} - -@-webkit-keyframes antZoomUpIn { - 0% { - transform: scale(0.8); - transform-origin: 50% 0%; - opacity: 0; - } - 100% { - transform: scale(1); - transform-origin: 50% 0%; - } -} - -@keyframes antZoomUpIn { - 0% { - transform: scale(0.8); - transform-origin: 50% 0%; - opacity: 0; - } - 100% { - transform: scale(1); - transform-origin: 50% 0%; - } -} - -@-webkit-keyframes antZoomUpOut { - 0% { - transform: scale(1); - transform-origin: 50% 0%; - } - 100% { - transform: scale(0.8); - transform-origin: 50% 0%; - opacity: 0; - } -} - -@keyframes antZoomUpOut { - 0% { - transform: scale(1); - transform-origin: 50% 0%; - } - 100% { - transform: scale(0.8); - transform-origin: 50% 0%; - opacity: 0; - } -} - -@-webkit-keyframes antZoomLeftIn { - 0% { - transform: scale(0.8); - transform-origin: 0% 50%; - opacity: 0; - } - 100% { - transform: scale(1); - transform-origin: 0% 50%; - } -} - -@keyframes antZoomLeftIn { - 0% { - transform: scale(0.8); - transform-origin: 0% 50%; - opacity: 0; - } - 100% { - transform: scale(1); - transform-origin: 0% 50%; - } -} - -@-webkit-keyframes antZoomLeftOut { - 0% { - transform: scale(1); - transform-origin: 0% 50%; - } - 100% { - transform: scale(0.8); - transform-origin: 0% 50%; - opacity: 0; - } -} - -@keyframes antZoomLeftOut { - 0% { - transform: scale(1); - transform-origin: 0% 50%; - } - 100% { - transform: scale(0.8); - transform-origin: 0% 50%; - opacity: 0; - } -} - -@-webkit-keyframes antZoomRightIn { - 0% { - transform: scale(0.8); - transform-origin: 100% 50%; - opacity: 0; - } - 100% { - transform: scale(1); - transform-origin: 100% 50%; - } -} - -@keyframes antZoomRightIn { - 0% { - transform: scale(0.8); - transform-origin: 100% 50%; - opacity: 0; - } - 100% { - transform: scale(1); - transform-origin: 100% 50%; - } -} - -@-webkit-keyframes antZoomRightOut { - 0% { - transform: scale(1); - transform-origin: 100% 50%; - } - 100% { - transform: scale(0.8); - transform-origin: 100% 50%; - opacity: 0; - } -} - -@keyframes antZoomRightOut { - 0% { - transform: scale(1); - transform-origin: 100% 50%; - } - 100% { - transform: scale(0.8); - transform-origin: 100% 50%; - opacity: 0; - } -} - -@-webkit-keyframes antZoomDownIn { - 0% { - transform: scale(0.8); - transform-origin: 50% 100%; - opacity: 0; - } - 100% { - transform: scale(1); - transform-origin: 50% 100%; - } -} - -@keyframes antZoomDownIn { - 0% { - transform: scale(0.8); - transform-origin: 50% 100%; - opacity: 0; - } - 100% { - transform: scale(1); - transform-origin: 50% 100%; - } -} - -@-webkit-keyframes antZoomDownOut { - 0% { - transform: scale(1); - transform-origin: 50% 100%; - } - 100% { - transform: scale(0.8); - transform-origin: 50% 100%; - opacity: 0; - } -} - -@keyframes antZoomDownOut { - 0% { - transform: scale(1); - transform-origin: 50% 100%; - } - 100% { - transform: scale(0.8); - transform-origin: 50% 100%; - opacity: 0; - } -} - -.ant-motion-collapse-legacy { - overflow: hidden; -} - -.ant-motion-collapse-legacy-active { - transition: height 0.2s cubic-bezier(0.645, 0.045, 0.355, 1), opacity 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) !important; -} - -.ant-motion-collapse { - overflow: hidden; - transition: height 0.2s cubic-bezier(0.645, 0.045, 0.355, 1), opacity 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) !important; -} - -.ant-affix { - position: fixed; - z-index: 10; -} - -.ant-alert { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - display: flex; - align-items: center; - padding: 8px 15px; - word-wrap: break-word; - border-radius: 2px; -} - -.ant-alert-content { - flex: 1; - min-width: 0; -} - -.ant-alert-icon { - margin-right: 8px; -} - -.ant-alert-description { - display: none; - font-size: 14px; - line-height: 22px; -} - -.ant-alert-success { - background-color: #f6ffed; - border: 1px solid #b7eb8f; -} - -.ant-alert-success .ant-alert-icon { - color: #52c41a; -} - -.ant-alert-info { - background-color: #e6f7ff; - border: 1px solid #91d5ff; -} - -.ant-alert-info .ant-alert-icon { - color: #1890ff; -} - -.ant-alert-warning { - background-color: #fffbe6; - border: 1px solid #ffe58f; -} - -.ant-alert-warning .ant-alert-icon { - color: #faad14; -} - -.ant-alert-error { - background-color: #fff2f0; - border: 1px solid #ffccc7; -} - -.ant-alert-error .ant-alert-icon { - color: #ff4d4f; -} - -.ant-alert-error .ant-alert-description > pre { - margin: 0; - padding: 0; -} - -.ant-alert-action { - margin-left: 8px; -} - -.ant-alert-close-icon { - margin-left: 8px; - padding: 0; - overflow: hidden; - font-size: 12px; - line-height: 12px; - background-color: transparent; - border: none; - outline: none; - cursor: pointer; -} - -.ant-alert-close-icon .anticon-close { - color: rgba(0, 0, 0, 0.45); - transition: color 0.3s; -} - -.ant-alert-close-icon .anticon-close:hover { - color: rgba(0, 0, 0, 0.75); -} - -.ant-alert-close-text { - color: rgba(0, 0, 0, 0.45); - transition: color 0.3s; -} - -.ant-alert-close-text:hover { - color: rgba(0, 0, 0, 0.75); -} - -.ant-alert-with-description { - align-items: flex-start; - padding: 15px 15px 15px 24px; -} - -.ant-alert-with-description.ant-alert-no-icon { - padding: 15px 15px; -} - -.ant-alert-with-description .ant-alert-icon { - margin-right: 15px; - font-size: 24px; -} - -.ant-alert-with-description .ant-alert-message { - display: block; - margin-bottom: 4px; - color: rgba(0, 0, 0, 0.85); - font-size: 16px; -} - -.ant-alert-message { - color: rgba(0, 0, 0, 0.85); -} - -.ant-alert-with-description .ant-alert-description { - display: block; -} - -.ant-alert.ant-alert-motion-leave { - overflow: hidden; - opacity: 1; - transition: max-height 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86), opacity 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86), padding-top 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86), padding-bottom 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86), margin-bottom 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86); -} - -.ant-alert.ant-alert-motion-leave-active { - max-height: 0; - margin-bottom: 0 !important; - padding-top: 0; - padding-bottom: 0; - opacity: 0; -} - -.ant-alert-banner { - margin-bottom: 0; - border: 0; - border-radius: 0; -} - -.ant-anchor { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - padding-left: 2px; -} - -.ant-anchor-wrapper { - margin-left: -4px; - padding-left: 4px; - overflow: auto; - background-color: transparent; -} - -.ant-anchor-ink { - position: absolute; - top: 0; - left: 0; - height: 100%; -} - -.ant-anchor-ink::before { - position: relative; - display: block; - width: 2px; - height: 100%; - margin: 0 auto; - background-color: #f0f0f0; - content: ' '; -} - -.ant-anchor-ink-ball { - position: absolute; - left: 50%; - display: none; - width: 8px; - height: 8px; - background-color: #fff; - border: 2px solid #1890ff; - border-radius: 8px; - transform: translateX(-50%); - transition: top 0.3s ease-in-out; -} - -.ant-anchor-ink-ball.visible { - display: inline-block; -} - -.ant-anchor-fixed .ant-anchor-ink .ant-anchor-ink-ball { - display: none; -} - -.ant-anchor-link { - padding: 7px 0 7px 16px; - line-height: 1.143; -} - -.ant-anchor-link-title { - position: relative; - display: block; - margin-bottom: 6px; - overflow: hidden; - color: rgba(0, 0, 0, 0.85); - white-space: nowrap; - text-overflow: ellipsis; - transition: all 0.3s; -} - -.ant-anchor-link-title:only-child { - margin-bottom: 0; -} - -.ant-anchor-link-active > .ant-anchor-link-title { - color: #1890ff; -} - -.ant-anchor-link .ant-anchor-link { - padding-top: 5px; - padding-bottom: 5px; -} - -.ant-select-auto-complete { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; -} - -.ant-select-auto-complete .ant-select-clear { - right: 13px; -} - -.ant-select-single .ant-select-selector { - display: flex; -} - -.ant-select-single .ant-select-selector .ant-select-selection-search { - position: absolute; - top: 0; - right: 11px; - bottom: 0; - left: 11px; -} - -.ant-select-single .ant-select-selector .ant-select-selection-search-input { - width: 100%; -} - -.ant-select-single .ant-select-selector .ant-select-selection-item, -.ant-select-single .ant-select-selector .ant-select-selection-placeholder { - padding: 0; - line-height: 30px; - transition: all 0.3s; -} - -@supports (-moz-appearance: meterbar) { - .ant-select-single .ant-select-selector .ant-select-selection-item, - .ant-select-single .ant-select-selector .ant-select-selection-placeholder { - line-height: 30px; - } -} - -.ant-select-single .ant-select-selector .ant-select-selection-item { - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-select-single .ant-select-selector .ant-select-selection-placeholder { - transition: none; - pointer-events: none; -} - -.ant-select-single .ant-select-selector::after, -.ant-select-single .ant-select-selector .ant-select-selection-item::after, -.ant-select-single .ant-select-selector .ant-select-selection-placeholder::after { - display: inline-block; - width: 0; - visibility: hidden; - content: '\a0'; -} - -.ant-select-single.ant-select-show-arrow .ant-select-selection-search { - right: 25px; -} - -.ant-select-single.ant-select-show-arrow .ant-select-selection-item, -.ant-select-single.ant-select-show-arrow .ant-select-selection-placeholder { - padding-right: 18px; -} - -.ant-select-single.ant-select-open .ant-select-selection-item { - color: #bfbfbf; -} - -.ant-select-single:not(.ant-select-customize-input) .ant-select-selector { - width: 100%; - height: 32px; - padding: 0 11px; -} - -.ant-select-single:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-search-input { - height: 30px; -} - -.ant-select-single:not(.ant-select-customize-input) .ant-select-selector::after { - line-height: 30px; -} - -.ant-select-single.ant-select-customize-input .ant-select-selector::after { - display: none; -} - -.ant-select-single.ant-select-customize-input .ant-select-selector .ant-select-selection-search { - position: static; - width: 100%; -} - -.ant-select-single.ant-select-customize-input .ant-select-selector .ant-select-selection-placeholder { - position: absolute; - right: 0; - left: 0; - padding: 0 11px; -} - -.ant-select-single.ant-select-customize-input .ant-select-selector .ant-select-selection-placeholder::after { - display: none; -} - -.ant-select-single.ant-select-lg:not(.ant-select-customize-input) .ant-select-selector { - height: 40px; -} - -.ant-select-single.ant-select-lg:not(.ant-select-customize-input) .ant-select-selector::after, -.ant-select-single.ant-select-lg:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-item, -.ant-select-single.ant-select-lg:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-placeholder { - line-height: 38px; -} - -.ant-select-single.ant-select-lg:not(.ant-select-customize-input):not(.ant-select-customize-input) .ant-select-selection-search-input { - height: 38px; -} - -.ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector { - height: 24px; -} - -.ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector::after, -.ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-item, -.ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-placeholder { - line-height: 22px; -} - -.ant-select-single.ant-select-sm:not(.ant-select-customize-input):not(.ant-select-customize-input) .ant-select-selection-search-input { - height: 22px; -} - -.ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selection-search { - right: 7px; - left: 7px; -} - -.ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector { - padding: 0 7px; -} - -.ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-search { - right: 28px; -} - -.ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-item, -.ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-placeholder { - padding-right: 21px; -} - -.ant-select-single.ant-select-lg:not(.ant-select-customize-input) .ant-select-selector { - padding: 0 11px; -} - -/** - * Do not merge `height` & `line-height` under style with `selection` & `search`, - * since chrome may update to redesign with its align logic. - */ -.ant-select-selection-overflow { - position: relative; - display: flex; - flex: auto; - flex-wrap: wrap; - max-width: 100%; -} - -.ant-select-selection-overflow-item { - flex: none; - align-self: center; - max-width: 100%; -} - -.ant-select-multiple .ant-select-selector { - display: flex; - flex-wrap: wrap; - align-items: center; - padding: 1px 4px; -} - -.ant-select-show-search.ant-select-multiple .ant-select-selector { - cursor: text; -} - -.ant-select-disabled.ant-select-multiple .ant-select-selector { - background: #f5f5f5; - cursor: not-allowed; -} - -.ant-select-multiple .ant-select-selector::after { - display: inline-block; - width: 0; - margin: 2px 0; - line-height: 24px; - content: '\a0'; -} - -.ant-select-multiple.ant-select-show-arrow .ant-select-selector, -.ant-select-multiple.ant-select-allow-clear .ant-select-selector { - padding-right: 24px; -} - -.ant-select-multiple .ant-select-selection-item { - position: relative; - display: flex; - flex: none; - box-sizing: border-box; - max-width: 100%; - height: 24px; - margin-top: 2px; - margin-bottom: 2px; - line-height: 22px; - background: #f5f5f5; - border: 1px solid #f0f0f0; - border-radius: 2px; - cursor: default; - transition: font-size 0.3s, line-height 0.3s, height 0.3s; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - -webkit-margin-end: 4px; - margin-inline-end: 4px; - -webkit-padding-start: 8px; - padding-inline-start: 8px; - -webkit-padding-end: 4px; - padding-inline-end: 4px; -} - -.ant-select-disabled.ant-select-multiple .ant-select-selection-item { - color: #bfbfbf; - border-color: #d9d9d9; - cursor: not-allowed; -} - -.ant-select-multiple .ant-select-selection-item-content { - display: inline-block; - margin-right: 4px; - overflow: hidden; - white-space: pre; - text-overflow: ellipsis; -} - -.ant-select-multiple .ant-select-selection-item-remove { - color: inherit; - font-style: normal; - line-height: 0; - text-align: center; - text-transform: none; - vertical-align: -0.125em; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - display: inline-block; - color: rgba(0, 0, 0, 0.45); - font-weight: bold; - font-size: 10px; - line-height: inherit; - cursor: pointer; -} - -.ant-select-multiple .ant-select-selection-item-remove > * { - line-height: 1; -} - -.ant-select-multiple .ant-select-selection-item-remove svg { - display: inline-block; -} - -.ant-select-multiple .ant-select-selection-item-remove::before { - display: none; -} - -.ant-select-multiple .ant-select-selection-item-remove .ant-select-multiple .ant-select-selection-item-remove-icon { - display: block; -} - -.ant-select-multiple .ant-select-selection-item-remove > .anticon { - vertical-align: -0.2em; -} - -.ant-select-multiple .ant-select-selection-item-remove:hover { - color: rgba(0, 0, 0, 0.75); -} - -.ant-select-multiple .ant-select-selection-overflow-item + .ant-select-selection-overflow-item .ant-select-selection-search { - -webkit-margin-start: 0; - margin-inline-start: 0; -} - -.ant-select-multiple .ant-select-selection-search { - position: relative; - max-width: 100%; - margin-top: 2px; - margin-bottom: 2px; - -webkit-margin-start: 7px; - margin-inline-start: 7px; -} - -.ant-select-multiple .ant-select-selection-search-input, -.ant-select-multiple .ant-select-selection-search-mirror { - height: 24px; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; - line-height: 24px; - transition: all 0.3s; -} - -.ant-select-multiple .ant-select-selection-search-input { - width: 100%; - min-width: 4.1px; -} - -.ant-select-multiple .ant-select-selection-search-mirror { - position: absolute; - top: 0; - left: 0; - z-index: 999; - white-space: pre; - visibility: hidden; -} - -.ant-select-multiple .ant-select-selection-placeholder { - position: absolute; - top: 50%; - right: 11px; - left: 11px; - transform: translateY(-50%); - transition: all 0.3s; -} - -.ant-select-multiple.ant-select-lg .ant-select-selector::after { - line-height: 32px; -} - -.ant-select-multiple.ant-select-lg .ant-select-selection-item { - height: 32px; - line-height: 30px; -} - -.ant-select-multiple.ant-select-lg .ant-select-selection-search { - height: 32px; - line-height: 32px; -} - -.ant-select-multiple.ant-select-lg .ant-select-selection-search-input, -.ant-select-multiple.ant-select-lg .ant-select-selection-search-mirror { - height: 32px; - line-height: 30px; -} - -.ant-select-multiple.ant-select-sm .ant-select-selector::after { - line-height: 16px; -} - -.ant-select-multiple.ant-select-sm .ant-select-selection-item { - height: 16px; - line-height: 14px; -} - -.ant-select-multiple.ant-select-sm .ant-select-selection-search { - height: 16px; - line-height: 16px; -} - -.ant-select-multiple.ant-select-sm .ant-select-selection-search-input, -.ant-select-multiple.ant-select-sm .ant-select-selection-search-mirror { - height: 16px; - line-height: 14px; -} - -.ant-select-multiple.ant-select-sm .ant-select-selection-placeholder { - left: 7px; -} - -.ant-select-multiple.ant-select-sm .ant-select-selection-search { - -webkit-margin-start: 3px; - margin-inline-start: 3px; -} - -.ant-select-multiple.ant-select-lg .ant-select-selection-item { - height: 32px; - line-height: 32px; -} - -.ant-select-disabled .ant-select-selection-item-remove { - display: none; -} - -/* Reset search input style */ -.ant-select { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - display: inline-block; - cursor: pointer; -} - -.ant-select:not(.ant-select-customize-input) .ant-select-selector { - position: relative; - background-color: #fff; - border: 1px solid #d9d9d9; - border-radius: 2px; - transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); -} - -.ant-select:not(.ant-select-customize-input) .ant-select-selector input { - cursor: pointer; -} - -.ant-select-show-search.ant-select:not(.ant-select-customize-input) .ant-select-selector { - cursor: text; -} - -.ant-select-show-search.ant-select:not(.ant-select-customize-input) .ant-select-selector input { - cursor: auto; -} - -.ant-select-focused:not(.ant-select-disabled).ant-select:not(.ant-select-customize-input) .ant-select-selector { - border-color: #40a9ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-select-disabled.ant-select:not(.ant-select-customize-input) .ant-select-selector { - color: rgba(0, 0, 0, 0.25); - background: #f5f5f5; - cursor: not-allowed; -} - -.ant-select-multiple.ant-select-disabled.ant-select:not(.ant-select-customize-input) .ant-select-selector { - background: #f5f5f5; -} - -.ant-select-disabled.ant-select:not(.ant-select-customize-input) .ant-select-selector input { - cursor: not-allowed; -} - -.ant-select:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-search-input { - margin: 0; - padding: 0; - background: transparent; - border: none; - outline: none; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; -} - -.ant-select:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-search-input::-webkit-search-cancel-button { - display: none; - -webkit-appearance: none; -} - -.ant-select:not(.ant-select-disabled):hover .ant-select-selector { - border-color: #40a9ff; - border-right-width: 1px !important; -} - -.ant-select-selection-item { - flex: 1; - overflow: hidden; - font-weight: normal; - white-space: nowrap; - text-overflow: ellipsis; -} - -@media all and (-ms-high-contrast: none) { - .ant-select-selection-item *::-ms-backdrop, - .ant-select-selection-item { - flex: auto; - } -} - -.ant-select-selection-placeholder { - flex: 1; - overflow: hidden; - color: #bfbfbf; - white-space: nowrap; - text-overflow: ellipsis; - pointer-events: none; -} - -@media all and (-ms-high-contrast: none) { - .ant-select-selection-placeholder *::-ms-backdrop, - .ant-select-selection-placeholder { - flex: auto; - } -} - -.ant-select-arrow { - display: inline-block; - color: inherit; - font-style: normal; - line-height: 0; - text-transform: none; - vertical-align: -0.125em; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - position: absolute; - top: 50%; - right: 11px; - width: 12px; - height: 12px; - margin-top: -6px; - color: rgba(0, 0, 0, 0.25); - font-size: 12px; - line-height: 1; - text-align: center; - pointer-events: none; -} - -.ant-select-arrow > * { - line-height: 1; -} - -.ant-select-arrow svg { - display: inline-block; -} - -.ant-select-arrow::before { - display: none; -} - -.ant-select-arrow .ant-select-arrow-icon { - display: block; -} - -.ant-select-arrow .anticon { - vertical-align: top; - transition: transform 0.3s; -} - -.ant-select-arrow .anticon > svg { - vertical-align: top; -} - -.ant-select-arrow .anticon:not(.ant-select-suffix) { - pointer-events: auto; -} - -.ant-select-disabled .ant-select-arrow { - cursor: not-allowed; -} - -.ant-select-clear { - position: absolute; - top: 50%; - right: 11px; - z-index: 1; - display: inline-block; - width: 12px; - height: 12px; - margin-top: -6px; - color: rgba(0, 0, 0, 0.25); - font-size: 12px; - font-style: normal; - line-height: 1; - text-align: center; - text-transform: none; - background: #fff; - cursor: pointer; - opacity: 0; - transition: color 0.3s ease, opacity 0.15s ease; - text-rendering: auto; -} - -.ant-select-clear::before { - display: block; -} - -.ant-select-clear:hover { - color: rgba(0, 0, 0, 0.45); -} - -.ant-select:hover .ant-select-clear { - opacity: 1; -} - -.ant-select-dropdown { - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: absolute; - top: -9999px; - left: -9999px; - z-index: 1050; - box-sizing: border-box; - padding: 4px 0; - overflow: hidden; - font-size: 14px; - font-variant: initial; - background-color: #fff; - border-radius: 2px; - outline: none; - box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); -} - -.ant-select-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-select-dropdown-placement-bottomLeft, -.ant-select-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-select-dropdown-placement-bottomLeft { - -webkit-animation-name: antSlideUpIn; - animation-name: antSlideUpIn; -} - -.ant-select-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-select-dropdown-placement-topLeft, -.ant-select-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-select-dropdown-placement-topLeft { - -webkit-animation-name: antSlideDownIn; - animation-name: antSlideDownIn; -} - -.ant-select-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-select-dropdown-placement-bottomLeft { - -webkit-animation-name: antSlideUpOut; - animation-name: antSlideUpOut; -} - -.ant-select-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-select-dropdown-placement-topLeft { - -webkit-animation-name: antSlideDownOut; - animation-name: antSlideDownOut; -} - -.ant-select-dropdown-hidden { - display: none; -} - -.ant-select-dropdown-empty { - color: rgba(0, 0, 0, 0.25); -} - -.ant-select-item-empty { - position: relative; - display: block; - min-height: 32px; - padding: 5px 12px; - color: rgba(0, 0, 0, 0.85); - font-weight: normal; - font-size: 14px; - line-height: 22px; - color: rgba(0, 0, 0, 0.25); -} - -.ant-select-item { - position: relative; - display: block; - min-height: 32px; - padding: 5px 12px; - color: rgba(0, 0, 0, 0.85); - font-weight: normal; - font-size: 14px; - line-height: 22px; - cursor: pointer; - transition: background 0.3s ease; -} - -.ant-select-item-group { - color: rgba(0, 0, 0, 0.45); - font-size: 12px; - cursor: default; -} - -.ant-select-item-option { - display: flex; -} - -.ant-select-item-option-content { - flex: auto; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - -.ant-select-item-option-state { - flex: none; -} - -.ant-select-item-option-active:not(.ant-select-item-option-disabled) { - background-color: #f5f5f5; -} - -.ant-select-item-option-selected:not(.ant-select-item-option-disabled) { - color: rgba(0, 0, 0, 0.85); - font-weight: 600; - background-color: #e6f7ff; -} - -.ant-select-item-option-selected:not(.ant-select-item-option-disabled) .ant-select-item-option-state { - color: #1890ff; -} - -.ant-select-item-option-disabled { - color: rgba(0, 0, 0, 0.25); - cursor: not-allowed; -} - -.ant-select-item-option-disabled.ant-select-item-option-selected { - background-color: #f5f5f5; -} - -.ant-select-item-option-grouped { - padding-left: 24px; -} - -.ant-select-lg { - font-size: 16px; -} - -.ant-select-borderless .ant-select-selector { - background-color: transparent !important; - border-color: transparent !important; - box-shadow: none !important; -} - -.ant-empty { - margin: 0 8px; - font-size: 14px; - line-height: 1.5715; - text-align: center; -} - -.ant-empty-image { - height: 100px; - margin-bottom: 8px; -} - -.ant-empty-image img { - height: 100%; -} - -.ant-empty-image svg { - height: 100%; - margin: auto; -} - -.ant-empty-footer { - margin-top: 16px; -} - -.ant-empty-normal { - margin: 32px 0; - color: rgba(0, 0, 0, 0.25); -} - -.ant-empty-normal .ant-empty-image { - height: 40px; -} - -.ant-empty-small { - margin: 8px 0; - color: rgba(0, 0, 0, 0.25); -} - -.ant-empty-small .ant-empty-image { - height: 35px; -} - -.ant-empty-img-default-ellipse { - fill: #f5f5f5; - fill-opacity: 0.8; -} - -.ant-empty-img-default-path-1 { - fill: #aeb8c2; -} - -.ant-empty-img-default-path-2 { - fill: url('#linearGradient-1'); -} - -.ant-empty-img-default-path-3 { - fill: #f5f5f7; -} - -.ant-empty-img-default-path-4 { - fill: #dce0e6; -} - -.ant-empty-img-default-path-5 { - fill: #dce0e6; -} - -.ant-empty-img-default-g { - fill: #fff; -} - -.ant-empty-img-simple-ellipse { - fill: #f5f5f5; -} - -.ant-empty-img-simple-g { - stroke: #d9d9d9; -} - -.ant-empty-img-simple-path { - fill: #fafafa; -} - -.ant-avatar { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - display: inline-block; - overflow: hidden; - color: #fff; - white-space: nowrap; - text-align: center; - vertical-align: middle; - background: #ccc; - width: 32px; - height: 32px; - line-height: 32px; - border-radius: 50%; -} - -.ant-avatar-image { - background: transparent; -} - -.ant-avatar .ant-image-img { - display: block; -} - -.ant-avatar-string { - position: absolute; - left: 50%; - transform-origin: 0 center; -} - -.ant-avatar.ant-avatar-icon { - font-size: 18px; -} - -.ant-avatar.ant-avatar-icon > .anticon { - margin: 0; -} - -.ant-avatar-lg { - width: 40px; - height: 40px; - line-height: 40px; - border-radius: 50%; -} - -.ant-avatar-lg-string { - position: absolute; - left: 50%; - transform-origin: 0 center; -} - -.ant-avatar-lg.ant-avatar-icon { - font-size: 24px; -} - -.ant-avatar-lg.ant-avatar-icon > .anticon { - margin: 0; -} - -.ant-avatar-sm { - width: 24px; - height: 24px; - line-height: 24px; - border-radius: 50%; -} - -.ant-avatar-sm-string { - position: absolute; - left: 50%; - transform-origin: 0 center; -} - -.ant-avatar-sm.ant-avatar-icon { - font-size: 14px; -} - -.ant-avatar-sm.ant-avatar-icon > .anticon { - margin: 0; -} - -.ant-avatar-square { - border-radius: 2px; -} - -.ant-avatar > img { - display: block; - width: 100%; - height: 100%; - -o-object-fit: cover; - object-fit: cover; -} - -.ant-avatar-group { - display: inline-flex; -} - -.ant-avatar-group .ant-avatar { - border: 1px solid #fff; -} - -.ant-avatar-group .ant-avatar:not(:first-child) { - margin-left: -8px; -} - -.ant-avatar-group-popover .ant-avatar + .ant-avatar { - margin-left: 3px; -} - -// -//.ant-popover { -// box-sizing: border-box; -// margin: 0; -// padding: 0; -// color: rgba(0, 0, 0, 0.85); -// font-size: 14px; -// font-variant: tabular-nums; -// line-height: 1.5715; -// list-style: none; -// font-feature-settings: 'tnum'; -// position: absolute; -// top: 0; -// left: 0; -// z-index: 1030; -// font-weight: normal; -// white-space: normal; -// text-align: left; -// cursor: auto; -// -webkit-user-select: text; -// -moz-user-select: text; -// -ms-user-select: text; -// user-select: text; -//} -// -//.ant-popover::after { -// position: absolute; -// background: rgba(255, 255, 255, 0.01); -// content: ''; -//} -// -//.ant-popover-hidden { -// display: none; -//} -// -//.ant-popover-placement-top, -//.ant-popover-placement-topLeft, -//.ant-popover-placement-topRight { -// padding-bottom: 10px; -//} -// -//.ant-popover-placement-right, -//.ant-popover-placement-rightTop, -//.ant-popover-placement-rightBottom { -// padding-left: 10px; -//} -// -//.ant-popover-placement-bottom, -//.ant-popover-placement-bottomLeft, -//.ant-popover-placement-bottomRight { -// padding-top: 10px; -//} -// -//.ant-popover-placement-left, -//.ant-popover-placement-leftTop, -//.ant-popover-placement-leftBottom { -// padding-right: 10px; -//} -// -//.ant-popover-inner { -// background-color: #fff; -// background-clip: padding-box; -// border-radius: 2px; -// box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); -// box-shadow: 0 0 8px rgba(0, 0, 0, 0.15) \9 -//; -//} -// -//@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { -// .ant-popover { -// /* IE10+ */ -// } -// .ant-popover-inner { -// box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); -// } -//} -// -//.ant-popover-title { -// min-width: 177px; -// min-height: 32px; -// margin: 0; -// padding: 5px 16px 4px; -// color: rgba(0, 0, 0, 0.85); -// font-weight: 500; -// border-bottom: 1px solid #f0f0f0; -//} -// -//.ant-popover-inner-content { -// padding: 12px 16px; -// color: rgba(0, 0, 0, 0.85); -//} -// -//.ant-popover-message { -// position: relative; -// padding: 4px 0 12px; -// color: rgba(0, 0, 0, 0.85); -// font-size: 14px; -//} -// -//.ant-popover-message > .anticon { -// position: absolute; -// top: 8.0005px; -// color: #faad14; -// font-size: 14px; -//} -// -//.ant-popover-message-title { -// padding-left: 22px; -//} -// -//.ant-popover-buttons { -// margin-bottom: 4px; -// text-align: right; -//} -// -//.ant-popover-buttons button { -// margin-left: 8px; -//} -// -//.ant-popover-arrow { -// position: absolute; -// display: block; -// width: 8.48528137px; -// height: 8.48528137px; -// overflow: hidden; -// background: transparent; -// pointer-events: none; -//} -// -//.ant-popover-arrow-content { -// position: absolute; -// top: 0; -// right: 0; -// bottom: 0; -// left: 0; -// display: block; -// width: 6px; -// height: 6px; -// margin: auto; -// background-color: #fff; -// content: ''; -// pointer-events: auto; -//} -// -//.ant-popover-placement-top .ant-popover-arrow, -//.ant-popover-placement-topLeft .ant-popover-arrow, -//.ant-popover-placement-topRight .ant-popover-arrow { -// bottom: 1.51471863px; -//} -// -//.ant-popover-placement-top .ant-popover-arrow-content, -//.ant-popover-placement-topLeft .ant-popover-arrow-content, -//.ant-popover-placement-topRight .ant-popover-arrow-content { -// box-shadow: 3px 3px 7px rgba(0, 0, 0, 0.07); -// transform: translateY(-4.24264069px) rotate(45deg); -//} -// -//.ant-popover-placement-top .ant-popover-arrow { -// left: 50%; -// transform: translateX(-50%); -//} -// -//.ant-popover-placement-topLeft .ant-popover-arrow { -// left: 16px; -//} -// -//.ant-popover-placement-topRight .ant-popover-arrow { -// right: 16px; -//} -// -//.ant-popover-placement-right .ant-popover-arrow, -//.ant-popover-placement-rightTop .ant-popover-arrow, -//.ant-popover-placement-rightBottom .ant-popover-arrow { -// left: 1.51471863px; -//} -// -//.ant-popover-placement-right .ant-popover-arrow-content, -//.ant-popover-placement-rightTop .ant-popover-arrow-content, -//.ant-popover-placement-rightBottom .ant-popover-arrow-content { -// box-shadow: -3px 3px 7px rgba(0, 0, 0, 0.07); -// transform: translateX(4.24264069px) rotate(45deg); -//} -// -//.ant-popover-placement-right .ant-popover-arrow { -// top: 50%; -// transform: translateY(-50%); -//} -// -//.ant-popover-placement-rightTop .ant-popover-arrow { -// top: 12px; -//} -// -//.ant-popover-placement-rightBottom .ant-popover-arrow { -// bottom: 12px; -//} -// -//.ant-popover-placement-bottom .ant-popover-arrow, -//.ant-popover-placement-bottomLeft .ant-popover-arrow, -//.ant-popover-placement-bottomRight .ant-popover-arrow { -// top: 1.51471863px; -//} -// -//.ant-popover-placement-bottom .ant-popover-arrow-content, -//.ant-popover-placement-bottomLeft .ant-popover-arrow-content, -//.ant-popover-placement-bottomRight .ant-popover-arrow-content { -// box-shadow: -2px -2px 5px rgba(0, 0, 0, 0.06); -// transform: translateY(4.24264069px) rotate(45deg); -//} -// -//.ant-popover-placement-bottom .ant-popover-arrow { -// left: 50%; -// transform: translateX(-50%); -//} -// -//.ant-popover-placement-bottomLeft .ant-popover-arrow { -// left: 16px; -//} -// -//.ant-popover-placement-bottomRight .ant-popover-arrow { -// right: 16px; -//} -// -//.ant-popover-placement-left .ant-popover-arrow, -//.ant-popover-placement-leftTop .ant-popover-arrow, -//.ant-popover-placement-leftBottom .ant-popover-arrow { -// right: 1.51471863px; -//} -// -//.ant-popover-placement-left .ant-popover-arrow-content, -//.ant-popover-placement-leftTop .ant-popover-arrow-content, -//.ant-popover-placement-leftBottom .ant-popover-arrow-content { -// box-shadow: 3px -3px 7px rgba(0, 0, 0, 0.07); -// transform: translateX(-4.24264069px) rotate(45deg); -//} -// -//.ant-popover-placement-left .ant-popover-arrow { -// top: 50%; -// transform: translateY(-50%); -//} -// -//.ant-popover-placement-leftTop .ant-popover-arrow { -// top: 12px; -//} -// -//.ant-popover-placement-leftBottom .ant-popover-arrow { -// bottom: 12px; -//} -// -//.ant-popover-pink .ant-popover-inner { -// background-color: #eb2f96; -//} -// -//.ant-popover-pink .ant-popover-arrow-content { -// background-color: #eb2f96; -//} -// -//.ant-popover-magenta .ant-popover-inner { -// background-color: #eb2f96; -//} -// -//.ant-popover-magenta .ant-popover-arrow-content { -// background-color: #eb2f96; -//} -// -//.ant-popover-red .ant-popover-inner { -// background-color: #f5222d; -//} -// -//.ant-popover-red .ant-popover-arrow-content { -// background-color: #f5222d; -//} -// -//.ant-popover-volcano .ant-popover-inner { -// background-color: #fa541c; -//} -// -//.ant-popover-volcano .ant-popover-arrow-content { -// background-color: #fa541c; -//} -// -//.ant-popover-orange .ant-popover-inner { -// background-color: #fa8c16; -//} -// -//.ant-popover-orange .ant-popover-arrow-content { -// background-color: #fa8c16; -//} -// -//.ant-popover-yellow .ant-popover-inner { -// background-color: #fadb14; -//} -// -//.ant-popover-yellow .ant-popover-arrow-content { -// background-color: #fadb14; -//} -// -//.ant-popover-gold .ant-popover-inner { -// background-color: #faad14; -//} -// -//.ant-popover-gold .ant-popover-arrow-content { -// background-color: #faad14; -//} -// -//.ant-popover-cyan .ant-popover-inner { -// background-color: #13c2c2; -//} -// -//.ant-popover-cyan .ant-popover-arrow-content { -// background-color: #13c2c2; -//} -// -//.ant-popover-lime .ant-popover-inner { -// background-color: #a0d911; -//} -// -//.ant-popover-lime .ant-popover-arrow-content { -// background-color: #a0d911; -//} -// -//.ant-popover-green .ant-popover-inner { -// background-color: #52c41a; -//} -// -//.ant-popover-green .ant-popover-arrow-content { -// background-color: #52c41a; -//} -// -//.ant-popover-blue .ant-popover-inner { -// background-color: #1890ff; -//} -// -//.ant-popover-blue .ant-popover-arrow-content { -// background-color: #1890ff; -//} -// -//.ant-popover-geekblue .ant-popover-inner { -// background-color: #2f54eb; -//} -// -//.ant-popover-geekblue .ant-popover-arrow-content { -// background-color: #2f54eb; -//} -// -//.ant-popover-purple .ant-popover-inner { -// background-color: #722ed1; -//} -// -//.ant-popover-purple .ant-popover-arrow-content { -// background-color: #722ed1; -//} -// - -.ant-back-top { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: fixed; - right: 100px; - bottom: 50px; - z-index: 10; - width: 40px; - height: 40px; - cursor: pointer; -} - -.ant-back-top:empty { - display: none; -} - -.ant-back-top-content { - width: 40px; - height: 40px; - overflow: hidden; - color: #fff; - text-align: center; - background-color: rgba(0, 0, 0, 0.45); - border-radius: 20px; - transition: all 0.3s; -} - -.ant-back-top-content:hover { - background-color: rgba(0, 0, 0, 0.85); - transition: all 0.3s; -} - -.ant-back-top-icon { - font-size: 24px; - line-height: 40px; -} - -@media screen and (max-width: 768px) { - .ant-back-top { - right: 60px; - } -} - -@media screen and (max-width: 480px) { - .ant-back-top { - right: 20px; - } -} - -.ant-badge { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - display: inline-block; - line-height: 1; -} - -.ant-badge-count { - z-index: auto; - min-width: 20px; - height: 20px; - padding: 0 6px; - color: #fff; - font-weight: normal; - font-size: 12px; - line-height: 20px; - white-space: nowrap; - text-align: center; - background: #ff4d4f; - border-radius: 10px; - box-shadow: 0 0 0 1px #fff; -} - -.ant-badge-count a, -.ant-badge-count a:hover { - color: #fff; -} - -.ant-badge-count-sm { - min-width: 14px; - height: 14px; - padding: 0; - font-size: 12px; - line-height: 14px; - border-radius: 7px; -} - -.ant-badge-multiple-words { - padding: 0 8px; -} - -.ant-badge-dot { - z-index: auto; - width: 6px; - min-width: 6px; - height: 6px; - background: #ff4d4f; - border-radius: 100%; - box-shadow: 0 0 0 1px #fff; -} - -.ant-badge-dot.ant-scroll-number { - transition: background 1.5s; -} - -.ant-badge-count, -.ant-badge-dot, -.ant-badge .ant-scroll-number-custom-component { - position: absolute; - top: 0; - right: 0; - transform: translate(50%, -50%); - transform-origin: 100% 0%; -} - -.ant-badge-count.anticon-spin, -.ant-badge-dot.anticon-spin, -.ant-badge .ant-scroll-number-custom-component.anticon-spin { - -webkit-animation: antBadgeLoadingCircle 1s infinite linear; - animation: antBadgeLoadingCircle 1s infinite linear; -} - -.ant-badge-status { - line-height: inherit; - vertical-align: baseline; -} - -.ant-badge-status-dot { - position: relative; - top: -1px; - display: inline-block; - width: 6px; - height: 6px; - vertical-align: middle; - border-radius: 50%; -} - -.ant-badge-status-success { - background-color: #52c41a; -} - -.ant-badge-status-processing { - position: relative; - background-color: #1890ff; -} - -.ant-badge-status-processing::after { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border: 1px solid #1890ff; - border-radius: 50%; - -webkit-animation: antStatusProcessing 1.2s infinite ease-in-out; - animation: antStatusProcessing 1.2s infinite ease-in-out; - content: ''; -} - -.ant-badge-status-default { - background-color: #d9d9d9; -} - -.ant-badge-status-error { - background-color: #ff4d4f; -} - -.ant-badge-status-warning { - background-color: #faad14; -} - -.ant-badge-status-pink { - background: #eb2f96; -} - -.ant-badge-status-magenta { - background: #eb2f96; -} - -.ant-badge-status-red { - background: #f5222d; -} - -.ant-badge-status-volcano { - background: #fa541c; -} - -.ant-badge-status-orange { - background: #fa8c16; -} - -.ant-badge-status-yellow { - background: #fadb14; -} - -.ant-badge-status-gold { - background: #faad14; -} - -.ant-badge-status-cyan { - background: #13c2c2; -} - -.ant-badge-status-lime { - background: #a0d911; -} - -.ant-badge-status-green { - background: #52c41a; -} - -.ant-badge-status-blue { - background: #1890ff; -} - -.ant-badge-status-geekblue { - background: #2f54eb; -} - -.ant-badge-status-purple { - background: #722ed1; -} - -.ant-badge-status-text { - margin-left: 8px; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; -} - -.ant-badge-zoom-appear, -.ant-badge-zoom-enter { - -webkit-animation: antZoomBadgeIn 0.3s cubic-bezier(0.12, 0.4, 0.29, 1.46); - animation: antZoomBadgeIn 0.3s cubic-bezier(0.12, 0.4, 0.29, 1.46); - -webkit-animation-fill-mode: both; - animation-fill-mode: both; -} - -.ant-badge-zoom-leave { - -webkit-animation: antZoomBadgeOut 0.3s cubic-bezier(0.71, -0.46, 0.88, 0.6); - animation: antZoomBadgeOut 0.3s cubic-bezier(0.71, -0.46, 0.88, 0.6); - -webkit-animation-fill-mode: both; - animation-fill-mode: both; -} - -.ant-badge-not-a-wrapper .ant-badge-zoom-appear, -.ant-badge-not-a-wrapper .ant-badge-zoom-enter { - -webkit-animation: antNoWrapperZoomBadgeIn 0.3s cubic-bezier(0.12, 0.4, 0.29, 1.46); - animation: antNoWrapperZoomBadgeIn 0.3s cubic-bezier(0.12, 0.4, 0.29, 1.46); -} - -.ant-badge-not-a-wrapper .ant-badge-zoom-leave { - -webkit-animation: antNoWrapperZoomBadgeOut 0.3s cubic-bezier(0.71, -0.46, 0.88, 0.6); - animation: antNoWrapperZoomBadgeOut 0.3s cubic-bezier(0.71, -0.46, 0.88, 0.6); -} - -.ant-badge-not-a-wrapper:not(.ant-badge-status) { - vertical-align: middle; -} - -.ant-badge-not-a-wrapper .ant-scroll-number-custom-component, -.ant-badge-not-a-wrapper .ant-badge-count { - transform: none; -} - -.ant-badge-not-a-wrapper .ant-scroll-number-custom-component, -.ant-badge-not-a-wrapper .ant-scroll-number { - position: relative; - top: auto; - display: block; - transform-origin: 50% 50%; -} - -@-webkit-keyframes antStatusProcessing { - 0% { - transform: scale(0.8); - opacity: 0.5; - } - 100% { - transform: scale(2.4); - opacity: 0; - } -} - -@keyframes antStatusProcessing { - 0% { - transform: scale(0.8); - opacity: 0.5; - } - 100% { - transform: scale(2.4); - opacity: 0; - } -} - -.ant-scroll-number { - overflow: hidden; - direction: ltr; -} - -.ant-scroll-number-only { - position: relative; - display: inline-block; - height: 20px; - transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); - -webkit-transform-style: preserve-3d; - -webkit-backface-visibility: hidden; - /* stylelint-enable property-no-vendor-prefix */ -} - -.ant-scroll-number-only > p.ant-scroll-number-only-unit { - height: 20px; - margin: 0; - -webkit-transform-style: preserve-3d; - -webkit-backface-visibility: hidden; - /* stylelint-enable property-no-vendor-prefix */ -} - -.ant-scroll-number-symbol { - vertical-align: top; -} - -@-webkit-keyframes antZoomBadgeIn { - 0% { - transform: scale(0) translate(50%, -50%); - opacity: 0; - } - 100% { - transform: scale(1) translate(50%, -50%); - } -} - -@keyframes antZoomBadgeIn { - 0% { - transform: scale(0) translate(50%, -50%); - opacity: 0; - } - 100% { - transform: scale(1) translate(50%, -50%); - } -} - -@-webkit-keyframes antZoomBadgeOut { - 0% { - transform: scale(1) translate(50%, -50%); - } - 100% { - transform: scale(0) translate(50%, -50%); - opacity: 0; - } -} - -@keyframes antZoomBadgeOut { - 0% { - transform: scale(1) translate(50%, -50%); - } - 100% { - transform: scale(0) translate(50%, -50%); - opacity: 0; - } -} - -@-webkit-keyframes antNoWrapperZoomBadgeIn { - 0% { - transform: scale(0); - opacity: 0; - } - 100% { - transform: scale(1); - } -} - -@keyframes antNoWrapperZoomBadgeIn { - 0% { - transform: scale(0); - opacity: 0; - } - 100% { - transform: scale(1); - } -} - -@-webkit-keyframes antNoWrapperZoomBadgeOut { - 0% { - transform: scale(1); - } - 100% { - transform: scale(0); - opacity: 0; - } -} - -@keyframes antNoWrapperZoomBadgeOut { - 0% { - transform: scale(1); - } - 100% { - transform: scale(0); - opacity: 0; - } -} - -@-webkit-keyframes antBadgeLoadingCircle { - 0% { - transform-origin: 50%; - } - 100% { - transform: translate(50%, -50%) rotate(360deg); - transform-origin: 50%; - } -} - -@keyframes antBadgeLoadingCircle { - 0% { - transform-origin: 50%; - } - 100% { - transform: translate(50%, -50%) rotate(360deg); - transform-origin: 50%; - } -} - -.ant-ribbon-wrapper { - position: relative; -} - -.ant-ribbon { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: absolute; - top: 8px; - height: 22px; - padding: 0 8px; - color: #fff; - line-height: 22px; - white-space: nowrap; - background-color: #1890ff; - border-radius: 2px; -} - -.ant-ribbon-text { - color: #fff; -} - -.ant-ribbon-corner { - position: absolute; - top: 100%; - width: 8px; - height: 8px; - color: currentColor; - border: 4px solid; - transform: scaleY(0.75); - transform-origin: top; -} - -.ant-ribbon-corner::after { - position: absolute; - top: -4px; - left: -4px; - width: inherit; - height: inherit; - color: rgba(0, 0, 0, 0.25); - border: inherit; - content: ''; -} - -.ant-ribbon-color-pink { - color: #eb2f96; - background: #eb2f96; -} - -.ant-ribbon-color-magenta { - color: #eb2f96; - background: #eb2f96; -} - -.ant-ribbon-color-red { - color: #f5222d; - background: #f5222d; -} - -.ant-ribbon-color-volcano { - color: #fa541c; - background: #fa541c; -} - -.ant-ribbon-color-orange { - color: #fa8c16; - background: #fa8c16; -} - -.ant-ribbon-color-yellow { - color: #fadb14; - background: #fadb14; -} - -.ant-ribbon-color-gold { - color: #faad14; - background: #faad14; -} - -.ant-ribbon-color-cyan { - color: #13c2c2; - background: #13c2c2; -} - -.ant-ribbon-color-lime { - color: #a0d911; - background: #a0d911; -} - -.ant-ribbon-color-green { - color: #52c41a; - background: #52c41a; -} - -.ant-ribbon-color-blue { - color: #1890ff; - background: #1890ff; -} - -.ant-ribbon-color-geekblue { - color: #2f54eb; - background: #2f54eb; -} - -.ant-ribbon-color-purple { - color: #722ed1; - background: #722ed1; -} - -.ant-ribbon.ant-ribbon-placement-end { - right: -8px; - border-bottom-right-radius: 0; -} - -.ant-ribbon.ant-ribbon-placement-end .ant-ribbon-corner { - right: 0; - border-color: currentColor transparent transparent currentColor; -} - -.ant-ribbon.ant-ribbon-placement-start { - left: -8px; - border-bottom-left-radius: 0; -} - -.ant-ribbon.ant-ribbon-placement-start .ant-ribbon-corner { - left: 0; - border-color: currentColor currentColor transparent transparent; -} - -.ant-breadcrumb { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - color: rgba(0, 0, 0, 0.45); - font-size: 14px; -} - -.ant-breadcrumb .anticon { - font-size: 14px; -} - -.ant-breadcrumb a { - color: rgba(0, 0, 0, 0.45); - transition: color 0.3s; -} - -.ant-breadcrumb a:hover { - color: #40a9ff; -} - -.ant-breadcrumb > span:last-child { - color: rgba(0, 0, 0, 0.85); -} - -.ant-breadcrumb > span:last-child a { - color: rgba(0, 0, 0, 0.85); -} - -.ant-breadcrumb > span:last-child .ant-breadcrumb-separator { - display: none; -} - -.ant-breadcrumb-separator { - margin: 0 8px; - color: rgba(0, 0, 0, 0.45); -} - -.ant-breadcrumb-link > .anticon + span, -.ant-breadcrumb-link > .anticon + a { - margin-left: 4px; -} - -.ant-breadcrumb-overlay-link > .anticon { - margin-left: 4px; -} - -.ant-menu-item-danger.ant-menu-item { - color: #ff4d4f; -} - -.ant-menu-item-danger.ant-menu-item:hover, -.ant-menu-item-danger.ant-menu-item-active { - color: #ff4d4f; -} - -.ant-menu-item-danger.ant-menu-item:active { - background: #fff1f0; -} - -.ant-menu-item-danger.ant-menu-item-selected { - color: #ff4d4f; -} - -.ant-menu-item-danger.ant-menu-item-selected > a, -.ant-menu-item-danger.ant-menu-item-selected > a:hover { - color: #ff4d4f; -} - -.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-danger.ant-menu-item-selected { - background-color: #fff1f0; -} - -.ant-menu-inline .ant-menu-item-danger.ant-menu-item::after { - border-right-color: #ff4d4f; -} - -.ant-menu-dark .ant-menu-item-danger.ant-menu-item, -.ant-menu-dark .ant-menu-item-danger.ant-menu-item:hover, -.ant-menu-dark .ant-menu-item-danger.ant-menu-item > a { - color: #ff4d4f; -} - -.ant-menu-dark.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-danger.ant-menu-item-selected { - color: #fff; - background-color: #ff4d4f; -} - -.ant-menu { - box-sizing: border-box; - margin: 0; - padding: 0; - font-variant: tabular-nums; - line-height: 1.5715; - font-feature-settings: 'tnum'; - margin-bottom: 0; - padding-left: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - line-height: 0; - text-align: left; - list-style: none; - background: #fff; - outline: none; - box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); - transition: background 0.3s, width 0.3s cubic-bezier(0.2, 0, 0, 1) 0s; -} - -.ant-menu::before { - display: table; - content: ''; -} - -.ant-menu::after { - display: table; - clear: both; - content: ''; -} - -.ant-menu.ant-menu-root:focus-visible { - box-shadow: 0 0 0 2px #e6f7ff; -} - -.ant-menu ul, -.ant-menu ol { - margin: 0; - padding: 0; - list-style: none; -} - -.ant-menu-overflow { - display: flex; -} - -.ant-menu-overflow-item { - flex: none; -} - -.ant-menu-hidden, -.ant-menu-submenu-hidden { - display: none; -} - -.ant-menu-item-group-title { - height: 1.5715; - padding: 8px 16px; - color: rgba(0, 0, 0, 0.45); - font-size: 14px; - line-height: 1.5715; - transition: all 0.3s; -} - -.ant-menu-horizontal .ant-menu-submenu { - transition: border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); -} - -.ant-menu-submenu, -.ant-menu-submenu-inline { - transition: border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), padding 0.15s cubic-bezier(0.645, 0.045, 0.355, 1); -} - -.ant-menu-submenu-selected { - color: #1890ff; -} - -.ant-menu-item:active, -.ant-menu-submenu-title:active { - background: #e6f7ff; -} - -.ant-menu-submenu .ant-menu-sub { - cursor: initial; - transition: background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), padding 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); -} - -.ant-menu-title-content { - transition: color 0.3s; -} - -.ant-menu-item a { - color: rgba(0, 0, 0, 0.85); -} - -.ant-menu-item a:hover { - color: #1890ff; -} - -.ant-menu-item a::before { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-color: transparent; - content: ''; -} - -.ant-menu-item > .ant-badge a { - color: rgba(0, 0, 0, 0.85); -} - -.ant-menu-item > .ant-badge a:hover { - color: #1890ff; -} - -.ant-menu-item-divider { - overflow: hidden; - line-height: 0; - border-color: #f0f0f0; - border-style: solid; - border-width: 1px 0 0; -} - -.ant-menu-item-divider-dashed { - border-style: dashed; -} - -.ant-menu-horizontal .ant-menu-item, -.ant-menu-horizontal .ant-menu-submenu { - margin-top: -1px; -} - -.ant-menu-horizontal > .ant-menu-item:hover, -.ant-menu-horizontal > .ant-menu-item-active, -.ant-menu-horizontal > .ant-menu-submenu .ant-menu-submenu-title:hover { - background-color: transparent; -} - -.ant-menu-item-selected { - color: #1890ff; -} - -.ant-menu-item-selected a, -.ant-menu-item-selected a:hover { - color: #1890ff; -} - -.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected { - background-color: #e6f7ff; -} - -.ant-menu-inline, -.ant-menu-vertical, -.ant-menu-vertical-left { - border-right: 1px solid #f0f0f0; -} - -.ant-menu-vertical-right { - border-left: 1px solid #f0f0f0; -} - -.ant-menu-vertical.ant-menu-sub, -.ant-menu-vertical-left.ant-menu-sub, -.ant-menu-vertical-right.ant-menu-sub { - min-width: 160px; - max-height: calc(100vh - 100px); - padding: 0; - overflow: hidden; - border-right: 0; -} - -.ant-menu-vertical.ant-menu-sub:not([class*='-active']), -.ant-menu-vertical-left.ant-menu-sub:not([class*='-active']), -.ant-menu-vertical-right.ant-menu-sub:not([class*='-active']) { - overflow-x: hidden; - overflow-y: auto; -} - -.ant-menu-vertical.ant-menu-sub .ant-menu-item, -.ant-menu-vertical-left.ant-menu-sub .ant-menu-item, -.ant-menu-vertical-right.ant-menu-sub .ant-menu-item { - left: 0; - margin-left: 0; - border-right: 0; -} - -.ant-menu-vertical.ant-menu-sub .ant-menu-item::after, -.ant-menu-vertical-left.ant-menu-sub .ant-menu-item::after, -.ant-menu-vertical-right.ant-menu-sub .ant-menu-item::after { - border-right: 0; -} - -.ant-menu-vertical.ant-menu-sub > .ant-menu-item, -.ant-menu-vertical-left.ant-menu-sub > .ant-menu-item, -.ant-menu-vertical-right.ant-menu-sub > .ant-menu-item, -.ant-menu-vertical.ant-menu-sub > .ant-menu-submenu, -.ant-menu-vertical-left.ant-menu-sub > .ant-menu-submenu, -.ant-menu-vertical-right.ant-menu-sub > .ant-menu-submenu { - transform-origin: 0 0; -} - -.ant-menu-horizontal.ant-menu-sub { - min-width: 114px; -} - -.ant-menu-horizontal .ant-menu-item, -.ant-menu-horizontal .ant-menu-submenu-title { - transition: border-color 0.3s, background 0.3s; -} - -.ant-menu-item, -.ant-menu-submenu-title { - position: relative; - display: block; - margin: 0; - padding: 0 20px; - white-space: nowrap; - cursor: pointer; - transition: border-color 0.3s, background 0.3s, padding 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); -} - -.ant-menu-item .ant-menu-item-icon, -.ant-menu-submenu-title .ant-menu-item-icon, -.ant-menu-item .anticon, -.ant-menu-submenu-title .anticon { - min-width: 14px; - font-size: 14px; - transition: font-size 0.15s cubic-bezier(0.215, 0.61, 0.355, 1), margin 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), color 0.3s; -} - -.ant-menu-item .ant-menu-item-icon + span, -.ant-menu-submenu-title .ant-menu-item-icon + span, -.ant-menu-item .anticon + span, -.ant-menu-submenu-title .anticon + span { - margin-left: 10px; - opacity: 1; - transition: opacity 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), margin 0.3s, color 0.3s; -} - -.ant-menu-item .ant-menu-item-icon.svg, -.ant-menu-submenu-title .ant-menu-item-icon.svg { - vertical-align: -0.125em; -} - -.ant-menu-item.ant-menu-item-only-child > .anticon, -.ant-menu-submenu-title.ant-menu-item-only-child > .anticon, -.ant-menu-item.ant-menu-item-only-child > .ant-menu-item-icon, -.ant-menu-submenu-title.ant-menu-item-only-child > .ant-menu-item-icon { - margin-right: 0; -} - -.ant-menu-item:focus-visible, -.ant-menu-submenu-title:focus-visible { - box-shadow: 0 0 0 2px #e6f7ff; -} - -.ant-menu > .ant-menu-item-divider { - margin: 1px 0; - padding: 0; -} - -.ant-menu-submenu-popup { - position: absolute; - z-index: 1050; - background: transparent; - border-radius: 2px; - box-shadow: none; - transform-origin: 0 0; -} - -.ant-menu-submenu-popup::before { - position: absolute; - top: -7px; - right: 0; - bottom: 0; - left: 0; - z-index: -1; - width: 100%; - height: 100%; - opacity: 0.0001; - content: ' '; -} - -.ant-menu-submenu-placement-rightTop::before { - top: 0; - left: -7px; -} - -.ant-menu-submenu > .ant-menu { - background-color: #fff; - border-radius: 2px; -} - -.ant-menu-submenu > .ant-menu-submenu-title::after { - transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); -} - -.ant-menu-submenu-popup > .ant-menu { - background-color: #fff; -} - -.ant-menu-submenu-expand-icon, -.ant-menu-submenu-arrow { - position: absolute; - top: 50%; - right: 16px; - width: 10px; - color: rgba(0, 0, 0, 0.85); - transform: translateY(-50%); - transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); -} - -.ant-menu-submenu-arrow::before, -.ant-menu-submenu-arrow::after { - position: absolute; - width: 6px; - height: 1.5px; - background-color: currentColor; - border-radius: 2px; - transition: background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), top 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); - content: ''; -} - -.ant-menu-submenu-arrow::before { - transform: rotate(45deg) translateY(-2.5px); -} - -.ant-menu-submenu-arrow::after { - transform: rotate(-45deg) translateY(2.5px); -} - -.ant-menu-submenu:hover > .ant-menu-submenu-title > .ant-menu-submenu-expand-icon, -.ant-menu-submenu:hover > .ant-menu-submenu-title > .ant-menu-submenu-arrow { - color: #1890ff; -} - -.ant-menu-inline-collapsed .ant-menu-submenu-arrow::before, -.ant-menu-submenu-inline .ant-menu-submenu-arrow::before { - transform: rotate(-45deg) translateX(2.5px); -} - -.ant-menu-inline-collapsed .ant-menu-submenu-arrow::after, -.ant-menu-submenu-inline .ant-menu-submenu-arrow::after { - transform: rotate(45deg) translateX(-2.5px); -} - -.ant-menu-submenu-horizontal .ant-menu-submenu-arrow { - display: none; -} - -.ant-menu-submenu-open.ant-menu-submenu-inline > .ant-menu-submenu-title > .ant-menu-submenu-arrow { - transform: translateY(-2px); -} - -.ant-menu-submenu-open.ant-menu-submenu-inline > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after { - transform: rotate(-45deg) translateX(-2.5px); -} - -.ant-menu-submenu-open.ant-menu-submenu-inline > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before { - transform: rotate(45deg) translateX(2.5px); -} - -.ant-menu-vertical .ant-menu-submenu-selected, -.ant-menu-vertical-left .ant-menu-submenu-selected, -.ant-menu-vertical-right .ant-menu-submenu-selected { - color: #1890ff; -} - -.ant-menu-horizontal { - line-height: 46px; - border: 0; - border-bottom: 1px solid #f0f0f0; - box-shadow: none; -} - -.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item, -.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu { - margin-top: -1px; - margin-bottom: 0; - padding: 0 20px; -} - -.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item:hover, -.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu:hover, -.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-active, -.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-active, -.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-open, -.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-open, -.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-selected, -.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected { - color: #1890ff; -} - -.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item:hover::after, -.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu:hover::after, -.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-active::after, -.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-active::after, -.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-open::after, -.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-open::after, -.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-selected::after, -.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected::after { - border-bottom: 2px solid #1890ff; -} - -.ant-menu-horizontal > .ant-menu-item, -.ant-menu-horizontal > .ant-menu-submenu { - position: relative; - top: 1px; - display: inline-block; - vertical-align: bottom; -} - -.ant-menu-horizontal > .ant-menu-item::after, -.ant-menu-horizontal > .ant-menu-submenu::after { - position: absolute; - right: 20px; - bottom: 0; - left: 20px; - border-bottom: 2px solid transparent; - transition: border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); - content: ''; -} - -.ant-menu-horizontal > .ant-menu-submenu > .ant-menu-submenu-title { - padding: 0; -} - -.ant-menu-horizontal > .ant-menu-item a { - color: rgba(0, 0, 0, 0.85); -} - -.ant-menu-horizontal > .ant-menu-item a:hover { - color: #1890ff; -} - -.ant-menu-horizontal > .ant-menu-item a::before { - bottom: -2px; -} - -.ant-menu-horizontal > .ant-menu-item-selected a { - color: #1890ff; -} - -.ant-menu-horizontal::after { - display: block; - clear: both; - height: 0; - content: '\20'; -} - -.ant-menu-vertical .ant-menu-item, -.ant-menu-vertical-left .ant-menu-item, -.ant-menu-vertical-right .ant-menu-item, -.ant-menu-inline .ant-menu-item { - position: relative; -} - -.ant-menu-vertical .ant-menu-item::after, -.ant-menu-vertical-left .ant-menu-item::after, -.ant-menu-vertical-right .ant-menu-item::after, -.ant-menu-inline .ant-menu-item::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - border-right: 3px solid #1890ff; - transform: scaleY(0.0001); - opacity: 0; - transition: transform 0.15s cubic-bezier(0.215, 0.61, 0.355, 1), opacity 0.15s cubic-bezier(0.215, 0.61, 0.355, 1); - content: ''; -} - -.ant-menu-vertical .ant-menu-item, -.ant-menu-vertical-left .ant-menu-item, -.ant-menu-vertical-right .ant-menu-item, -.ant-menu-inline .ant-menu-item, -.ant-menu-vertical .ant-menu-submenu-title, -.ant-menu-vertical-left .ant-menu-submenu-title, -.ant-menu-vertical-right .ant-menu-submenu-title, -.ant-menu-inline .ant-menu-submenu-title { - height: 40px; - margin-top: 4px; - margin-bottom: 4px; - padding: 0 16px; - overflow: hidden; - line-height: 40px; - text-overflow: ellipsis; -} - -.ant-menu-vertical .ant-menu-submenu, -.ant-menu-vertical-left .ant-menu-submenu, -.ant-menu-vertical-right .ant-menu-submenu, -.ant-menu-inline .ant-menu-submenu { - padding-bottom: 0.02px; -} - -.ant-menu-vertical .ant-menu-item:not(:last-child), -.ant-menu-vertical-left .ant-menu-item:not(:last-child), -.ant-menu-vertical-right .ant-menu-item:not(:last-child), -.ant-menu-inline .ant-menu-item:not(:last-child) { - margin-bottom: 8px; -} - -.ant-menu-vertical > .ant-menu-item, -.ant-menu-vertical-left > .ant-menu-item, -.ant-menu-vertical-right > .ant-menu-item, -.ant-menu-inline > .ant-menu-item, -.ant-menu-vertical > .ant-menu-submenu > .ant-menu-submenu-title, -.ant-menu-vertical-left > .ant-menu-submenu > .ant-menu-submenu-title, -.ant-menu-vertical-right > .ant-menu-submenu > .ant-menu-submenu-title, -.ant-menu-inline > .ant-menu-submenu > .ant-menu-submenu-title { - height: 40px; - line-height: 40px; -} - -.ant-menu-vertical .ant-menu-item-group-list .ant-menu-submenu-title, -.ant-menu-vertical .ant-menu-submenu-title { - padding-right: 34px; -} - -.ant-menu-inline { - width: 100%; -} - -.ant-menu-inline .ant-menu-selected::after, -.ant-menu-inline .ant-menu-item-selected::after { - transform: scaleY(1); - opacity: 1; - transition: transform 0.15s cubic-bezier(0.645, 0.045, 0.355, 1), opacity 0.15s cubic-bezier(0.645, 0.045, 0.355, 1); -} - -.ant-menu-inline .ant-menu-item, -.ant-menu-inline .ant-menu-submenu-title { - width: calc(100% + 1px); -} - -.ant-menu-inline .ant-menu-item-group-list .ant-menu-submenu-title, -.ant-menu-inline .ant-menu-submenu-title { - padding-right: 34px; -} - -.ant-menu-inline.ant-menu-root .ant-menu-item, -.ant-menu-inline.ant-menu-root .ant-menu-submenu-title { - display: flex; - align-items: center; - transition: border-color 0.3s, background 0.3s, padding 0.1s cubic-bezier(0.215, 0.61, 0.355, 1); -} - -.ant-menu-inline.ant-menu-root .ant-menu-item > .ant-menu-title-content, -.ant-menu-inline.ant-menu-root .ant-menu-submenu-title > .ant-menu-title-content { - flex: auto; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; -} - -.ant-menu-inline.ant-menu-root .ant-menu-item > *, -.ant-menu-inline.ant-menu-root .ant-menu-submenu-title > * { - flex: none; -} - -.ant-menu.ant-menu-inline-collapsed { - width: 80px; -} - -.ant-menu.ant-menu-inline-collapsed > .ant-menu-item, -.ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-item, -.ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-submenu > .ant-menu-submenu-title, -.ant-menu.ant-menu-inline-collapsed > .ant-menu-submenu > .ant-menu-submenu-title { - left: 0; - padding: 0 calc(50% - 16px / 2); - text-overflow: clip; -} - -.ant-menu.ant-menu-inline-collapsed > .ant-menu-item .ant-menu-submenu-arrow, -.ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-item .ant-menu-submenu-arrow, -.ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-submenu > .ant-menu-submenu-title .ant-menu-submenu-arrow, -.ant-menu.ant-menu-inline-collapsed > .ant-menu-submenu > .ant-menu-submenu-title .ant-menu-submenu-arrow { - opacity: 0; -} - -.ant-menu.ant-menu-inline-collapsed > .ant-menu-item .ant-menu-item-icon, -.ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-item .ant-menu-item-icon, -.ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-submenu > .ant-menu-submenu-title .ant-menu-item-icon, -.ant-menu.ant-menu-inline-collapsed > .ant-menu-submenu > .ant-menu-submenu-title .ant-menu-item-icon, -.ant-menu.ant-menu-inline-collapsed > .ant-menu-item .anticon, -.ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-item .anticon, -.ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-submenu > .ant-menu-submenu-title .anticon, -.ant-menu.ant-menu-inline-collapsed > .ant-menu-submenu > .ant-menu-submenu-title .anticon { - margin: 0; - font-size: 16px; - line-height: 40px; -} - -.ant-menu.ant-menu-inline-collapsed > .ant-menu-item .ant-menu-item-icon + span, -.ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-item .ant-menu-item-icon + span, -.ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-submenu > .ant-menu-submenu-title .ant-menu-item-icon + span, -.ant-menu.ant-menu-inline-collapsed > .ant-menu-submenu > .ant-menu-submenu-title .ant-menu-item-icon + span, -.ant-menu.ant-menu-inline-collapsed > .ant-menu-item .anticon + span, -.ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-item .anticon + span, -.ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-submenu > .ant-menu-submenu-title .anticon + span, -.ant-menu.ant-menu-inline-collapsed > .ant-menu-submenu > .ant-menu-submenu-title .anticon + span { - display: inline-block; - opacity: 0; -} - -.ant-menu.ant-menu-inline-collapsed .ant-menu-item-icon, -.ant-menu.ant-menu-inline-collapsed .anticon { - display: inline-block; -} - -.ant-menu.ant-menu-inline-collapsed-tooltip { - pointer-events: none; -} - -.ant-menu.ant-menu-inline-collapsed-tooltip .ant-menu-item-icon, -.ant-menu.ant-menu-inline-collapsed-tooltip .anticon { - display: none; -} - -.ant-menu.ant-menu-inline-collapsed-tooltip a { - color: rgba(255, 255, 255, 0.85); -} - -.ant-menu.ant-menu-inline-collapsed .ant-menu-item-group-title { - padding-right: 4px; - padding-left: 4px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - -.ant-menu-item-group-list { - margin: 0; - padding: 0; -} - -.ant-menu-item-group-list .ant-menu-item, -.ant-menu-item-group-list .ant-menu-submenu-title { - padding: 0 16px 0 28px; -} - -.ant-menu-root.ant-menu-vertical, -.ant-menu-root.ant-menu-vertical-left, -.ant-menu-root.ant-menu-vertical-right, -.ant-menu-root.ant-menu-inline { - box-shadow: none; -} - -.ant-menu-root.ant-menu-inline-collapsed .ant-menu-item > .ant-menu-inline-collapsed-noicon, -.ant-menu-root.ant-menu-inline-collapsed .ant-menu-submenu .ant-menu-submenu-title > .ant-menu-inline-collapsed-noicon { - font-size: 16px; - text-align: center; -} - -.ant-menu-sub.ant-menu-inline { - padding: 0; - background: #fafafa; - border: 0; - border-radius: 0; - box-shadow: none; -} - -.ant-menu-sub.ant-menu-inline > .ant-menu-item, -.ant-menu-sub.ant-menu-inline > .ant-menu-submenu > .ant-menu-submenu-title { - height: 40px; - line-height: 40px; - list-style-position: inside; - list-style-type: disc; -} - -.ant-menu-sub.ant-menu-inline .ant-menu-item-group-title { - padding-left: 32px; -} - -.ant-menu-item-disabled, -.ant-menu-submenu-disabled { - color: rgba(0, 0, 0, 0.25) !important; - background: none; - cursor: not-allowed; -} - -.ant-menu-item-disabled::after, -.ant-menu-submenu-disabled::after { - border-color: transparent !important; -} - -.ant-menu-item-disabled a, -.ant-menu-submenu-disabled a { - color: rgba(0, 0, 0, 0.25) !important; - pointer-events: none; -} - -.ant-menu-item-disabled > .ant-menu-submenu-title, -.ant-menu-submenu-disabled > .ant-menu-submenu-title { - color: rgba(0, 0, 0, 0.25) !important; - cursor: not-allowed; -} - -.ant-menu-item-disabled > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, -.ant-menu-submenu-disabled > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, -.ant-menu-item-disabled > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, -.ant-menu-submenu-disabled > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after { - background: rgba(0, 0, 0, 0.25) !important; -} - -.ant-layout-header .ant-menu { - line-height: inherit; -} - -.ant-menu-inline-collapsed-tooltip a, -.ant-menu-inline-collapsed-tooltip a:hover { - color: #fff; -} - -.ant-menu-light .ant-menu-item:hover, -.ant-menu-light .ant-menu-item-active, -.ant-menu-light .ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open, -.ant-menu-light .ant-menu-submenu-active, -.ant-menu-light .ant-menu-submenu-title:hover { - color: #1890ff; -} - -.ant-menu.ant-menu-dark, -.ant-menu-dark .ant-menu-sub, -.ant-menu.ant-menu-dark .ant-menu-sub { - color: rgba(255, 255, 255, 0.65); - background: #001529; -} - -.ant-menu.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow, -.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow, -.ant-menu.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow { - opacity: 0.45; - transition: all 0.3s; -} - -.ant-menu.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow::after, -.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow::after, -.ant-menu.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow::after, -.ant-menu.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow::before, -.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow::before, -.ant-menu.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow::before { - background: #fff; -} - -.ant-menu-dark.ant-menu-submenu-popup { - background: transparent; -} - -.ant-menu-dark .ant-menu-inline.ant-menu-sub { - background: #000c17; -} - -.ant-menu-dark.ant-menu-horizontal { - border-bottom: 0; -} - -.ant-menu-dark.ant-menu-horizontal > .ant-menu-item, -.ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu { - top: 0; - margin-top: 0; - padding: 0 20px; - border-color: #001529; - border-bottom: 0; -} - -.ant-menu-dark.ant-menu-horizontal > .ant-menu-item:hover { - background-color: #1890ff; -} - -.ant-menu-dark.ant-menu-horizontal > .ant-menu-item > a::before { - bottom: 0; -} - -.ant-menu-dark .ant-menu-item, -.ant-menu-dark .ant-menu-item-group-title, -.ant-menu-dark .ant-menu-item > a, -.ant-menu-dark .ant-menu-item > span > a { - color: rgba(255, 255, 255, 0.65); -} - -.ant-menu-dark.ant-menu-inline, -.ant-menu-dark.ant-menu-vertical, -.ant-menu-dark.ant-menu-vertical-left, -.ant-menu-dark.ant-menu-vertical-right { - border-right: 0; -} - -.ant-menu-dark.ant-menu-inline .ant-menu-item, -.ant-menu-dark.ant-menu-vertical .ant-menu-item, -.ant-menu-dark.ant-menu-vertical-left .ant-menu-item, -.ant-menu-dark.ant-menu-vertical-right .ant-menu-item { - left: 0; - margin-left: 0; - border-right: 0; -} - -.ant-menu-dark.ant-menu-inline .ant-menu-item::after, -.ant-menu-dark.ant-menu-vertical .ant-menu-item::after, -.ant-menu-dark.ant-menu-vertical-left .ant-menu-item::after, -.ant-menu-dark.ant-menu-vertical-right .ant-menu-item::after { - border-right: 0; -} - -.ant-menu-dark.ant-menu-inline .ant-menu-item, -.ant-menu-dark.ant-menu-inline .ant-menu-submenu-title { - width: 100%; -} - -.ant-menu-dark .ant-menu-item:hover, -.ant-menu-dark .ant-menu-item-active, -.ant-menu-dark .ant-menu-submenu-active, -.ant-menu-dark .ant-menu-submenu-open, -.ant-menu-dark .ant-menu-submenu-selected, -.ant-menu-dark .ant-menu-submenu-title:hover { - color: #fff; - background-color: transparent; -} - -.ant-menu-dark .ant-menu-item:hover > a, -.ant-menu-dark .ant-menu-item-active > a, -.ant-menu-dark .ant-menu-submenu-active > a, -.ant-menu-dark .ant-menu-submenu-open > a, -.ant-menu-dark .ant-menu-submenu-selected > a, -.ant-menu-dark .ant-menu-submenu-title:hover > a, -.ant-menu-dark .ant-menu-item:hover > span > a, -.ant-menu-dark .ant-menu-item-active > span > a, -.ant-menu-dark .ant-menu-submenu-active > span > a, -.ant-menu-dark .ant-menu-submenu-open > span > a, -.ant-menu-dark .ant-menu-submenu-selected > span > a, -.ant-menu-dark .ant-menu-submenu-title:hover > span > a { - color: #fff; -} - -.ant-menu-dark .ant-menu-item:hover > .ant-menu-submenu-title > .ant-menu-submenu-arrow, -.ant-menu-dark .ant-menu-item-active > .ant-menu-submenu-title > .ant-menu-submenu-arrow, -.ant-menu-dark .ant-menu-submenu-active > .ant-menu-submenu-title > .ant-menu-submenu-arrow, -.ant-menu-dark .ant-menu-submenu-open > .ant-menu-submenu-title > .ant-menu-submenu-arrow, -.ant-menu-dark .ant-menu-submenu-selected > .ant-menu-submenu-title > .ant-menu-submenu-arrow, -.ant-menu-dark .ant-menu-submenu-title:hover > .ant-menu-submenu-title > .ant-menu-submenu-arrow { - opacity: 1; -} - -.ant-menu-dark .ant-menu-item:hover > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, -.ant-menu-dark .ant-menu-item-active > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, -.ant-menu-dark .ant-menu-submenu-active > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, -.ant-menu-dark .ant-menu-submenu-open > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, -.ant-menu-dark .ant-menu-submenu-selected > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, -.ant-menu-dark .ant-menu-submenu-title:hover > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, -.ant-menu-dark .ant-menu-item:hover > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, -.ant-menu-dark .ant-menu-item-active > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, -.ant-menu-dark .ant-menu-submenu-active > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, -.ant-menu-dark .ant-menu-submenu-open > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, -.ant-menu-dark .ant-menu-submenu-selected > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, -.ant-menu-dark .ant-menu-submenu-title:hover > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before { - background: #fff; -} - -.ant-menu-dark .ant-menu-item:hover { - background-color: transparent; -} - -.ant-menu-dark.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected { - background-color: #1890ff; -} - -.ant-menu-dark .ant-menu-item-selected { - color: #fff; - border-right: 0; -} - -.ant-menu-dark .ant-menu-item-selected::after { - border-right: 0; -} - -.ant-menu-dark .ant-menu-item-selected > a, -.ant-menu-dark .ant-menu-item-selected > span > a, -.ant-menu-dark .ant-menu-item-selected > a:hover, -.ant-menu-dark .ant-menu-item-selected > span > a:hover { - color: #fff; -} - -.ant-menu-dark .ant-menu-item-selected .ant-menu-item-icon, -.ant-menu-dark .ant-menu-item-selected .anticon { - color: #fff; -} - -.ant-menu-dark .ant-menu-item-selected .ant-menu-item-icon + span, -.ant-menu-dark .ant-menu-item-selected .anticon + span { - color: #fff; -} - -.ant-menu.ant-menu-dark .ant-menu-item-selected, -.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected { - background-color: #1890ff; -} - -.ant-menu-dark .ant-menu-item-disabled, -.ant-menu-dark .ant-menu-submenu-disabled, -.ant-menu-dark .ant-menu-item-disabled > a, -.ant-menu-dark .ant-menu-submenu-disabled > a, -.ant-menu-dark .ant-menu-item-disabled > span > a, -.ant-menu-dark .ant-menu-submenu-disabled > span > a { - color: rgba(255, 255, 255, 0.35) !important; - opacity: 0.8; -} - -.ant-menu-dark .ant-menu-item-disabled > .ant-menu-submenu-title, -.ant-menu-dark .ant-menu-submenu-disabled > .ant-menu-submenu-title { - color: rgba(255, 255, 255, 0.35) !important; -} - -.ant-menu-dark .ant-menu-item-disabled > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, -.ant-menu-dark .ant-menu-submenu-disabled > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, -.ant-menu-dark .ant-menu-item-disabled > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, -.ant-menu-dark .ant-menu-submenu-disabled > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after { - background: rgba(255, 255, 255, 0.35) !important; -} - -.ant-tooltip { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: absolute; - z-index: 1070; - display: block; - width: -webkit-max-content; - width: -moz-max-content; - width: max-content; - max-width: 250px; - visibility: visible; -} - -.ant-tooltip-hidden { - display: none; -} - -.ant-tooltip-placement-top, -.ant-tooltip-placement-topLeft, -.ant-tooltip-placement-topRight { - padding-bottom: 8px; -} - -.ant-tooltip-placement-right, -.ant-tooltip-placement-rightTop, -.ant-tooltip-placement-rightBottom { - padding-left: 8px; -} - -.ant-tooltip-placement-bottom, -.ant-tooltip-placement-bottomLeft, -.ant-tooltip-placement-bottomRight { - padding-top: 8px; -} - -.ant-tooltip-placement-left, -.ant-tooltip-placement-leftTop, -.ant-tooltip-placement-leftBottom { - padding-right: 8px; -} - -.ant-tooltip-inner { - min-width: 30px; - min-height: 32px; - padding: 6px 8px; - color: #fff; - text-align: left; - text-decoration: none; - word-wrap: break-word; - background-color: rgba(0, 0, 0, 0.75); - border-radius: 2px; - box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); -} - -.ant-tooltip-arrow { - position: absolute; - display: block; - width: 13.07106781px; - height: 13.07106781px; - overflow: hidden; - background: transparent; - pointer-events: none; -} - -.ant-tooltip-arrow-content { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - display: block; - width: 5px; - height: 5px; - margin: auto; - background-color: rgba(0, 0, 0, 0.75); - content: ''; - pointer-events: auto; -} - -.ant-tooltip-placement-top .ant-tooltip-arrow, -.ant-tooltip-placement-topLeft .ant-tooltip-arrow, -.ant-tooltip-placement-topRight .ant-tooltip-arrow { - bottom: -5.07106781px; -} - -.ant-tooltip-placement-top .ant-tooltip-arrow-content, -.ant-tooltip-placement-topLeft .ant-tooltip-arrow-content, -.ant-tooltip-placement-topRight .ant-tooltip-arrow-content { - box-shadow: 3px 3px 7px rgba(0, 0, 0, 0.07); - transform: translateY(-6.53553391px) rotate(45deg); -} - -.ant-tooltip-placement-top .ant-tooltip-arrow { - left: 50%; - transform: translateX(-50%); -} - -.ant-tooltip-placement-topLeft .ant-tooltip-arrow { - left: 13px; -} - -.ant-tooltip-placement-topRight .ant-tooltip-arrow { - right: 13px; -} - -.ant-tooltip-placement-right .ant-tooltip-arrow, -.ant-tooltip-placement-rightTop .ant-tooltip-arrow, -.ant-tooltip-placement-rightBottom .ant-tooltip-arrow { - left: -5.07106781px; -} - -.ant-tooltip-placement-right .ant-tooltip-arrow-content, -.ant-tooltip-placement-rightTop .ant-tooltip-arrow-content, -.ant-tooltip-placement-rightBottom .ant-tooltip-arrow-content { - box-shadow: -3px 3px 7px rgba(0, 0, 0, 0.07); - transform: translateX(6.53553391px) rotate(45deg); -} - -.ant-tooltip-placement-right .ant-tooltip-arrow { - top: 50%; - transform: translateY(-50%); -} - -.ant-tooltip-placement-rightTop .ant-tooltip-arrow { - top: 5px; -} - -.ant-tooltip-placement-rightBottom .ant-tooltip-arrow { - bottom: 5px; -} - -.ant-tooltip-placement-left .ant-tooltip-arrow, -.ant-tooltip-placement-leftTop .ant-tooltip-arrow, -.ant-tooltip-placement-leftBottom .ant-tooltip-arrow { - right: -5.07106781px; -} - -.ant-tooltip-placement-left .ant-tooltip-arrow-content, -.ant-tooltip-placement-leftTop .ant-tooltip-arrow-content, -.ant-tooltip-placement-leftBottom .ant-tooltip-arrow-content { - box-shadow: 3px -3px 7px rgba(0, 0, 0, 0.07); - transform: translateX(-6.53553391px) rotate(45deg); -} - -.ant-tooltip-placement-left .ant-tooltip-arrow { - top: 50%; - transform: translateY(-50%); -} - -.ant-tooltip-placement-leftTop .ant-tooltip-arrow { - top: 5px; -} - -.ant-tooltip-placement-leftBottom .ant-tooltip-arrow { - bottom: 5px; -} - -.ant-tooltip-placement-bottom .ant-tooltip-arrow, -.ant-tooltip-placement-bottomLeft .ant-tooltip-arrow, -.ant-tooltip-placement-bottomRight .ant-tooltip-arrow { - top: -5.07106781px; -} - -.ant-tooltip-placement-bottom .ant-tooltip-arrow-content, -.ant-tooltip-placement-bottomLeft .ant-tooltip-arrow-content, -.ant-tooltip-placement-bottomRight .ant-tooltip-arrow-content { - box-shadow: -3px -3px 7px rgba(0, 0, 0, 0.07); - transform: translateY(6.53553391px) rotate(45deg); -} - -.ant-tooltip-placement-bottom .ant-tooltip-arrow { - left: 50%; - transform: translateX(-50%); -} - -.ant-tooltip-placement-bottomLeft .ant-tooltip-arrow { - left: 13px; -} - -.ant-tooltip-placement-bottomRight .ant-tooltip-arrow { - right: 13px; -} - -.ant-tooltip-pink .ant-tooltip-inner { - background-color: #eb2f96; -} - -.ant-tooltip-pink .ant-tooltip-arrow-content { - background-color: #eb2f96; -} - -.ant-tooltip-magenta .ant-tooltip-inner { - background-color: #eb2f96; -} - -.ant-tooltip-magenta .ant-tooltip-arrow-content { - background-color: #eb2f96; -} - -.ant-tooltip-red .ant-tooltip-inner { - background-color: #f5222d; -} - -.ant-tooltip-red .ant-tooltip-arrow-content { - background-color: #f5222d; -} - -.ant-tooltip-volcano .ant-tooltip-inner { - background-color: #fa541c; -} - -.ant-tooltip-volcano .ant-tooltip-arrow-content { - background-color: #fa541c; -} - -.ant-tooltip-orange .ant-tooltip-inner { - background-color: #fa8c16; -} - -.ant-tooltip-orange .ant-tooltip-arrow-content { - background-color: #fa8c16; -} - -.ant-tooltip-yellow .ant-tooltip-inner { - background-color: #fadb14; -} - -.ant-tooltip-yellow .ant-tooltip-arrow-content { - background-color: #fadb14; -} - -.ant-tooltip-gold .ant-tooltip-inner { - background-color: #faad14; -} - -.ant-tooltip-gold .ant-tooltip-arrow-content { - background-color: #faad14; -} - -.ant-tooltip-cyan .ant-tooltip-inner { - background-color: #13c2c2; -} - -.ant-tooltip-cyan .ant-tooltip-arrow-content { - background-color: #13c2c2; -} - -.ant-tooltip-lime .ant-tooltip-inner { - background-color: #a0d911; -} - -.ant-tooltip-lime .ant-tooltip-arrow-content { - background-color: #a0d911; -} - -.ant-tooltip-green .ant-tooltip-inner { - background-color: #52c41a; -} - -.ant-tooltip-green .ant-tooltip-arrow-content { - background-color: #52c41a; -} - -.ant-tooltip-blue .ant-tooltip-inner { - background-color: #1890ff; -} - -.ant-tooltip-blue .ant-tooltip-arrow-content { - background-color: #1890ff; -} - -.ant-tooltip-geekblue .ant-tooltip-inner { - background-color: #2f54eb; -} - -.ant-tooltip-geekblue .ant-tooltip-arrow-content { - background-color: #2f54eb; -} - -.ant-tooltip-purple .ant-tooltip-inner { - background-color: #722ed1; -} - -.ant-tooltip-purple .ant-tooltip-arrow-content { - background-color: #722ed1; -} - -.ant-dropdown-menu-item.ant-dropdown-menu-item-danger { - color: #ff4d4f; -} - -.ant-dropdown-menu-item.ant-dropdown-menu-item-danger:hover { - color: #fff; - background-color: #ff4d4f; -} - -.ant-dropdown { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: absolute; - top: -9999px; - left: -9999px; - z-index: 1050; - display: block; -} - -.ant-dropdown::before { - position: absolute; - top: -4px; - right: 0; - bottom: -4px; - left: -7px; - z-index: -9999; - opacity: 0.0001; - content: ' '; -} - -.ant-dropdown-wrap { - position: relative; -} - -.ant-dropdown-wrap .ant-btn > .anticon-down { - font-size: 10px; -} - -.ant-dropdown-wrap .anticon-down::before { - transition: transform 0.2s; -} - -.ant-dropdown-wrap-open .anticon-down::before { - transform: rotate(180deg); -} - -.ant-dropdown-hidden, -.ant-dropdown-menu-hidden, -.ant-dropdown-menu-submenu-hidden { - display: none; -} - -.ant-dropdown-show-arrow.ant-dropdown-placement-topCenter, -.ant-dropdown-show-arrow.ant-dropdown-placement-topLeft, -.ant-dropdown-show-arrow.ant-dropdown-placement-topRight { - padding-bottom: 10px; -} - -.ant-dropdown-show-arrow.ant-dropdown-placement-bottomCenter, -.ant-dropdown-show-arrow.ant-dropdown-placement-bottomLeft, -.ant-dropdown-show-arrow.ant-dropdown-placement-bottomRight { - padding-top: 10px; -} - -.ant-dropdown-arrow { - position: absolute; - z-index: 1; - display: block; - width: 8.48528137px; - height: 8.48528137px; - background: transparent; - border-style: solid; - border-width: 4.24264069px; - transform: rotate(45deg); -} - -.ant-dropdown-placement-topCenter > .ant-dropdown-arrow, -.ant-dropdown-placement-topLeft > .ant-dropdown-arrow, -.ant-dropdown-placement-topRight > .ant-dropdown-arrow { - bottom: 6.2px; - border-color: transparent #fff #fff transparent; - box-shadow: 3px 3px 7px rgba(0, 0, 0, 0.07); -} - -.ant-dropdown-placement-topCenter > .ant-dropdown-arrow { - left: 50%; - transform: translateX(-50%) rotate(45deg); -} - -.ant-dropdown-placement-topLeft > .ant-dropdown-arrow { - left: 16px; -} - -.ant-dropdown-placement-topRight > .ant-dropdown-arrow { - right: 16px; -} - -.ant-dropdown-placement-bottomCenter > .ant-dropdown-arrow, -.ant-dropdown-placement-bottomLeft > .ant-dropdown-arrow, -.ant-dropdown-placement-bottomRight > .ant-dropdown-arrow { - top: 6px; - border-color: #fff transparent transparent #fff; - box-shadow: -2px -2px 5px rgba(0, 0, 0, 0.06); -} - -.ant-dropdown-placement-bottomCenter > .ant-dropdown-arrow { - left: 50%; - transform: translateX(-50%) rotate(45deg); -} - -.ant-dropdown-placement-bottomLeft > .ant-dropdown-arrow { - left: 16px; -} - -.ant-dropdown-placement-bottomRight > .ant-dropdown-arrow { - right: 16px; -} - -.ant-dropdown-menu { - position: relative; - margin: 0; - padding: 4px 0; - text-align: left; - list-style-type: none; - background-color: #fff; - background-clip: padding-box; - border-radius: 2px; - outline: none; - box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); -} - -.ant-dropdown-menu-item-group-title { - padding: 5px 12px; - color: rgba(0, 0, 0, 0.45); - transition: all 0.3s; -} - -.ant-dropdown-menu-submenu-popup { - position: absolute; - z-index: 1050; - background: transparent; - box-shadow: none; - transform-origin: 0 0; -} - -.ant-dropdown-menu-submenu-popup ul, -.ant-dropdown-menu-submenu-popup li { - list-style: none; -} - -.ant-dropdown-menu-submenu-popup ul { - margin-right: 0.3em; - margin-left: 0.3em; -} - -.ant-dropdown-menu-item { - position: relative; - display: flex; - align-items: center; -} - -.ant-dropdown-menu-item-icon { - min-width: 12px; - margin-right: 8px; - font-size: 12px; -} - -.ant-dropdown-menu-title-content { - flex: auto; -} - -.ant-dropdown-menu-title-content > a { - color: inherit; - transition: all 0.3s; -} - -.ant-dropdown-menu-title-content > a:hover { - color: inherit; -} - -.ant-dropdown-menu-title-content > a::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - content: ''; -} - -.ant-dropdown-menu-item, -.ant-dropdown-menu-submenu-title { - clear: both; - margin: 0; - padding: 5px 12px; - color: rgba(0, 0, 0, 0.85); - font-weight: normal; - font-size: 14px; - line-height: 22px; - white-space: nowrap; - cursor: pointer; - transition: all 0.3s; -} - -.ant-dropdown-menu-item-selected, -.ant-dropdown-menu-submenu-title-selected { - color: #1890ff; - background-color: #e6f7ff; -} - -.ant-dropdown-menu-item:hover, -.ant-dropdown-menu-submenu-title:hover { - background-color: #f5f5f5; -} - -.ant-dropdown-menu-item-disabled, -.ant-dropdown-menu-submenu-title-disabled { - color: rgba(0, 0, 0, 0.25); - cursor: not-allowed; -} - -.ant-dropdown-menu-item-disabled:hover, -.ant-dropdown-menu-submenu-title-disabled:hover { - color: rgba(0, 0, 0, 0.25); - background-color: #fff; - cursor: not-allowed; -} - -.ant-dropdown-menu-item-disabled a, -.ant-dropdown-menu-submenu-title-disabled a { - pointer-events: none; -} - -.ant-dropdown-menu-item-divider, -.ant-dropdown-menu-submenu-title-divider { - height: 1px; - margin: 4px 0; - overflow: hidden; - line-height: 0; - background-color: #f0f0f0; -} - -.ant-dropdown-menu-item .ant-dropdown-menu-submenu-expand-icon, -.ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-expand-icon { - position: absolute; - right: 8px; -} - -.ant-dropdown-menu-item .ant-dropdown-menu-submenu-expand-icon .ant-dropdown-menu-submenu-arrow-icon, -.ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-expand-icon .ant-dropdown-menu-submenu-arrow-icon { - margin-right: 0 !important; - color: rgba(0, 0, 0, 0.45); - font-size: 10px; - font-style: normal; -} - -.ant-dropdown-menu-item-group-list { - margin: 0 8px; - padding: 0; - list-style: none; -} - -.ant-dropdown-menu-submenu-title { - padding-right: 24px; -} - -.ant-dropdown-menu-submenu-vertical { - position: relative; -} - -.ant-dropdown-menu-submenu-vertical > .ant-dropdown-menu { - position: absolute; - top: 0; - left: 100%; - min-width: 100%; - margin-left: 4px; - transform-origin: 0 0; -} - -.ant-dropdown-menu-submenu.ant-dropdown-menu-submenu-disabled .ant-dropdown-menu-submenu-title, -.ant-dropdown-menu-submenu.ant-dropdown-menu-submenu-disabled .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow-icon { - color: rgba(0, 0, 0, 0.25); - background-color: #fff; - cursor: not-allowed; -} - -.ant-dropdown-menu-submenu-selected .ant-dropdown-menu-submenu-title { - color: #1890ff; -} - -.ant-dropdown.ant-slide-down-enter.ant-slide-down-enter-active.ant-dropdown-placement-bottomLeft, -.ant-dropdown.ant-slide-down-appear.ant-slide-down-appear-active.ant-dropdown-placement-bottomLeft, -.ant-dropdown.ant-slide-down-enter.ant-slide-down-enter-active.ant-dropdown-placement-bottomCenter, -.ant-dropdown.ant-slide-down-appear.ant-slide-down-appear-active.ant-dropdown-placement-bottomCenter, -.ant-dropdown.ant-slide-down-enter.ant-slide-down-enter-active.ant-dropdown-placement-bottomRight, -.ant-dropdown.ant-slide-down-appear.ant-slide-down-appear-active.ant-dropdown-placement-bottomRight { - -webkit-animation-name: antSlideUpIn; - animation-name: antSlideUpIn; -} - -.ant-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-dropdown-placement-topLeft, -.ant-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-dropdown-placement-topLeft, -.ant-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-dropdown-placement-topCenter, -.ant-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-dropdown-placement-topCenter, -.ant-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-dropdown-placement-topRight, -.ant-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-dropdown-placement-topRight { - -webkit-animation-name: antSlideDownIn; - animation-name: antSlideDownIn; -} - -.ant-dropdown.ant-slide-down-leave.ant-slide-down-leave-active.ant-dropdown-placement-bottomLeft, -.ant-dropdown.ant-slide-down-leave.ant-slide-down-leave-active.ant-dropdown-placement-bottomCenter, -.ant-dropdown.ant-slide-down-leave.ant-slide-down-leave-active.ant-dropdown-placement-bottomRight { - -webkit-animation-name: antSlideUpOut; - animation-name: antSlideUpOut; -} - -.ant-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-dropdown-placement-topLeft, -.ant-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-dropdown-placement-topCenter, -.ant-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-dropdown-placement-topRight { - -webkit-animation-name: antSlideDownOut; - animation-name: antSlideDownOut; -} - -.ant-dropdown-trigger > .anticon.anticon-down, -.ant-dropdown-link > .anticon.anticon-down, -.ant-dropdown-button > .anticon.anticon-down { - font-size: 10px; - vertical-align: baseline; -} - -.ant-dropdown-button { - white-space: nowrap; -} - -.ant-dropdown-button.ant-btn-group > .ant-btn:last-child:not(:first-child):not(.ant-btn-icon-only) { - padding-right: 8px; - padding-left: 8px; -} - -.ant-dropdown-menu-dark, -.ant-dropdown-menu-dark .ant-dropdown-menu { - background: #001529; -} - -.ant-dropdown-menu-dark .ant-dropdown-menu-item, -.ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title, -.ant-dropdown-menu-dark .ant-dropdown-menu-item > a, -.ant-dropdown-menu-dark .ant-dropdown-menu-item > .anticon + span > a { - color: rgba(255, 255, 255, 0.65); -} - -.ant-dropdown-menu-dark .ant-dropdown-menu-item .ant-dropdown-menu-submenu-arrow::after, -.ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow::after, -.ant-dropdown-menu-dark .ant-dropdown-menu-item > a .ant-dropdown-menu-submenu-arrow::after, -.ant-dropdown-menu-dark .ant-dropdown-menu-item > .anticon + span > a .ant-dropdown-menu-submenu-arrow::after { - color: rgba(255, 255, 255, 0.65); -} - -.ant-dropdown-menu-dark .ant-dropdown-menu-item:hover, -.ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title:hover, -.ant-dropdown-menu-dark .ant-dropdown-menu-item > a:hover, -.ant-dropdown-menu-dark .ant-dropdown-menu-item > .anticon + span > a:hover { - color: #fff; - background: transparent; -} - -.ant-dropdown-menu-dark .ant-dropdown-menu-item-selected, -.ant-dropdown-menu-dark .ant-dropdown-menu-item-selected:hover, -.ant-dropdown-menu-dark .ant-dropdown-menu-item-selected > a { - color: #fff; - background: #1890ff; -} - -.ant-btn { - line-height: 1.5715; - position: relative; - display: inline-block; - font-weight: 400; - white-space: nowrap; - text-align: center; - background-image: none; - border: 1px solid transparent; - box-shadow: 0 2px 0 rgba(0, 0, 0, 0.015); - cursor: pointer; - transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - touch-action: manipulation; - height: 32px; - padding: 4px 15px; - font-size: 14px; - border-radius: 2px; - color: rgba(0, 0, 0, 0.85); - border-color: #d9d9d9; - background: #fff; -} - -.ant-btn > .anticon { - line-height: 1; -} - -.ant-btn, -.ant-btn:active, -.ant-btn:focus { - outline: 0; -} - -.ant-btn:not([disabled]):hover { - text-decoration: none; -} - -.ant-btn:not([disabled]):active { - outline: 0; - box-shadow: none; -} - -.ant-btn[disabled] { - cursor: not-allowed; -} - -.ant-btn[disabled] > * { - pointer-events: none; -} - -.ant-btn-lg { - height: 40px; - padding: 6.4px 15px; - font-size: 16px; - border-radius: 2px; -} - -.ant-btn-sm { - height: 24px; - padding: 0px 7px; - font-size: 14px; - border-radius: 2px; -} - -.ant-btn > a:only-child { - color: currentColor; -} - -.ant-btn > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn:hover, -.ant-btn:focus { - color: #40a9ff; - border-color: #40a9ff; - background: #fff; -} - -.ant-btn:hover > a:only-child, -.ant-btn:focus > a:only-child { - color: currentColor; -} - -.ant-btn:hover > a:only-child::after, -.ant-btn:focus > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn:active { - color: #096dd9; - border-color: #096dd9; - background: #fff; -} - -.ant-btn:active > a:only-child { - color: currentColor; -} - -.ant-btn:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn[disabled], -.ant-btn[disabled]:hover, -.ant-btn[disabled]:focus, -.ant-btn[disabled]:active { - color: rgba(0, 0, 0, 0.25); - border-color: #d9d9d9; - background: #f5f5f5; - text-shadow: none; - box-shadow: none; -} - -.ant-btn[disabled] > a:only-child, -.ant-btn[disabled]:hover > a:only-child, -.ant-btn[disabled]:focus > a:only-child, -.ant-btn[disabled]:active > a:only-child { - color: currentColor; -} - -.ant-btn[disabled] > a:only-child::after, -.ant-btn[disabled]:hover > a:only-child::after, -.ant-btn[disabled]:focus > a:only-child::after, -.ant-btn[disabled]:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn:hover, -.ant-btn:focus, -.ant-btn:active { - text-decoration: none; - background: #fff; -} - -.ant-btn > span { - display: inline-block; -} - -.ant-btn-primary { - color: #fff; - border-color: #1890ff; - background: #1890ff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12); - box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045); -} - -.ant-btn-primary > a:only-child { - color: currentColor; -} - -.ant-btn-primary > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-primary:hover, -.ant-btn-primary:focus { - color: #fff; - border-color: #40a9ff; - background: #40a9ff; -} - -.ant-btn-primary:hover > a:only-child, -.ant-btn-primary:focus > a:only-child { - color: currentColor; -} - -.ant-btn-primary:hover > a:only-child::after, -.ant-btn-primary:focus > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-primary:active { - color: #fff; - border-color: #096dd9; - background: #096dd9; -} - -.ant-btn-primary:active > a:only-child { - color: currentColor; -} - -.ant-btn-primary:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-primary[disabled], -.ant-btn-primary[disabled]:hover, -.ant-btn-primary[disabled]:focus, -.ant-btn-primary[disabled]:active { - color: rgba(0, 0, 0, 0.25); - border-color: #d9d9d9; - background: #f5f5f5; - text-shadow: none; - box-shadow: none; -} - -.ant-btn-primary[disabled] > a:only-child, -.ant-btn-primary[disabled]:hover > a:only-child, -.ant-btn-primary[disabled]:focus > a:only-child, -.ant-btn-primary[disabled]:active > a:only-child { - color: currentColor; -} - -.ant-btn-primary[disabled] > a:only-child::after, -.ant-btn-primary[disabled]:hover > a:only-child::after, -.ant-btn-primary[disabled]:focus > a:only-child::after, -.ant-btn-primary[disabled]:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-group .ant-btn-primary:not(:first-child):not(:last-child) { - border-right-color: #40a9ff; - border-left-color: #40a9ff; -} - -.ant-btn-group .ant-btn-primary:not(:first-child):not(:last-child):disabled { - border-color: #d9d9d9; -} - -.ant-btn-group .ant-btn-primary:first-child:not(:last-child) { - border-right-color: #40a9ff; -} - -.ant-btn-group .ant-btn-primary:first-child:not(:last-child)[disabled] { - border-right-color: #d9d9d9; -} - -.ant-btn-group .ant-btn-primary:last-child:not(:first-child), -.ant-btn-group .ant-btn-primary + .ant-btn-primary { - border-left-color: #40a9ff; -} - -.ant-btn-group .ant-btn-primary:last-child:not(:first-child)[disabled], -.ant-btn-group .ant-btn-primary + .ant-btn-primary[disabled] { - border-left-color: #d9d9d9; -} - -.ant-btn-ghost { - color: rgba(0, 0, 0, 0.85); - border-color: #d9d9d9; - background: transparent; -} - -.ant-btn-ghost > a:only-child { - color: currentColor; -} - -.ant-btn-ghost > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-ghost:hover, -.ant-btn-ghost:focus { - color: #40a9ff; - border-color: #40a9ff; - background: transparent; -} - -.ant-btn-ghost:hover > a:only-child, -.ant-btn-ghost:focus > a:only-child { - color: currentColor; -} - -.ant-btn-ghost:hover > a:only-child::after, -.ant-btn-ghost:focus > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-ghost:active { - color: #096dd9; - border-color: #096dd9; - background: transparent; -} - -.ant-btn-ghost:active > a:only-child { - color: currentColor; -} - -.ant-btn-ghost:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-ghost[disabled], -.ant-btn-ghost[disabled]:hover, -.ant-btn-ghost[disabled]:focus, -.ant-btn-ghost[disabled]:active { - color: rgba(0, 0, 0, 0.25); - border-color: #d9d9d9; - background: #f5f5f5; - text-shadow: none; - box-shadow: none; -} - -.ant-btn-ghost[disabled] > a:only-child, -.ant-btn-ghost[disabled]:hover > a:only-child, -.ant-btn-ghost[disabled]:focus > a:only-child, -.ant-btn-ghost[disabled]:active > a:only-child { - color: currentColor; -} - -.ant-btn-ghost[disabled] > a:only-child::after, -.ant-btn-ghost[disabled]:hover > a:only-child::after, -.ant-btn-ghost[disabled]:focus > a:only-child::after, -.ant-btn-ghost[disabled]:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dashed { - color: rgba(0, 0, 0, 0.85); - border-color: #d9d9d9; - background: #fff; - border-style: dashed; -} - -.ant-btn-dashed > a:only-child { - color: currentColor; -} - -.ant-btn-dashed > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dashed:hover, -.ant-btn-dashed:focus { - color: #40a9ff; - border-color: #40a9ff; - background: #fff; -} - -.ant-btn-dashed:hover > a:only-child, -.ant-btn-dashed:focus > a:only-child { - color: currentColor; -} - -.ant-btn-dashed:hover > a:only-child::after, -.ant-btn-dashed:focus > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dashed:active { - color: #096dd9; - border-color: #096dd9; - background: #fff; -} - -.ant-btn-dashed:active > a:only-child { - color: currentColor; -} - -.ant-btn-dashed:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dashed[disabled], -.ant-btn-dashed[disabled]:hover, -.ant-btn-dashed[disabled]:focus, -.ant-btn-dashed[disabled]:active { - color: rgba(0, 0, 0, 0.25); - border-color: #d9d9d9; - background: #f5f5f5; - text-shadow: none; - box-shadow: none; -} - -.ant-btn-dashed[disabled] > a:only-child, -.ant-btn-dashed[disabled]:hover > a:only-child, -.ant-btn-dashed[disabled]:focus > a:only-child, -.ant-btn-dashed[disabled]:active > a:only-child { - color: currentColor; -} - -.ant-btn-dashed[disabled] > a:only-child::after, -.ant-btn-dashed[disabled]:hover > a:only-child::after, -.ant-btn-dashed[disabled]:focus > a:only-child::after, -.ant-btn-dashed[disabled]:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-danger { - color: #fff; - border-color: #ff4d4f; - background: #ff4d4f; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12); - box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045); -} - -.ant-btn-danger > a:only-child { - color: currentColor; -} - -.ant-btn-danger > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-danger:hover, -.ant-btn-danger:focus { - color: #fff; - border-color: #ff7875; - background: #ff7875; -} - -.ant-btn-danger:hover > a:only-child, -.ant-btn-danger:focus > a:only-child { - color: currentColor; -} - -.ant-btn-danger:hover > a:only-child::after, -.ant-btn-danger:focus > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-danger:active { - color: #fff; - border-color: #d9363e; - background: #d9363e; -} - -.ant-btn-danger:active > a:only-child { - color: currentColor; -} - -.ant-btn-danger:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-danger[disabled], -.ant-btn-danger[disabled]:hover, -.ant-btn-danger[disabled]:focus, -.ant-btn-danger[disabled]:active { - color: rgba(0, 0, 0, 0.25); - border-color: #d9d9d9; - background: #f5f5f5; - text-shadow: none; - box-shadow: none; -} - -.ant-btn-danger[disabled] > a:only-child, -.ant-btn-danger[disabled]:hover > a:only-child, -.ant-btn-danger[disabled]:focus > a:only-child, -.ant-btn-danger[disabled]:active > a:only-child { - color: currentColor; -} - -.ant-btn-danger[disabled] > a:only-child::after, -.ant-btn-danger[disabled]:hover > a:only-child::after, -.ant-btn-danger[disabled]:focus > a:only-child::after, -.ant-btn-danger[disabled]:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-link { - color: #1890ff; - border-color: transparent; - background: transparent; - box-shadow: none; -} - -.ant-btn-link > a:only-child { - color: currentColor; -} - -.ant-btn-link > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-link:hover, -.ant-btn-link:focus { - color: #40a9ff; - border-color: #40a9ff; - background: transparent; -} - -.ant-btn-link:hover > a:only-child, -.ant-btn-link:focus > a:only-child { - color: currentColor; -} - -.ant-btn-link:hover > a:only-child::after, -.ant-btn-link:focus > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-link:active { - color: #096dd9; - border-color: #096dd9; - background: transparent; -} - -.ant-btn-link:active > a:only-child { - color: currentColor; -} - -.ant-btn-link:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-link[disabled], -.ant-btn-link[disabled]:hover, -.ant-btn-link[disabled]:focus, -.ant-btn-link[disabled]:active { - color: rgba(0, 0, 0, 0.25); - border-color: #d9d9d9; - background: #f5f5f5; - text-shadow: none; - box-shadow: none; -} - -.ant-btn-link[disabled] > a:only-child, -.ant-btn-link[disabled]:hover > a:only-child, -.ant-btn-link[disabled]:focus > a:only-child, -.ant-btn-link[disabled]:active > a:only-child { - color: currentColor; -} - -.ant-btn-link[disabled] > a:only-child::after, -.ant-btn-link[disabled]:hover > a:only-child::after, -.ant-btn-link[disabled]:focus > a:only-child::after, -.ant-btn-link[disabled]:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-link:hover { - background: transparent; -} - -.ant-btn-link:hover, -.ant-btn-link:focus, -.ant-btn-link:active { - border-color: transparent; -} - -.ant-btn-link[disabled], -.ant-btn-link[disabled]:hover, -.ant-btn-link[disabled]:focus, -.ant-btn-link[disabled]:active { - color: rgba(0, 0, 0, 0.25); - border-color: transparent; - background: transparent; - text-shadow: none; - box-shadow: none; -} - -.ant-btn-link[disabled] > a:only-child, -.ant-btn-link[disabled]:hover > a:only-child, -.ant-btn-link[disabled]:focus > a:only-child, -.ant-btn-link[disabled]:active > a:only-child { - color: currentColor; -} - -.ant-btn-link[disabled] > a:only-child::after, -.ant-btn-link[disabled]:hover > a:only-child::after, -.ant-btn-link[disabled]:focus > a:only-child::after, -.ant-btn-link[disabled]:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-text { - color: rgba(0, 0, 0, 0.85); - border-color: transparent; - background: transparent; - box-shadow: none; -} - -.ant-btn-text > a:only-child { - color: currentColor; -} - -.ant-btn-text > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-text:hover, -.ant-btn-text:focus { - color: #40a9ff; - border-color: #40a9ff; - background: transparent; -} - -.ant-btn-text:hover > a:only-child, -.ant-btn-text:focus > a:only-child { - color: currentColor; -} - -.ant-btn-text:hover > a:only-child::after, -.ant-btn-text:focus > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-text:active { - color: #096dd9; - border-color: #096dd9; - background: transparent; -} - -.ant-btn-text:active > a:only-child { - color: currentColor; -} - -.ant-btn-text:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-text[disabled], -.ant-btn-text[disabled]:hover, -.ant-btn-text[disabled]:focus, -.ant-btn-text[disabled]:active { - color: rgba(0, 0, 0, 0.25); - border-color: #d9d9d9; - background: #f5f5f5; - text-shadow: none; - box-shadow: none; -} - -.ant-btn-text[disabled] > a:only-child, -.ant-btn-text[disabled]:hover > a:only-child, -.ant-btn-text[disabled]:focus > a:only-child, -.ant-btn-text[disabled]:active > a:only-child { - color: currentColor; -} - -.ant-btn-text[disabled] > a:only-child::after, -.ant-btn-text[disabled]:hover > a:only-child::after, -.ant-btn-text[disabled]:focus > a:only-child::after, -.ant-btn-text[disabled]:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-text:hover, -.ant-btn-text:focus { - color: rgba(0, 0, 0, 0.85); - background: rgba(0, 0, 0, 0.018); - border-color: transparent; -} - -.ant-btn-text:active { - color: rgba(0, 0, 0, 0.85); - background: rgba(0, 0, 0, 0.028); - border-color: transparent; -} - -.ant-btn-text[disabled], -.ant-btn-text[disabled]:hover, -.ant-btn-text[disabled]:focus, -.ant-btn-text[disabled]:active { - color: rgba(0, 0, 0, 0.25); - border-color: transparent; - background: transparent; - text-shadow: none; - box-shadow: none; -} - -.ant-btn-text[disabled] > a:only-child, -.ant-btn-text[disabled]:hover > a:only-child, -.ant-btn-text[disabled]:focus > a:only-child, -.ant-btn-text[disabled]:active > a:only-child { - color: currentColor; -} - -.ant-btn-text[disabled] > a:only-child::after, -.ant-btn-text[disabled]:hover > a:only-child::after, -.ant-btn-text[disabled]:focus > a:only-child::after, -.ant-btn-text[disabled]:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous { - color: #ff4d4f; - border-color: #ff4d4f; - background: #fff; -} - -.ant-btn-dangerous > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous:hover, -.ant-btn-dangerous:focus { - color: #ff7875; - border-color: #ff7875; - background: #fff; -} - -.ant-btn-dangerous:hover > a:only-child, -.ant-btn-dangerous:focus > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous:hover > a:only-child::after, -.ant-btn-dangerous:focus > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous:active { - color: #d9363e; - border-color: #d9363e; - background: #fff; -} - -.ant-btn-dangerous:active > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous[disabled], -.ant-btn-dangerous[disabled]:hover, -.ant-btn-dangerous[disabled]:focus, -.ant-btn-dangerous[disabled]:active { - color: rgba(0, 0, 0, 0.25); - border-color: #d9d9d9; - background: #f5f5f5; - text-shadow: none; - box-shadow: none; -} - -.ant-btn-dangerous[disabled] > a:only-child, -.ant-btn-dangerous[disabled]:hover > a:only-child, -.ant-btn-dangerous[disabled]:focus > a:only-child, -.ant-btn-dangerous[disabled]:active > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous[disabled] > a:only-child::after, -.ant-btn-dangerous[disabled]:hover > a:only-child::after, -.ant-btn-dangerous[disabled]:focus > a:only-child::after, -.ant-btn-dangerous[disabled]:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous.ant-btn-primary { - color: #fff; - border-color: #ff4d4f; - background: #ff4d4f; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12); - box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045); -} - -.ant-btn-dangerous.ant-btn-primary > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous.ant-btn-primary > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous.ant-btn-primary:hover, -.ant-btn-dangerous.ant-btn-primary:focus { - color: #fff; - border-color: #ff7875; - background: #ff7875; -} - -.ant-btn-dangerous.ant-btn-primary:hover > a:only-child, -.ant-btn-dangerous.ant-btn-primary:focus > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous.ant-btn-primary:hover > a:only-child::after, -.ant-btn-dangerous.ant-btn-primary:focus > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous.ant-btn-primary:active { - color: #fff; - border-color: #d9363e; - background: #d9363e; -} - -.ant-btn-dangerous.ant-btn-primary:active > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous.ant-btn-primary:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous.ant-btn-primary[disabled], -.ant-btn-dangerous.ant-btn-primary[disabled]:hover, -.ant-btn-dangerous.ant-btn-primary[disabled]:focus, -.ant-btn-dangerous.ant-btn-primary[disabled]:active { - color: rgba(0, 0, 0, 0.25); - border-color: #d9d9d9; - background: #f5f5f5; - text-shadow: none; - box-shadow: none; -} - -.ant-btn-dangerous.ant-btn-primary[disabled] > a:only-child, -.ant-btn-dangerous.ant-btn-primary[disabled]:hover > a:only-child, -.ant-btn-dangerous.ant-btn-primary[disabled]:focus > a:only-child, -.ant-btn-dangerous.ant-btn-primary[disabled]:active > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous.ant-btn-primary[disabled] > a:only-child::after, -.ant-btn-dangerous.ant-btn-primary[disabled]:hover > a:only-child::after, -.ant-btn-dangerous.ant-btn-primary[disabled]:focus > a:only-child::after, -.ant-btn-dangerous.ant-btn-primary[disabled]:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous.ant-btn-link { - color: #ff4d4f; - border-color: transparent; - background: transparent; - box-shadow: none; -} - -.ant-btn-dangerous.ant-btn-link > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous.ant-btn-link > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous.ant-btn-link:hover, -.ant-btn-dangerous.ant-btn-link:focus { - color: #40a9ff; - border-color: #40a9ff; - background: transparent; -} - -.ant-btn-dangerous.ant-btn-link:hover > a:only-child, -.ant-btn-dangerous.ant-btn-link:focus > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous.ant-btn-link:hover > a:only-child::after, -.ant-btn-dangerous.ant-btn-link:focus > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous.ant-btn-link:active { - color: #096dd9; - border-color: #096dd9; - background: transparent; -} - -.ant-btn-dangerous.ant-btn-link:active > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous.ant-btn-link:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous.ant-btn-link[disabled], -.ant-btn-dangerous.ant-btn-link[disabled]:hover, -.ant-btn-dangerous.ant-btn-link[disabled]:focus, -.ant-btn-dangerous.ant-btn-link[disabled]:active { - color: rgba(0, 0, 0, 0.25); - border-color: #d9d9d9; - background: #f5f5f5; - text-shadow: none; - box-shadow: none; -} - -.ant-btn-dangerous.ant-btn-link[disabled] > a:only-child, -.ant-btn-dangerous.ant-btn-link[disabled]:hover > a:only-child, -.ant-btn-dangerous.ant-btn-link[disabled]:focus > a:only-child, -.ant-btn-dangerous.ant-btn-link[disabled]:active > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous.ant-btn-link[disabled] > a:only-child::after, -.ant-btn-dangerous.ant-btn-link[disabled]:hover > a:only-child::after, -.ant-btn-dangerous.ant-btn-link[disabled]:focus > a:only-child::after, -.ant-btn-dangerous.ant-btn-link[disabled]:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous.ant-btn-link:hover, -.ant-btn-dangerous.ant-btn-link:focus { - color: #ff7875; - border-color: transparent; - background: transparent; -} - -.ant-btn-dangerous.ant-btn-link:hover > a:only-child, -.ant-btn-dangerous.ant-btn-link:focus > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous.ant-btn-link:hover > a:only-child::after, -.ant-btn-dangerous.ant-btn-link:focus > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous.ant-btn-link:active { - color: #d9363e; - border-color: transparent; - background: transparent; -} - -.ant-btn-dangerous.ant-btn-link:active > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous.ant-btn-link:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous.ant-btn-link[disabled], -.ant-btn-dangerous.ant-btn-link[disabled]:hover, -.ant-btn-dangerous.ant-btn-link[disabled]:focus, -.ant-btn-dangerous.ant-btn-link[disabled]:active { - color: rgba(0, 0, 0, 0.25); - border-color: transparent; - background: transparent; - text-shadow: none; - box-shadow: none; -} - -.ant-btn-dangerous.ant-btn-link[disabled] > a:only-child, -.ant-btn-dangerous.ant-btn-link[disabled]:hover > a:only-child, -.ant-btn-dangerous.ant-btn-link[disabled]:focus > a:only-child, -.ant-btn-dangerous.ant-btn-link[disabled]:active > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous.ant-btn-link[disabled] > a:only-child::after, -.ant-btn-dangerous.ant-btn-link[disabled]:hover > a:only-child::after, -.ant-btn-dangerous.ant-btn-link[disabled]:focus > a:only-child::after, -.ant-btn-dangerous.ant-btn-link[disabled]:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous.ant-btn-text { - color: #ff4d4f; - border-color: transparent; - background: transparent; - box-shadow: none; -} - -.ant-btn-dangerous.ant-btn-text > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous.ant-btn-text > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous.ant-btn-text:hover, -.ant-btn-dangerous.ant-btn-text:focus { - color: #40a9ff; - border-color: #40a9ff; - background: transparent; -} - -.ant-btn-dangerous.ant-btn-text:hover > a:only-child, -.ant-btn-dangerous.ant-btn-text:focus > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous.ant-btn-text:hover > a:only-child::after, -.ant-btn-dangerous.ant-btn-text:focus > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous.ant-btn-text:active { - color: #096dd9; - border-color: #096dd9; - background: transparent; -} - -.ant-btn-dangerous.ant-btn-text:active > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous.ant-btn-text:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous.ant-btn-text[disabled], -.ant-btn-dangerous.ant-btn-text[disabled]:hover, -.ant-btn-dangerous.ant-btn-text[disabled]:focus, -.ant-btn-dangerous.ant-btn-text[disabled]:active { - color: rgba(0, 0, 0, 0.25); - border-color: #d9d9d9; - background: #f5f5f5; - text-shadow: none; - box-shadow: none; -} - -.ant-btn-dangerous.ant-btn-text[disabled] > a:only-child, -.ant-btn-dangerous.ant-btn-text[disabled]:hover > a:only-child, -.ant-btn-dangerous.ant-btn-text[disabled]:focus > a:only-child, -.ant-btn-dangerous.ant-btn-text[disabled]:active > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous.ant-btn-text[disabled] > a:only-child::after, -.ant-btn-dangerous.ant-btn-text[disabled]:hover > a:only-child::after, -.ant-btn-dangerous.ant-btn-text[disabled]:focus > a:only-child::after, -.ant-btn-dangerous.ant-btn-text[disabled]:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous.ant-btn-text:hover, -.ant-btn-dangerous.ant-btn-text:focus { - color: #ff7875; - border-color: transparent; - background: rgba(0, 0, 0, 0.018); -} - -.ant-btn-dangerous.ant-btn-text:hover > a:only-child, -.ant-btn-dangerous.ant-btn-text:focus > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous.ant-btn-text:hover > a:only-child::after, -.ant-btn-dangerous.ant-btn-text:focus > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous.ant-btn-text:active { - color: #d9363e; - border-color: transparent; - background: rgba(0, 0, 0, 0.028); -} - -.ant-btn-dangerous.ant-btn-text:active > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous.ant-btn-text:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-dangerous.ant-btn-text[disabled], -.ant-btn-dangerous.ant-btn-text[disabled]:hover, -.ant-btn-dangerous.ant-btn-text[disabled]:focus, -.ant-btn-dangerous.ant-btn-text[disabled]:active { - color: rgba(0, 0, 0, 0.25); - border-color: transparent; - background: transparent; - text-shadow: none; - box-shadow: none; -} - -.ant-btn-dangerous.ant-btn-text[disabled] > a:only-child, -.ant-btn-dangerous.ant-btn-text[disabled]:hover > a:only-child, -.ant-btn-dangerous.ant-btn-text[disabled]:focus > a:only-child, -.ant-btn-dangerous.ant-btn-text[disabled]:active > a:only-child { - color: currentColor; -} - -.ant-btn-dangerous.ant-btn-text[disabled] > a:only-child::after, -.ant-btn-dangerous.ant-btn-text[disabled]:hover > a:only-child::after, -.ant-btn-dangerous.ant-btn-text[disabled]:focus > a:only-child::after, -.ant-btn-dangerous.ant-btn-text[disabled]:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-icon-only { - width: 32px; - height: 32px; - padding: 2.4px 0; - font-size: 16px; - border-radius: 2px; - vertical-align: -3px; -} - -.ant-btn-icon-only > * { - font-size: 16px; -} - -.ant-btn-icon-only.ant-btn-lg { - width: 40px; - height: 40px; - padding: 4.9px 0; - font-size: 18px; - border-radius: 2px; -} - -.ant-btn-icon-only.ant-btn-lg > * { - font-size: 18px; -} - -.ant-btn-icon-only.ant-btn-sm { - width: 24px; - height: 24px; - padding: 0px 0; - font-size: 14px; - border-radius: 2px; -} - -.ant-btn-icon-only.ant-btn-sm > * { - font-size: 14px; -} - -.ant-btn-icon-only > .anticon { - display: flex; - justify-content: center; -} - -a.ant-btn-icon-only { - vertical-align: -1px; -} - -a.ant-btn-icon-only > .anticon { - display: inline; -} - -.ant-btn-round { - height: 32px; - padding: 4px 16px; - font-size: 14px; - border-radius: 32px; -} - -.ant-btn-round.ant-btn-lg { - height: 40px; - padding: 6.4px 20px; - font-size: 16px; - border-radius: 40px; -} - -.ant-btn-round.ant-btn-sm { - height: 24px; - padding: 0px 12px; - font-size: 14px; - border-radius: 24px; -} - -.ant-btn-round.ant-btn-icon-only { - width: auto; -} - -.ant-btn-circle { - min-width: 32px; - padding-right: 0; - padding-left: 0; - text-align: center; - border-radius: 50%; -} - -.ant-btn-circle.ant-btn-lg { - min-width: 40px; - border-radius: 50%; -} - -.ant-btn-circle.ant-btn-sm { - min-width: 24px; - border-radius: 50%; -} - -.ant-btn::before { - position: absolute; - top: -1px; - right: -1px; - bottom: -1px; - left: -1px; - z-index: 1; - display: none; - background: #fff; - border-radius: inherit; - opacity: 0.35; - transition: opacity 0.2s; - content: ''; - pointer-events: none; -} - -.ant-btn .anticon { - transition: margin-left 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); -} - -.ant-btn .anticon.anticon-plus > svg, -.ant-btn .anticon.anticon-minus > svg { - shape-rendering: optimizeSpeed; -} - -.ant-btn.ant-btn-loading { - position: relative; - cursor: default; -} - -.ant-btn.ant-btn-loading::before { - display: block; -} - -.ant-btn > .ant-btn-loading-icon { - transition: width 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), opacity 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); -} - -.ant-btn > .ant-btn-loading-icon .anticon { - padding-right: 8px; - -webkit-animation: none; - animation: none; -} - -.ant-btn > .ant-btn-loading-icon .anticon svg { - -webkit-animation: loadingCircle 1s infinite linear; - animation: loadingCircle 1s infinite linear; -} - -.ant-btn > .ant-btn-loading-icon:only-child .anticon { - padding-right: 0; -} - -.ant-btn-group { - position: relative; - display: inline-flex; -} - -.ant-btn-group > .ant-btn, -.ant-btn-group > span > .ant-btn { - position: relative; -} - -.ant-btn-group > .ant-btn:hover, -.ant-btn-group > span > .ant-btn:hover, -.ant-btn-group > .ant-btn:focus, -.ant-btn-group > span > .ant-btn:focus, -.ant-btn-group > .ant-btn:active, -.ant-btn-group > span > .ant-btn:active { - z-index: 2; -} - -.ant-btn-group > .ant-btn[disabled], -.ant-btn-group > span > .ant-btn[disabled] { - z-index: 0; -} - -.ant-btn-group .ant-btn-icon-only { - font-size: 14px; -} - -.ant-btn-group-lg > .ant-btn, -.ant-btn-group-lg > span > .ant-btn { - height: 40px; - padding: 6.4px 15px; - font-size: 16px; - border-radius: 0; -} - -.ant-btn-group-lg .ant-btn.ant-btn-icon-only { - width: 40px; - height: 40px; - padding-right: 0; - padding-left: 0; -} - -.ant-btn-group-sm > .ant-btn, -.ant-btn-group-sm > span > .ant-btn { - height: 24px; - padding: 0px 7px; - font-size: 14px; - border-radius: 0; -} - -.ant-btn-group-sm > .ant-btn > .anticon, -.ant-btn-group-sm > span > .ant-btn > .anticon { - font-size: 14px; -} - -.ant-btn-group-sm .ant-btn.ant-btn-icon-only { - width: 24px; - height: 24px; - padding-right: 0; - padding-left: 0; -} - -.ant-btn-group .ant-btn + .ant-btn, -.ant-btn + .ant-btn-group, -.ant-btn-group span + .ant-btn, -.ant-btn-group .ant-btn + span, -.ant-btn-group > span + span, -.ant-btn-group + .ant-btn, -.ant-btn-group + .ant-btn-group { - margin-left: -1px; -} - -.ant-btn-group .ant-btn-primary + .ant-btn:not(.ant-btn-primary):not([disabled]) { - border-left-color: transparent; -} - -.ant-btn-group .ant-btn { - border-radius: 0; -} - -.ant-btn-group > .ant-btn:first-child, -.ant-btn-group > span:first-child > .ant-btn { - margin-left: 0; -} - -.ant-btn-group > .ant-btn:only-child { - border-radius: 2px; -} - -.ant-btn-group > span:only-child > .ant-btn { - border-radius: 2px; -} - -.ant-btn-group > .ant-btn:first-child:not(:last-child), -.ant-btn-group > span:first-child:not(:last-child) > .ant-btn { - border-top-left-radius: 2px; - border-bottom-left-radius: 2px; -} - -.ant-btn-group > .ant-btn:last-child:not(:first-child), -.ant-btn-group > span:last-child:not(:first-child) > .ant-btn { - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; -} - -.ant-btn-group-sm > .ant-btn:only-child { - border-radius: 2px; -} - -.ant-btn-group-sm > span:only-child > .ant-btn { - border-radius: 2px; -} - -.ant-btn-group-sm > .ant-btn:first-child:not(:last-child), -.ant-btn-group-sm > span:first-child:not(:last-child) > .ant-btn { - border-top-left-radius: 2px; - border-bottom-left-radius: 2px; -} - -.ant-btn-group-sm > .ant-btn:last-child:not(:first-child), -.ant-btn-group-sm > span:last-child:not(:first-child) > .ant-btn { - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; -} - -.ant-btn-group > .ant-btn-group { - float: left; -} - -.ant-btn-group > .ant-btn-group:not(:first-child):not(:last-child) > .ant-btn { - border-radius: 0; -} - -.ant-btn-group > .ant-btn-group:first-child:not(:last-child) > .ant-btn:last-child { - padding-right: 8px; - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.ant-btn-group > .ant-btn-group:last-child:not(:first-child) > .ant-btn:first-child { - padding-left: 8px; - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.ant-btn:focus > span, -.ant-btn:active > span { - position: relative; -} - -.ant-btn > .anticon + span, -.ant-btn > span + .anticon { - margin-left: 8px; -} - -.ant-btn.ant-btn-background-ghost { - color: #fff; - border-color: #fff; -} - -.ant-btn.ant-btn-background-ghost, -.ant-btn.ant-btn-background-ghost:hover, -.ant-btn.ant-btn-background-ghost:active, -.ant-btn.ant-btn-background-ghost:focus { - background: transparent; -} - -.ant-btn.ant-btn-background-ghost:hover, -.ant-btn.ant-btn-background-ghost:focus { - color: #40a9ff; - border-color: #40a9ff; -} - -.ant-btn.ant-btn-background-ghost:active { - color: #096dd9; - border-color: #096dd9; -} - -.ant-btn.ant-btn-background-ghost[disabled] { - color: rgba(0, 0, 0, 0.25); - background: transparent; - border-color: #d9d9d9; -} - -.ant-btn-background-ghost.ant-btn-primary { - color: #1890ff; - border-color: #1890ff; - text-shadow: none; -} - -.ant-btn-background-ghost.ant-btn-primary > a:only-child { - color: currentColor; -} - -.ant-btn-background-ghost.ant-btn-primary > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-background-ghost.ant-btn-primary:hover, -.ant-btn-background-ghost.ant-btn-primary:focus { - color: #40a9ff; - border-color: #40a9ff; -} - -.ant-btn-background-ghost.ant-btn-primary:hover > a:only-child, -.ant-btn-background-ghost.ant-btn-primary:focus > a:only-child { - color: currentColor; -} - -.ant-btn-background-ghost.ant-btn-primary:hover > a:only-child::after, -.ant-btn-background-ghost.ant-btn-primary:focus > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-background-ghost.ant-btn-primary:active { - color: #096dd9; - border-color: #096dd9; -} - -.ant-btn-background-ghost.ant-btn-primary:active > a:only-child { - color: currentColor; -} - -.ant-btn-background-ghost.ant-btn-primary:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-background-ghost.ant-btn-primary[disabled], -.ant-btn-background-ghost.ant-btn-primary[disabled]:hover, -.ant-btn-background-ghost.ant-btn-primary[disabled]:focus, -.ant-btn-background-ghost.ant-btn-primary[disabled]:active { - color: rgba(0, 0, 0, 0.25); - border-color: #d9d9d9; - background: #f5f5f5; - text-shadow: none; - box-shadow: none; -} - -.ant-btn-background-ghost.ant-btn-primary[disabled] > a:only-child, -.ant-btn-background-ghost.ant-btn-primary[disabled]:hover > a:only-child, -.ant-btn-background-ghost.ant-btn-primary[disabled]:focus > a:only-child, -.ant-btn-background-ghost.ant-btn-primary[disabled]:active > a:only-child { - color: currentColor; -} - -.ant-btn-background-ghost.ant-btn-primary[disabled] > a:only-child::after, -.ant-btn-background-ghost.ant-btn-primary[disabled]:hover > a:only-child::after, -.ant-btn-background-ghost.ant-btn-primary[disabled]:focus > a:only-child::after, -.ant-btn-background-ghost.ant-btn-primary[disabled]:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-background-ghost.ant-btn-danger { - color: #ff4d4f; - border-color: #ff4d4f; - text-shadow: none; -} - -.ant-btn-background-ghost.ant-btn-danger > a:only-child { - color: currentColor; -} - -.ant-btn-background-ghost.ant-btn-danger > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-background-ghost.ant-btn-danger:hover, -.ant-btn-background-ghost.ant-btn-danger:focus { - color: #ff7875; - border-color: #ff7875; -} - -.ant-btn-background-ghost.ant-btn-danger:hover > a:only-child, -.ant-btn-background-ghost.ant-btn-danger:focus > a:only-child { - color: currentColor; -} - -.ant-btn-background-ghost.ant-btn-danger:hover > a:only-child::after, -.ant-btn-background-ghost.ant-btn-danger:focus > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-background-ghost.ant-btn-danger:active { - color: #d9363e; - border-color: #d9363e; -} - -.ant-btn-background-ghost.ant-btn-danger:active > a:only-child { - color: currentColor; -} - -.ant-btn-background-ghost.ant-btn-danger:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-background-ghost.ant-btn-danger[disabled], -.ant-btn-background-ghost.ant-btn-danger[disabled]:hover, -.ant-btn-background-ghost.ant-btn-danger[disabled]:focus, -.ant-btn-background-ghost.ant-btn-danger[disabled]:active { - color: rgba(0, 0, 0, 0.25); - border-color: #d9d9d9; - background: #f5f5f5; - text-shadow: none; - box-shadow: none; -} - -.ant-btn-background-ghost.ant-btn-danger[disabled] > a:only-child, -.ant-btn-background-ghost.ant-btn-danger[disabled]:hover > a:only-child, -.ant-btn-background-ghost.ant-btn-danger[disabled]:focus > a:only-child, -.ant-btn-background-ghost.ant-btn-danger[disabled]:active > a:only-child { - color: currentColor; -} - -.ant-btn-background-ghost.ant-btn-danger[disabled] > a:only-child::after, -.ant-btn-background-ghost.ant-btn-danger[disabled]:hover > a:only-child::after, -.ant-btn-background-ghost.ant-btn-danger[disabled]:focus > a:only-child::after, -.ant-btn-background-ghost.ant-btn-danger[disabled]:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-background-ghost.ant-btn-dangerous { - color: #ff4d4f; - border-color: #ff4d4f; - text-shadow: none; -} - -.ant-btn-background-ghost.ant-btn-dangerous > a:only-child { - color: currentColor; -} - -.ant-btn-background-ghost.ant-btn-dangerous > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-background-ghost.ant-btn-dangerous:hover, -.ant-btn-background-ghost.ant-btn-dangerous:focus { - color: #ff7875; - border-color: #ff7875; -} - -.ant-btn-background-ghost.ant-btn-dangerous:hover > a:only-child, -.ant-btn-background-ghost.ant-btn-dangerous:focus > a:only-child { - color: currentColor; -} - -.ant-btn-background-ghost.ant-btn-dangerous:hover > a:only-child::after, -.ant-btn-background-ghost.ant-btn-dangerous:focus > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-background-ghost.ant-btn-dangerous:active { - color: #d9363e; - border-color: #d9363e; -} - -.ant-btn-background-ghost.ant-btn-dangerous:active > a:only-child { - color: currentColor; -} - -.ant-btn-background-ghost.ant-btn-dangerous:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-background-ghost.ant-btn-dangerous[disabled], -.ant-btn-background-ghost.ant-btn-dangerous[disabled]:hover, -.ant-btn-background-ghost.ant-btn-dangerous[disabled]:focus, -.ant-btn-background-ghost.ant-btn-dangerous[disabled]:active { - color: rgba(0, 0, 0, 0.25); - border-color: #d9d9d9; - background: #f5f5f5; - text-shadow: none; - box-shadow: none; -} - -.ant-btn-background-ghost.ant-btn-dangerous[disabled] > a:only-child, -.ant-btn-background-ghost.ant-btn-dangerous[disabled]:hover > a:only-child, -.ant-btn-background-ghost.ant-btn-dangerous[disabled]:focus > a:only-child, -.ant-btn-background-ghost.ant-btn-dangerous[disabled]:active > a:only-child { - color: currentColor; -} - -.ant-btn-background-ghost.ant-btn-dangerous[disabled] > a:only-child::after, -.ant-btn-background-ghost.ant-btn-dangerous[disabled]:hover > a:only-child::after, -.ant-btn-background-ghost.ant-btn-dangerous[disabled]:focus > a:only-child::after, -.ant-btn-background-ghost.ant-btn-dangerous[disabled]:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link { - color: #ff4d4f; - border-color: transparent; - text-shadow: none; -} - -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link > a:only-child { - color: currentColor; -} - -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:hover, -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:focus { - color: #ff7875; - border-color: transparent; -} - -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:hover > a:only-child, -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:focus > a:only-child { - color: currentColor; -} - -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:hover > a:only-child::after, -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:focus > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:active { - color: #d9363e; - border-color: transparent; -} - -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:active > a:only-child { - color: currentColor; -} - -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled], -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:hover, -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:focus, -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:active { - color: rgba(0, 0, 0, 0.25); - border-color: #d9d9d9; - background: #f5f5f5; - text-shadow: none; - box-shadow: none; -} - -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled] > a:only-child, -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:hover > a:only-child, -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:focus > a:only-child, -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:active > a:only-child { - color: currentColor; -} - -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled] > a:only-child::after, -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:hover > a:only-child::after, -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:focus > a:only-child::after, -.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:active > a:only-child::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - content: ''; -} - -.ant-btn-two-chinese-chars::first-letter { - letter-spacing: 0.34em; -} - -.ant-btn-two-chinese-chars > *:not(.anticon) { - margin-right: -0.34em; - letter-spacing: 0.34em; -} - -.ant-btn-block { - width: 100%; -} - -.ant-btn:empty { - display: inline-block; - width: 0; - visibility: hidden; - content: '\a0'; -} - -a.ant-btn { - padding-top: 0.01px !important; - line-height: 30px; -} - -a.ant-btn-lg { - line-height: 38px; -} - -a.ant-btn-sm { - line-height: 22px; -} - - -.ant-btn > .ant-btn-loading-icon:only-child .anticon { - padding-right: 0; - padding-left: 0; -} - -.ant-radio-group { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - display: inline-block; - font-size: 0; -} - -.ant-radio-group .ant-badge-count { - z-index: 1; -} - -.ant-radio-group > .ant-badge:not(:first-child) > .ant-radio-button-wrapper { - border-left: none; -} - -.ant-radio-wrapper { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - display: inline-flex; - align-items: baseline; - margin-right: 8px; - cursor: pointer; -} - -.ant-radio-wrapper-disabled { - cursor: not-allowed; -} - -.ant-radio-wrapper::after { - display: inline-block; - width: 0; - overflow: hidden; - content: '\a0'; -} - -.ant-radio { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - top: 0.2em; - display: inline-block; - outline: none; - cursor: pointer; -} - -.ant-radio-wrapper:hover .ant-radio, -.ant-radio:hover .ant-radio-inner, -.ant-radio-input:focus + .ant-radio-inner { - border-color: #1890ff; -} - -.ant-radio-input:focus + .ant-radio-inner { - box-shadow: 0 0 0 3px #e6f7ff; -} - -.ant-radio-checked::after { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border: 1px solid #1890ff; - border-radius: 50%; - visibility: hidden; - -webkit-animation: antRadioEffect 0.36s ease-in-out; - animation: antRadioEffect 0.36s ease-in-out; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - content: ''; -} - -.ant-radio:hover::after, -.ant-radio-wrapper:hover .ant-radio::after { - visibility: visible; -} - -.ant-radio-inner { - position: relative; - top: 0; - left: 0; - display: block; - width: 16px; - height: 16px; - background-color: #fff; - border-color: #d9d9d9; - border-style: solid; - border-width: 1px; - border-radius: 50%; - transition: all 0.3s; -} - -.ant-radio-inner::after { - position: absolute; - top: 50%; - left: 50%; - display: block; - width: 16px; - height: 16px; - margin-top: -8px; - margin-left: -8px; - background-color: #1890ff; - border-top: 0; - border-left: 0; - border-radius: 16px; - transform: scale(0); - opacity: 0; - transition: all 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86); - content: ' '; -} - -.ant-radio-input { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - cursor: pointer; - opacity: 0; -} - -.ant-radio-checked .ant-radio-inner { - border-color: #1890ff; -} - -.ant-radio-checked .ant-radio-inner::after { - transform: scale(0.5); - opacity: 1; - transition: all 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86); -} - -.ant-radio-disabled { - cursor: not-allowed; -} - -.ant-radio-disabled .ant-radio-inner { - background-color: #f5f5f5; - border-color: #d9d9d9 !important; - cursor: not-allowed; -} - -.ant-radio-disabled .ant-radio-inner::after { - background-color: rgba(0, 0, 0, 0.2); -} - -.ant-radio-disabled .ant-radio-input { - cursor: not-allowed; -} - -.ant-radio-disabled + span { - color: rgba(0, 0, 0, 0.25); - cursor: not-allowed; -} - -span.ant-radio + * { - padding-right: 8px; - padding-left: 8px; -} - -.ant-radio-button-wrapper { - position: relative; - display: inline-block; - height: 32px; - margin: 0; - padding: 0 15px; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - line-height: 30px; - background: #fff; - border: 1px solid #d9d9d9; - border-top-width: 1.02px; - border-left-width: 0; - cursor: pointer; - transition: color 0.3s, background 0.3s, border-color 0.3s, box-shadow 0.3s; -} - -.ant-radio-button-wrapper a { - color: rgba(0, 0, 0, 0.85); -} - -.ant-radio-button-wrapper > .ant-radio-button { - position: absolute; - top: 0; - left: 0; - z-index: -1; - width: 100%; - height: 100%; -} - -.ant-radio-group-large .ant-radio-button-wrapper { - height: 40px; - font-size: 16px; - line-height: 38px; -} - -.ant-radio-group-small .ant-radio-button-wrapper { - height: 24px; - padding: 0 7px; - line-height: 22px; -} - -.ant-radio-button-wrapper:not(:first-child)::before { - position: absolute; - top: -1px; - left: -1px; - display: block; - box-sizing: content-box; - width: 1px; - height: 100%; - padding: 1px 0; - background-color: #d9d9d9; - transition: background-color 0.3s; - content: ''; -} - -.ant-radio-button-wrapper:first-child { - border-left: 1px solid #d9d9d9; - border-radius: 2px 0 0 2px; -} - -.ant-radio-button-wrapper:last-child { - border-radius: 0 2px 2px 0; -} - -.ant-radio-button-wrapper:first-child:last-child { - border-radius: 2px; -} - -.ant-radio-button-wrapper:hover { - position: relative; - color: #1890ff; -} - -.ant-radio-button-wrapper:focus-within { - box-shadow: 0 0 0 3px #e6f7ff; -} - -.ant-radio-button-wrapper .ant-radio-inner, -.ant-radio-button-wrapper input[type='checkbox'], -.ant-radio-button-wrapper input[type='radio'] { - width: 0; - height: 0; - opacity: 0; - pointer-events: none; -} - -.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) { - z-index: 1; - color: #1890ff; - background: #fff; - border-color: #1890ff; -} - -.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)::before { - background-color: #1890ff; -} - -.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):first-child { - border-color: #1890ff; -} - -.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover { - color: #40a9ff; - border-color: #40a9ff; -} - -.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover::before { - background-color: #40a9ff; -} - -.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active { - color: #096dd9; - border-color: #096dd9; -} - -.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active::before { - background-color: #096dd9; -} - -.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within { - box-shadow: 0 0 0 3px #e6f7ff; -} - -.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) { - color: #fff; - background: #1890ff; - border-color: #1890ff; -} - -.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover { - color: #fff; - background: #40a9ff; - border-color: #40a9ff; -} - -.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active { - color: #fff; - background: #096dd9; - border-color: #096dd9; -} - -.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within { - box-shadow: 0 0 0 3px #e6f7ff; -} - -.ant-radio-button-wrapper-disabled { - color: rgba(0, 0, 0, 0.25); - background-color: #f5f5f5; - border-color: #d9d9d9; - cursor: not-allowed; -} - -.ant-radio-button-wrapper-disabled:first-child, -.ant-radio-button-wrapper-disabled:hover { - color: rgba(0, 0, 0, 0.25); - background-color: #f5f5f5; - border-color: #d9d9d9; -} - -.ant-radio-button-wrapper-disabled:first-child { - border-left-color: #d9d9d9; -} - -.ant-radio-button-wrapper-disabled.ant-radio-button-wrapper-checked { - color: rgba(0, 0, 0, 0.25); - background-color: #e6e6e6; - border-color: #d9d9d9; - box-shadow: none; -} - -@-webkit-keyframes antRadioEffect { - 0% { - transform: scale(1); - opacity: 0.5; - } - 100% { - transform: scale(1.6); - opacity: 0; - } -} - -@keyframes antRadioEffect { - 0% { - transform: scale(1); - opacity: 0.5; - } - 100% { - transform: scale(1.6); - opacity: 0; - } -} - - -.ant-picker { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - padding: 4px 11px 4px; - position: relative; - display: inline-flex; - align-items: center; - background: #fff; - border: 1px solid #d9d9d9; - border-radius: 2px; - transition: border 0.3s, box-shadow 0.3s; -} - -.ant-picker:hover, -.ant-picker-focused { - border-color: #40a9ff; - border-right-width: 1px !important; -} - -.ant-picker-focused { - border-color: #40a9ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-picker.ant-picker-disabled { - background: #f5f5f5; - border-color: #d9d9d9; - cursor: not-allowed; -} - -.ant-picker.ant-picker-disabled .ant-picker-suffix { - color: rgba(0, 0, 0, 0.25); -} - -.ant-picker.ant-picker-borderless { - background-color: transparent !important; - border-color: transparent !important; - box-shadow: none !important; -} - -.ant-picker-input { - position: relative; - display: inline-flex; - align-items: center; - width: 100%; -} - -.ant-picker-input > input { - position: relative; - display: inline-block; - width: 100%; - min-width: 0; - padding: 4px 11px; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - line-height: 1.5715; - background-color: #fff; - background-image: none; - border: 1px solid #d9d9d9; - border-radius: 2px; - transition: all 0.3s; - flex: auto; - min-width: 1px; - height: auto; - padding: 0; - background: transparent; - border: 0; -} - -.ant-picker-input > input::-moz-placeholder { - opacity: 1; -} - -.ant-picker-input > input:-ms-input-placeholder { - color: #bfbfbf; - -ms-user-select: none; - user-select: none; -} - -.ant-picker-input > input::placeholder { - color: #bfbfbf; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-picker-input > input:-moz-placeholder-shown { - text-overflow: ellipsis; -} - -.ant-picker-input > input:-ms-input-placeholder { - text-overflow: ellipsis; -} - -.ant-picker-input > input:placeholder-shown { - text-overflow: ellipsis; -} - -.ant-picker-input > input:hover { - border-color: #40a9ff; - border-right-width: 1px !important; -} - -.ant-picker-input > input:focus, -.ant-picker-input > input-focused { - border-color: #40a9ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-picker-input > input-disabled { - color: rgba(0, 0, 0, 0.25); - background-color: #f5f5f5; - border-color: #d9d9d9; - box-shadow: none; - cursor: not-allowed; - opacity: 1; -} - -.ant-picker-input > input-disabled:hover { - border-color: #d9d9d9; - border-right-width: 1px !important; -} - -.ant-picker-input > input[disabled] { - color: rgba(0, 0, 0, 0.25); - background-color: #f5f5f5; - border-color: #d9d9d9; - box-shadow: none; - cursor: not-allowed; - opacity: 1; -} - -.ant-picker-input > input[disabled]:hover { - border-color: #d9d9d9; - border-right-width: 1px !important; -} - -.ant-picker-input > input-borderless, -.ant-picker-input > input-borderless:hover, -.ant-picker-input > input-borderless:focus, -.ant-picker-input > input-borderless-focused, -.ant-picker-input > input-borderless-disabled, -.ant-picker-input > input-borderless[disabled] { - background-color: transparent; - border: none; - box-shadow: none; -} - -textarea.ant-picker-input > input { - max-width: 100%; - height: auto; - min-height: 32px; - line-height: 1.5715; - vertical-align: bottom; - transition: all 0.3s, height 0s; -} - -.ant-picker-input > input-lg { - padding: 6.5px 11px; - font-size: 16px; -} - -.ant-picker-input > input-sm { - padding: 0px 7px; -} - -.ant-picker-input > input:focus { - box-shadow: none; -} - -.ant-picker-input > input[disabled] { - background: transparent; -} - -.ant-picker-input:hover .ant-picker-clear { - opacity: 1; -} - -.ant-picker-input-placeholder > input { - color: #bfbfbf; -} - -.ant-picker-large { - padding: 6.5px 11px 6.5px; -} - -.ant-picker-large .ant-picker-input > input { - font-size: 16px; -} - -.ant-picker-small { - padding: 0px 7px 0px; -} - -.ant-picker-suffix { - align-self: center; - margin-left: 4px; - color: rgba(0, 0, 0, 0.25); - line-height: 1; - pointer-events: none; -} - -.ant-picker-suffix > * { - vertical-align: top; -} - -.ant-picker-clear { - position: absolute; - top: 50%; - right: 0; - color: rgba(0, 0, 0, 0.25); - line-height: 1; - background: #fff; - transform: translateY(-50%); - cursor: pointer; - opacity: 0; - transition: opacity 0.3s, color 0.3s; -} - -.ant-picker-clear > * { - vertical-align: top; -} - -.ant-picker-clear:hover { - color: rgba(0, 0, 0, 0.45); -} - -.ant-picker-separator { - position: relative; - display: inline-block; - width: 1em; - height: 16px; - color: rgba(0, 0, 0, 0.25); - font-size: 16px; - vertical-align: top; - cursor: default; -} - -.ant-picker-focused .ant-picker-separator { - color: rgba(0, 0, 0, 0.45); -} - -.ant-picker-disabled .ant-picker-range-separator .ant-picker-separator { - cursor: not-allowed; -} - -.ant-picker-range { - position: relative; - display: inline-flex; -} - -.ant-picker-range .ant-picker-clear { - right: 11px; -} - -.ant-picker-range:hover .ant-picker-clear { - opacity: 1; -} - -.ant-picker-range .ant-picker-active-bar { - bottom: -1px; - height: 2px; - margin-left: 11px; - background: #1890ff; - opacity: 0; - transition: all 0.3s ease-out; - pointer-events: none; -} - -.ant-picker-range.ant-picker-focused .ant-picker-active-bar { - opacity: 1; -} - -.ant-picker-range-separator { - align-items: center; - padding: 0 8px; - line-height: 1; -} - -.ant-picker-range.ant-picker-small .ant-picker-clear { - right: 7px; -} - -.ant-picker-range.ant-picker-small .ant-picker-active-bar { - margin-left: 7px; -} - -.ant-picker-dropdown { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: absolute; - z-index: 1050; -} - -.ant-picker-dropdown-hidden { - display: none; -} - -.ant-picker-dropdown-placement-bottomLeft .ant-picker-range-arrow { - top: 1.66666667px; - display: block; - transform: rotate(-45deg); -} - -.ant-picker-dropdown-placement-topLeft .ant-picker-range-arrow { - bottom: 1.66666667px; - display: block; - transform: rotate(135deg); -} - -.ant-picker-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-picker-dropdown-placement-topLeft, -.ant-picker-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-picker-dropdown-placement-topRight, -.ant-picker-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-picker-dropdown-placement-topLeft, -.ant-picker-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-picker-dropdown-placement-topRight { - -webkit-animation-name: antSlideDownIn; - animation-name: antSlideDownIn; -} - -.ant-picker-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-picker-dropdown-placement-bottomLeft, -.ant-picker-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-picker-dropdown-placement-bottomRight, -.ant-picker-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-picker-dropdown-placement-bottomLeft, -.ant-picker-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-picker-dropdown-placement-bottomRight { - -webkit-animation-name: antSlideUpIn; - animation-name: antSlideUpIn; -} - -.ant-picker-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-picker-dropdown-placement-topLeft, -.ant-picker-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-picker-dropdown-placement-topRight { - -webkit-animation-name: antSlideDownOut; - animation-name: antSlideDownOut; -} - -.ant-picker-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-picker-dropdown-placement-bottomLeft, -.ant-picker-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-picker-dropdown-placement-bottomRight { - -webkit-animation-name: antSlideUpOut; - animation-name: antSlideUpOut; -} - -.ant-picker-dropdown-range { - padding: 6.66666667px 0; -} - -.ant-picker-dropdown-range-hidden { - display: none; -} - -.ant-picker-dropdown .ant-picker-panel > .ant-picker-time-panel { - padding-top: 4px; -} - -.ant-picker-ranges { - margin-bottom: 0; - padding: 4px 12px; - overflow: hidden; - line-height: 34px; - text-align: left; - list-style: none; -} - -.ant-picker-ranges > li { - display: inline-block; -} - -.ant-picker-ranges .ant-picker-preset > .ant-tag-blue { - color: #1890ff; - background: #e6f7ff; - border-color: #91d5ff; - cursor: pointer; -} - -.ant-picker-ranges .ant-picker-ok { - float: right; - margin-left: 8px; -} - -.ant-picker-range-wrapper { - display: flex; -} - -.ant-picker-range-arrow { - position: absolute; - z-index: 1; - display: none; - width: 10px; - height: 10px; - margin-left: 16.5px; - box-shadow: 2px -2px 6px rgba(0, 0, 0, 0.06); - transition: left 0.3s ease-out; -} - -.ant-picker-range-arrow::after { - position: absolute; - top: 1px; - right: 1px; - width: 10px; - height: 10px; - border: 5px solid #f0f0f0; - border-color: #fff #fff transparent transparent; - content: ''; -} - -.ant-picker-panel-container { - overflow: hidden; - vertical-align: top; - background: #fff; - border-radius: 2px; - box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); - transition: margin 0.3s; -} - -.ant-picker-panel-container .ant-picker-panels { - display: inline-flex; - flex-wrap: nowrap; - direction: ltr; -} - -.ant-picker-panel-container .ant-picker-panel { - vertical-align: top; - background: transparent; - border-width: 0 0 1px 0; - border-radius: 0; -} - -.ant-picker-panel-container .ant-picker-panel .ant-picker-content, -.ant-picker-panel-container .ant-picker-panel table { - text-align: center; -} - -.ant-picker-panel-container .ant-picker-panel-focused { - border-color: #f0f0f0; -} - -.ant-picker-panel { - display: inline-flex; - flex-direction: column; - text-align: center; - background: #fff; - border: 1px solid #f0f0f0; - border-radius: 2px; - outline: none; -} - -.ant-picker-panel-focused { - border-color: #1890ff; -} - -.ant-picker-decade-panel, -.ant-picker-year-panel, -.ant-picker-quarter-panel, -.ant-picker-month-panel, -.ant-picker-week-panel, -.ant-picker-date-panel, -.ant-picker-time-panel { - display: flex; - flex-direction: column; - width: 280px; -} - -.ant-picker-header { - display: flex; - padding: 0 8px; - color: rgba(0, 0, 0, 0.85); - border-bottom: 1px solid #f0f0f0; -} - -.ant-picker-header > * { - flex: none; -} - -.ant-picker-header button { - padding: 0; - color: rgba(0, 0, 0, 0.25); - line-height: 40px; - background: transparent; - border: 0; - cursor: pointer; - transition: color 0.3s; -} - -.ant-picker-header > button { - min-width: 1.6em; - font-size: 14px; -} - -.ant-picker-header > button:hover { - color: rgba(0, 0, 0, 0.85); -} - -.ant-picker-header-view { - flex: auto; - font-weight: 500; - line-height: 40px; -} - -.ant-picker-header-view button { - color: inherit; - font-weight: inherit; -} - -.ant-picker-header-view button:not(:first-child) { - margin-left: 8px; -} - -.ant-picker-header-view button:hover { - color: #1890ff; -} - -.ant-picker-prev-icon, -.ant-picker-next-icon, -.ant-picker-super-prev-icon, -.ant-picker-super-next-icon { - position: relative; - display: inline-block; - width: 7px; - height: 7px; -} - -.ant-picker-prev-icon::before, -.ant-picker-next-icon::before, -.ant-picker-super-prev-icon::before, -.ant-picker-super-next-icon::before { - position: absolute; - top: 0; - left: 0; - display: inline-block; - width: 7px; - height: 7px; - border: 0 solid currentColor; - border-width: 1.5px 0 0 1.5px; - content: ''; -} - -.ant-picker-super-prev-icon::after, -.ant-picker-super-next-icon::after { - position: absolute; - top: 4px; - left: 4px; - display: inline-block; - width: 7px; - height: 7px; - border: 0 solid currentColor; - border-width: 1.5px 0 0 1.5px; - content: ''; -} - -.ant-picker-prev-icon, -.ant-picker-super-prev-icon { - transform: rotate(-45deg); -} - -.ant-picker-next-icon, -.ant-picker-super-next-icon { - transform: rotate(135deg); -} - -.ant-picker-content { - width: 100%; - table-layout: fixed; - border-collapse: collapse; -} - -.ant-picker-content th, -.ant-picker-content td { - position: relative; - min-width: 24px; - font-weight: 400; -} - -.ant-picker-content th { - height: 30px; - color: rgba(0, 0, 0, 0.85); - line-height: 30px; -} - -.ant-picker-cell { - padding: 3px 0; - color: rgba(0, 0, 0, 0.25); - cursor: pointer; -} - -.ant-picker-cell-in-view { - color: rgba(0, 0, 0, 0.85); -} - -.ant-picker-cell::before { - position: absolute; - top: 50%; - right: 0; - left: 0; - z-index: 1; - height: 24px; - transform: translateY(-50%); - transition: all 0.3s; - content: ''; -} - -.ant-picker-cell .ant-picker-cell-inner { - position: relative; - z-index: 2; - display: inline-block; - min-width: 24px; - height: 24px; - line-height: 24px; - border-radius: 2px; - transition: background 0.3s, border 0.3s; -} - -.ant-picker-cell:hover:not(.ant-picker-cell-in-view) .ant-picker-cell-inner, -.ant-picker-cell:hover:not(.ant-picker-cell-selected):not(.ant-picker-cell-range-start):not(.ant-picker-cell-range-end):not(.ant-picker-cell-range-hover-start):not(.ant-picker-cell-range-hover-end) .ant-picker-cell-inner { - background: #f5f5f5; -} - -.ant-picker-cell-in-view.ant-picker-cell-today .ant-picker-cell-inner::before { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - border: 1px solid #1890ff; - border-radius: 2px; - content: ''; -} - -.ant-picker-cell-in-view.ant-picker-cell-in-range { - position: relative; -} - -.ant-picker-cell-in-view.ant-picker-cell-in-range::before { - background: #e6f7ff; -} - -.ant-picker-cell-in-view.ant-picker-cell-selected .ant-picker-cell-inner, -.ant-picker-cell-in-view.ant-picker-cell-range-start .ant-picker-cell-inner, -.ant-picker-cell-in-view.ant-picker-cell-range-end .ant-picker-cell-inner { - color: #fff; - background: #1890ff; -} - -.ant-picker-cell-in-view.ant-picker-cell-range-start:not(.ant-picker-cell-range-start-single)::before, -.ant-picker-cell-in-view.ant-picker-cell-range-end:not(.ant-picker-cell-range-end-single)::before { - background: #e6f7ff; -} - -.ant-picker-cell-in-view.ant-picker-cell-range-start::before { - left: 50%; -} - -.ant-picker-cell-in-view.ant-picker-cell-range-end::before { - right: 50%; -} - -.ant-picker-cell-in-view.ant-picker-cell-range-hover-start:not(.ant-picker-cell-in-range):not(.ant-picker-cell-range-start):not(.ant-picker-cell-range-end)::after, -.ant-picker-cell-in-view.ant-picker-cell-range-hover-end:not(.ant-picker-cell-in-range):not(.ant-picker-cell-range-start):not(.ant-picker-cell-range-end)::after, -.ant-picker-cell-in-view.ant-picker-cell-range-hover-start.ant-picker-cell-range-start-single::after, -.ant-picker-cell-in-view.ant-picker-cell-range-hover-start.ant-picker-cell-range-start.ant-picker-cell-range-end.ant-picker-cell-range-end-near-hover::after, -.ant-picker-cell-in-view.ant-picker-cell-range-hover-end.ant-picker-cell-range-start.ant-picker-cell-range-end.ant-picker-cell-range-start-near-hover::after, -.ant-picker-cell-in-view.ant-picker-cell-range-hover-end.ant-picker-cell-range-end-single::after, -.ant-picker-cell-in-view.ant-picker-cell-range-hover:not(.ant-picker-cell-in-range)::after { - position: absolute; - top: 50%; - z-index: 0; - height: 24px; - border-top: 1px dashed #7ec1ff; - border-bottom: 1px dashed #7ec1ff; - transform: translateY(-50%); - transition: all 0.3s; - content: ''; -} - -.ant-picker-cell-range-hover-start::after, -.ant-picker-cell-range-hover-end::after, -.ant-picker-cell-range-hover::after { - right: 0; - left: 2px; -} - -.ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover::before, -.ant-picker-cell-in-view.ant-picker-cell-range-start.ant-picker-cell-range-hover::before, -.ant-picker-cell-in-view.ant-picker-cell-range-end.ant-picker-cell-range-hover::before, -.ant-picker-cell-in-view.ant-picker-cell-range-start:not(.ant-picker-cell-range-start-single).ant-picker-cell-range-hover-start::before, -.ant-picker-cell-in-view.ant-picker-cell-range-end:not(.ant-picker-cell-range-end-single).ant-picker-cell-range-hover-end::before, -.ant-picker-panel > :not(.ant-picker-date-panel) .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start::before, -.ant-picker-panel > :not(.ant-picker-date-panel) .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end::before { - background: #cbe6ff; -} - -.ant-picker-cell-in-view.ant-picker-cell-range-start:not(.ant-picker-cell-range-start-single):not(.ant-picker-cell-range-end) .ant-picker-cell-inner { - border-radius: 2px 0 0 2px; -} - -.ant-picker-cell-in-view.ant-picker-cell-range-end:not(.ant-picker-cell-range-end-single):not(.ant-picker-cell-range-start) .ant-picker-cell-inner { - border-radius: 0 2px 2px 0; -} - -.ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start .ant-picker-cell-inner::after, -.ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end .ant-picker-cell-inner::after { - position: absolute; - top: 0; - bottom: 0; - z-index: -1; - background: #cbe6ff; - transition: all 0.3s; - content: ''; -} - -.ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start .ant-picker-cell-inner::after { - right: -6px; - left: 0; -} - -.ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end .ant-picker-cell-inner::after { - right: 0; - left: -6px; -} - -.ant-picker-cell-range-hover.ant-picker-cell-range-start::after { - right: 50%; -} - -.ant-picker-cell-range-hover.ant-picker-cell-range-end::after { - left: 50%; -} - -tr > .ant-picker-cell-in-view.ant-picker-cell-range-hover:first-child::after, -tr > .ant-picker-cell-in-view.ant-picker-cell-range-hover-end:first-child::after, -.ant-picker-cell-in-view.ant-picker-cell-start.ant-picker-cell-range-hover-edge-start.ant-picker-cell-range-hover-edge-start-near-range::after, -.ant-picker-cell-in-view.ant-picker-cell-range-hover-edge-start:not(.ant-picker-cell-range-hover-edge-start-near-range)::after, -.ant-picker-cell-in-view.ant-picker-cell-range-hover-start::after { - left: 6px; - border-left: 1px dashed #7ec1ff; - border-top-left-radius: 2px; - border-bottom-left-radius: 2px; -} - -tr > .ant-picker-cell-in-view.ant-picker-cell-range-hover:last-child::after, -tr > .ant-picker-cell-in-view.ant-picker-cell-range-hover-start:last-child::after, -.ant-picker-cell-in-view.ant-picker-cell-end.ant-picker-cell-range-hover-edge-end.ant-picker-cell-range-hover-edge-end-near-range::after, -.ant-picker-cell-in-view.ant-picker-cell-range-hover-edge-end:not(.ant-picker-cell-range-hover-edge-end-near-range)::after, -.ant-picker-cell-in-view.ant-picker-cell-range-hover-end::after { - right: 6px; - border-right: 1px dashed #7ec1ff; - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; -} - -.ant-picker-cell-disabled { - color: rgba(0, 0, 0, 0.25); - pointer-events: none; -} - -.ant-picker-cell-disabled .ant-picker-cell-inner { - background: transparent; -} - -.ant-picker-cell-disabled::before { - background: rgba(0, 0, 0, 0.04); -} - -.ant-picker-cell-disabled.ant-picker-cell-today .ant-picker-cell-inner::before { - border-color: rgba(0, 0, 0, 0.25); -} - -.ant-picker-decade-panel .ant-picker-content, -.ant-picker-year-panel .ant-picker-content, -.ant-picker-quarter-panel .ant-picker-content, -.ant-picker-month-panel .ant-picker-content { - height: 264px; -} - -.ant-picker-decade-panel .ant-picker-cell-inner, -.ant-picker-year-panel .ant-picker-cell-inner, -.ant-picker-quarter-panel .ant-picker-cell-inner, -.ant-picker-month-panel .ant-picker-cell-inner { - padding: 0 8px; -} - -.ant-picker-quarter-panel .ant-picker-content { - height: 56px; -} - -.ant-picker-footer { - width: -webkit-min-content; - width: -moz-min-content; - width: min-content; - min-width: 100%; - line-height: 38px; - text-align: center; - border-bottom: 1px solid transparent; -} - -.ant-picker-panel .ant-picker-footer { - border-top: 1px solid #f0f0f0; -} - -.ant-picker-footer-extra { - padding: 0 12px; - line-height: 38px; - text-align: left; -} - -.ant-picker-footer-extra:not(:last-child) { - border-bottom: 1px solid #f0f0f0; -} - -.ant-picker-now { - text-align: left; -} - -.ant-picker-today-btn { - color: #1890ff; -} - -.ant-picker-today-btn:hover { - color: #40a9ff; -} - -.ant-picker-today-btn:active { - color: #096dd9; -} - -.ant-picker-today-btn.ant-picker-today-btn-disabled { - color: rgba(0, 0, 0, 0.25); - cursor: not-allowed; -} - -.ant-picker-decade-panel .ant-picker-cell-inner { - padding: 0 4px; -} - -.ant-picker-decade-panel .ant-picker-cell::before { - display: none; -} - -.ant-picker-year-panel .ant-picker-body, -.ant-picker-quarter-panel .ant-picker-body, -.ant-picker-month-panel .ant-picker-body { - padding: 0 8px; -} - -.ant-picker-year-panel .ant-picker-cell-inner, -.ant-picker-quarter-panel .ant-picker-cell-inner, -.ant-picker-month-panel .ant-picker-cell-inner { - width: 60px; -} - -.ant-picker-year-panel .ant-picker-cell-range-hover-start::after, -.ant-picker-quarter-panel .ant-picker-cell-range-hover-start::after, -.ant-picker-month-panel .ant-picker-cell-range-hover-start::after { - left: 14px; - border-left: 1px dashed #7ec1ff; - border-radius: 2px 0 0 2px; -} - -.ant-picker-year-panel .ant-picker-cell-range-hover-end::after, -.ant-picker-quarter-panel .ant-picker-cell-range-hover-end::after, -.ant-picker-month-panel .ant-picker-cell-range-hover-end::after { - right: 14px; - border-right: 1px dashed #7ec1ff; - border-radius: 0 2px 2px 0; -} - -.ant-picker-week-panel .ant-picker-body { - padding: 8px 12px; -} - -.ant-picker-week-panel .ant-picker-cell:hover .ant-picker-cell-inner, -.ant-picker-week-panel .ant-picker-cell-selected .ant-picker-cell-inner, -.ant-picker-week-panel .ant-picker-cell .ant-picker-cell-inner { - background: transparent !important; -} - -.ant-picker-week-panel-row td { - transition: background 0.3s; -} - -.ant-picker-week-panel-row:hover td { - background: #f5f5f5; -} - -.ant-picker-week-panel-row-selected td, -.ant-picker-week-panel-row-selected:hover td { - background: #1890ff; -} - -.ant-picker-week-panel-row-selected td.ant-picker-cell-week, -.ant-picker-week-panel-row-selected:hover td.ant-picker-cell-week { - color: rgba(255, 255, 255, 0.5); -} - -.ant-picker-week-panel-row-selected td.ant-picker-cell-today .ant-picker-cell-inner::before, -.ant-picker-week-panel-row-selected:hover td.ant-picker-cell-today .ant-picker-cell-inner::before { - border-color: #fff; -} - -.ant-picker-week-panel-row-selected td .ant-picker-cell-inner, -.ant-picker-week-panel-row-selected:hover td .ant-picker-cell-inner { - color: #fff; -} - -.ant-picker-date-panel .ant-picker-body { - padding: 8px 12px; -} - -.ant-picker-date-panel .ant-picker-content { - width: 252px; -} - -.ant-picker-date-panel .ant-picker-content th { - width: 36px; -} - -.ant-picker-datetime-panel { - display: flex; -} - -.ant-picker-datetime-panel .ant-picker-time-panel { - border-left: 1px solid #f0f0f0; -} - -.ant-picker-datetime-panel .ant-picker-date-panel, -.ant-picker-datetime-panel .ant-picker-time-panel { - transition: opacity 0.3s; -} - -.ant-picker-datetime-panel-active .ant-picker-date-panel, -.ant-picker-datetime-panel-active .ant-picker-time-panel { - opacity: 0.3; -} - -.ant-picker-datetime-panel-active .ant-picker-date-panel-active, -.ant-picker-datetime-panel-active .ant-picker-time-panel-active { - opacity: 1; -} - -.ant-picker-time-panel { - width: auto; - min-width: auto; -} - -.ant-picker-time-panel .ant-picker-content { - display: flex; - flex: auto; - height: 224px; -} - -.ant-picker-time-panel-column { - flex: 1 0 auto; - width: 56px; - margin: 0; - padding: 0; - overflow-y: hidden; - text-align: left; - list-style: none; - transition: background 0.3s; -} - -.ant-picker-time-panel-column::after { - display: block; - height: 196px; - content: ''; -} - -.ant-picker-datetime-panel .ant-picker-time-panel-column::after { - height: 198px; -} - -.ant-picker-time-panel-column:not(:first-child) { - border-left: 1px solid #f0f0f0; -} - -.ant-picker-time-panel-column-active { - background: rgba(230, 247, 255, 0.2); -} - -.ant-picker-time-panel-column:hover { - overflow-y: auto; -} - -.ant-picker-time-panel-column > li { - margin: 0; - padding: 0; -} - -.ant-picker-time-panel-column > li.ant-picker-time-panel-cell .ant-picker-time-panel-cell-inner { - display: block; - width: 100%; - height: 28px; - margin: 0; - padding: 0 0 0 14px; - color: rgba(0, 0, 0, 0.85); - line-height: 28px; - border-radius: 0; - cursor: pointer; - transition: background 0.3s; -} - -.ant-picker-time-panel-column > li.ant-picker-time-panel-cell .ant-picker-time-panel-cell-inner:hover { - background: #f5f5f5; -} - -.ant-picker-time-panel-column > li.ant-picker-time-panel-cell-selected .ant-picker-time-panel-cell-inner { - background: #e6f7ff; -} - -.ant-picker-time-panel-column > li.ant-picker-time-panel-cell-disabled .ant-picker-time-panel-cell-inner { - color: rgba(0, 0, 0, 0.25); - background: transparent; - cursor: not-allowed; -} - -_:-ms-fullscreen .ant-picker-range-wrapper .ant-picker-month-panel .ant-picker-cell, -:root .ant-picker-range-wrapper .ant-picker-month-panel .ant-picker-cell, -_:-ms-fullscreen .ant-picker-range-wrapper .ant-picker-year-panel .ant-picker-cell, -:root .ant-picker-range-wrapper .ant-picker-year-panel .ant-picker-cell { - padding: 21px 0; -} - - -.ant-picker-cell .ant-picker-cell-inner { - position: relative; - z-index: 2; - display: inline-block; - min-width: 24px; - height: 24px; - line-height: 24px; - border-radius: 2px; - transition: background 0.3s, border 0.3s; -} - -.ant-tag { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - display: inline-block; - height: auto; - margin-right: 8px; - padding: 0 7px; - font-size: 12px; - line-height: 20px; - white-space: nowrap; - background: #fafafa; - border: 1px solid #d9d9d9; - border-radius: 2px; - opacity: 1; - transition: all 0.3s; -} - -.ant-tag, -.ant-tag a, -.ant-tag a:hover { - color: rgba(0, 0, 0, 0.85); -} - -.ant-tag > a:first-child:last-child { - display: inline-block; - margin: 0 -8px; - padding: 0 8px; -} - -.ant-tag-close-icon { - margin-left: 3px; - color: rgba(0, 0, 0, 0.45); - font-size: 10px; - cursor: pointer; - transition: all 0.3s; -} - -.ant-tag-close-icon:hover { - color: rgba(0, 0, 0, 0.85); -} - -.ant-tag-has-color { - border-color: transparent; -} - -.ant-tag-has-color, -.ant-tag-has-color a, -.ant-tag-has-color a:hover, -.ant-tag-has-color .anticon-close, -.ant-tag-has-color .anticon-close:hover { - color: #fff; -} - -.ant-tag-checkable { - background-color: transparent; - border-color: transparent; - cursor: pointer; -} - -.ant-tag-checkable:not(.ant-tag-checkable-checked):hover { - color: #1890ff; -} - -.ant-tag-checkable:active, -.ant-tag-checkable-checked { - color: #fff; -} - -.ant-tag-checkable-checked { - background-color: #1890ff; -} - -.ant-tag-checkable:active { - background-color: #096dd9; -} - -.ant-tag-hidden { - display: none; -} - -.ant-tag-pink { - color: #c41d7f; - background: #fff0f6; - border-color: #ffadd2; -} - -.ant-tag-pink-inverse { - color: #fff; - background: #eb2f96; - border-color: #eb2f96; -} - -.ant-tag-magenta { - color: #c41d7f; - background: #fff0f6; - border-color: #ffadd2; -} - -.ant-tag-magenta-inverse { - color: #fff; - background: #eb2f96; - border-color: #eb2f96; -} - -.ant-tag-red { - color: #cf1322; - background: #fff1f0; - border-color: #ffa39e; -} - -.ant-tag-red-inverse { - color: #fff; - background: #f5222d; - border-color: #f5222d; -} - -.ant-tag-volcano { - color: #d4380d; - background: #fff2e8; - border-color: #ffbb96; -} - -.ant-tag-volcano-inverse { - color: #fff; - background: #fa541c; - border-color: #fa541c; -} - -.ant-tag-orange { - color: #d46b08; - background: #fff7e6; - border-color: #ffd591; -} - -.ant-tag-orange-inverse { - color: #fff; - background: #fa8c16; - border-color: #fa8c16; -} - -.ant-tag-yellow { - color: #d4b106; - background: #feffe6; - border-color: #fffb8f; -} - -.ant-tag-yellow-inverse { - color: #fff; - background: #fadb14; - border-color: #fadb14; -} - -.ant-tag-gold { - color: #d48806; - background: #fffbe6; - border-color: #ffe58f; -} - -.ant-tag-gold-inverse { - color: #fff; - background: #faad14; - border-color: #faad14; -} - -.ant-tag-cyan { - color: #08979c; - background: #e6fffb; - border-color: #87e8de; -} - -.ant-tag-cyan-inverse { - color: #fff; - background: #13c2c2; - border-color: #13c2c2; -} - -.ant-tag-lime { - color: #7cb305; - background: #fcffe6; - border-color: #eaff8f; -} - -.ant-tag-lime-inverse { - color: #fff; - background: #a0d911; - border-color: #a0d911; -} - -.ant-tag-green { - color: #389e0d; - background: #f6ffed; - border-color: #b7eb8f; -} - -.ant-tag-green-inverse { - color: #fff; - background: #52c41a; - border-color: #52c41a; -} - -.ant-tag-blue { - color: #096dd9; - background: #e6f7ff; - border-color: #91d5ff; -} - -.ant-tag-blue-inverse { - color: #fff; - background: #1890ff; - border-color: #1890ff; -} - -.ant-tag-geekblue { - color: #1d39c4; - background: #f0f5ff; - border-color: #adc6ff; -} - -.ant-tag-geekblue-inverse { - color: #fff; - background: #2f54eb; - border-color: #2f54eb; -} - -.ant-tag-purple { - color: #531dab; - background: #f9f0ff; - border-color: #d3adf7; -} - -.ant-tag-purple-inverse { - color: #fff; - background: #722ed1; - border-color: #722ed1; -} - -.ant-tag-success { - color: #52c41a; - background: #f6ffed; - border-color: #b7eb8f; -} - -.ant-tag-processing { - color: #1890ff; - background: #e6f7ff; - border-color: #91d5ff; -} - -.ant-tag-error { - color: #ff4d4f; - background: #fff2f0; - border-color: #ffccc7; -} - -.ant-tag-warning { - color: #faad14; - background: #fffbe6; - border-color: #ffe58f; -} - -.ant-tag > .anticon + span, -.ant-tag > span + .anticon { - margin-left: 7px; -} - -.ant-card { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - background: #fff; - border-radius: 2px; -} - -.ant-card-hoverable { - cursor: pointer; - transition: box-shadow 0.3s, border-color 0.3s; -} - -.ant-card-hoverable:hover { - border-color: transparent; - box-shadow: 0 1px 2px -2px rgba(0, 0, 0, 0.16), 0 3px 6px 0 rgba(0, 0, 0, 0.12), 0 5px 12px 4px rgba(0, 0, 0, 0.09); -} - -.ant-card-bordered { - border: 1px solid #f0f0f0; -} - -.ant-card-head { - min-height: 48px; - margin-bottom: -1px; - padding: 0 24px; - color: rgba(0, 0, 0, 0.85); - font-weight: 500; - font-size: 16px; - background: transparent; - border-bottom: 1px solid #f0f0f0; - border-radius: 2px 2px 0 0; -} - -.ant-card-head::before { - display: table; - content: ''; -} - -.ant-card-head::after { - display: table; - clear: both; - content: ''; -} - -.ant-card-head-wrapper { - display: flex; - align-items: center; -} - -.ant-card-head-title { - display: inline-block; - flex: 1; - padding: 16px 0; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - -.ant-card-head-title > .ant-typography, -.ant-card-head-title > .ant-typography-edit-content { - left: 0; - margin-top: 0; - margin-bottom: 0; -} - -.ant-card-head .ant-tabs-top { - clear: both; - margin-bottom: -17px; - color: rgba(0, 0, 0, 0.85); - font-weight: normal; - font-size: 14px; -} - -.ant-card-head .ant-tabs-top-bar { - border-bottom: 1px solid #f0f0f0; -} - -.ant-card-extra { - float: right; - margin-left: auto; - padding: 16px 0; - color: rgba(0, 0, 0, 0.85); - font-weight: normal; - font-size: 14px; -} - -.ant-card-body { - padding: 24px; -} - -.ant-card-body::before { - display: table; - content: ''; -} - -.ant-card-body::after { - display: table; - clear: both; - content: ''; -} - -.ant-card-contain-grid:not(.ant-card-loading) .ant-card-body { - margin: -1px 0 0 -1px; - padding: 0; -} - -.ant-card-grid { - float: left; - width: 33.33%; - padding: 24px; - border: 0; - border-radius: 0; - box-shadow: 1px 0 0 0 #f0f0f0, 0 1px 0 0 #f0f0f0, 1px 1px 0 0 #f0f0f0, 1px 0 0 0 #f0f0f0 inset, 0 1px 0 0 #f0f0f0 inset; - transition: all 0.3s; -} - -.ant-card-grid-hoverable:hover { - position: relative; - z-index: 1; - box-shadow: 0 1px 2px -2px rgba(0, 0, 0, 0.16), 0 3px 6px 0 rgba(0, 0, 0, 0.12), 0 5px 12px 4px rgba(0, 0, 0, 0.09); -} - -.ant-card-contain-tabs > .ant-card-head .ant-card-head-title { - min-height: 32px; - padding-bottom: 0; -} - -.ant-card-contain-tabs > .ant-card-head .ant-card-extra { - padding-bottom: 0; -} - -.ant-card-bordered .ant-card-cover { - margin-top: -1px; - margin-right: -1px; - margin-left: -1px; -} - -.ant-card-cover > * { - display: block; - width: 100%; -} - -.ant-card-cover img { - border-radius: 2px 2px 0 0; -} - -.ant-card-actions { - margin: 0; - padding: 0; - list-style: none; - background: #fff; - border-top: 1px solid #f0f0f0; -} - -.ant-card-actions::before { - display: table; - content: ''; -} - -.ant-card-actions::after { - display: table; - clear: both; - content: ''; -} - -.ant-card-actions > li { - float: left; - margin: 12px 0; - color: rgba(0, 0, 0, 0.45); - text-align: center; -} - -.ant-card-actions > li > span { - position: relative; - display: block; - min-width: 32px; - font-size: 14px; - line-height: 1.5715; - cursor: pointer; -} - -.ant-card-actions > li > span:hover { - color: #1890ff; - transition: color 0.3s; -} - -.ant-card-actions > li > span a:not(.ant-btn), -.ant-card-actions > li > span > .anticon { - display: inline-block; - width: 100%; - color: rgba(0, 0, 0, 0.45); - line-height: 22px; - transition: color 0.3s; -} - -.ant-card-actions > li > span a:not(.ant-btn):hover, -.ant-card-actions > li > span > .anticon:hover { - color: #1890ff; -} - -.ant-card-actions > li > span > .anticon { - font-size: 16px; - line-height: 22px; -} - -.ant-card-actions > li:not(:last-child) { - border-right: 1px solid #f0f0f0; -} - -.ant-card-type-inner .ant-card-head { - padding: 0 24px; - background: #fafafa; -} - -.ant-card-type-inner .ant-card-head-title { - padding: 12px 0; - font-size: 14px; -} - -.ant-card-type-inner .ant-card-body { - padding: 16px 24px; -} - -.ant-card-type-inner .ant-card-extra { - padding: 13.5px 0; -} - -.ant-card-meta { - margin: -4px 0; -} - -.ant-card-meta::before { - display: table; - content: ''; -} - -.ant-card-meta::after { - display: table; - clear: both; - content: ''; -} - -.ant-card-meta-avatar { - float: left; - padding-right: 16px; -} - -.ant-card-meta-detail { - overflow: hidden; -} - -.ant-card-meta-detail > div:not(:last-child) { - margin-bottom: 8px; -} - -.ant-card-meta-title { - overflow: hidden; - color: rgba(0, 0, 0, 0.85); - font-weight: 500; - font-size: 16px; - white-space: nowrap; - text-overflow: ellipsis; -} - -.ant-card-meta-description { - color: rgba(0, 0, 0, 0.45); -} - -.ant-card-loading { - overflow: hidden; -} - -.ant-card-loading .ant-card-body { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-card-loading-content p { - margin: 0; -} - -.ant-card-loading-block { - height: 14px; - margin: 4px 0; - background: linear-gradient(90deg, rgba(207, 216, 220, 0.2), rgba(207, 216, 220, 0.4), rgba(207, 216, 220, 0.2)); - background-size: 600% 600%; - border-radius: 2px; - -webkit-animation: card-loading 1.4s ease infinite; - animation: card-loading 1.4s ease infinite; -} - -@-webkit-keyframes card-loading { - 0%, - 100% { - background-position: 0 50%; - } - 50% { - background-position: 100% 50%; - } -} - -@keyframes card-loading { - 0%, - 100% { - background-position: 0 50%; - } - 50% { - background-position: 100% 50%; - } -} - -.ant-card-small > .ant-card-head { - min-height: 36px; - padding: 0 12px; - font-size: 14px; -} - -.ant-card-small > .ant-card-head > .ant-card-head-wrapper > .ant-card-head-title { - padding: 8px 0; -} - -.ant-card-small > .ant-card-head > .ant-card-head-wrapper > .ant-card-extra { - padding: 8px 0; - font-size: 14px; -} - -.ant-card-small > .ant-card-body { - padding: 12px; -} - -.ant-tabs-small > .ant-tabs-nav .ant-tabs-tab { - padding: 8px 0; - font-size: 14px; -} - -.ant-tabs-large > .ant-tabs-nav .ant-tabs-tab { - padding: 16px 0; - font-size: 16px; -} - -.ant-tabs-card.ant-tabs-small > .ant-tabs-nav .ant-tabs-tab { - padding: 6px 16px; -} - -.ant-tabs-card.ant-tabs-large > .ant-tabs-nav .ant-tabs-tab { - padding: 7px 16px 6px; -} - -.ant-tabs-top, -.ant-tabs-bottom { - flex-direction: column; -} - -.ant-tabs-top > .ant-tabs-nav, -.ant-tabs-bottom > .ant-tabs-nav, -.ant-tabs-top > div > .ant-tabs-nav, -.ant-tabs-bottom > div > .ant-tabs-nav { - margin: 0 0 16px 0; -} - -.ant-tabs-top > .ant-tabs-nav::before, -.ant-tabs-bottom > .ant-tabs-nav::before, -.ant-tabs-top > div > .ant-tabs-nav::before, -.ant-tabs-bottom > div > .ant-tabs-nav::before { - position: absolute; - right: 0; - left: 0; - border-bottom: 1px solid #f0f0f0; - content: ''; -} - -.ant-tabs-top > .ant-tabs-nav .ant-tabs-ink-bar, -.ant-tabs-bottom > .ant-tabs-nav .ant-tabs-ink-bar, -.ant-tabs-top > div > .ant-tabs-nav .ant-tabs-ink-bar, -.ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-ink-bar { - height: 2px; -} - -.ant-tabs-top > .ant-tabs-nav .ant-tabs-ink-bar-animated, -.ant-tabs-bottom > .ant-tabs-nav .ant-tabs-ink-bar-animated, -.ant-tabs-top > div > .ant-tabs-nav .ant-tabs-ink-bar-animated, -.ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-ink-bar-animated { - transition: width 0.3s, left 0.3s, right 0.3s; -} - -.ant-tabs-top > .ant-tabs-nav .ant-tabs-nav-wrap::before, -.ant-tabs-bottom > .ant-tabs-nav .ant-tabs-nav-wrap::before, -.ant-tabs-top > div > .ant-tabs-nav .ant-tabs-nav-wrap::before, -.ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-nav-wrap::before, -.ant-tabs-top > .ant-tabs-nav .ant-tabs-nav-wrap::after, -.ant-tabs-bottom > .ant-tabs-nav .ant-tabs-nav-wrap::after, -.ant-tabs-top > div > .ant-tabs-nav .ant-tabs-nav-wrap::after, -.ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-nav-wrap::after { - top: 0; - bottom: 0; - width: 30px; -} - -.ant-tabs-top > .ant-tabs-nav .ant-tabs-nav-wrap::before, -.ant-tabs-bottom > .ant-tabs-nav .ant-tabs-nav-wrap::before, -.ant-tabs-top > div > .ant-tabs-nav .ant-tabs-nav-wrap::before, -.ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-nav-wrap::before { - left: 0; - box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, 0.08); -} - -.ant-tabs-top > .ant-tabs-nav .ant-tabs-nav-wrap::after, -.ant-tabs-bottom > .ant-tabs-nav .ant-tabs-nav-wrap::after, -.ant-tabs-top > div > .ant-tabs-nav .ant-tabs-nav-wrap::after, -.ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-nav-wrap::after { - right: 0; - box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, 0.08); -} - -.ant-tabs-top > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-left::before, -.ant-tabs-bottom > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-left::before, -.ant-tabs-top > div > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-left::before, -.ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-left::before { - opacity: 1; -} - -.ant-tabs-top > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-right::after, -.ant-tabs-bottom > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-right::after, -.ant-tabs-top > div > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-right::after, -.ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-right::after { - opacity: 1; -} - -.ant-tabs-top > .ant-tabs-nav::before, -.ant-tabs-top > div > .ant-tabs-nav::before { - bottom: 0; -} - -.ant-tabs-top > .ant-tabs-nav .ant-tabs-ink-bar, -.ant-tabs-top > div > .ant-tabs-nav .ant-tabs-ink-bar { - bottom: 0; -} - -.ant-tabs-bottom > .ant-tabs-nav, -.ant-tabs-bottom > div > .ant-tabs-nav { - order: 1; - margin-top: 16px; - margin-bottom: 0; -} - -.ant-tabs-bottom > .ant-tabs-nav::before, -.ant-tabs-bottom > div > .ant-tabs-nav::before { - top: 0; -} - -.ant-tabs-bottom > .ant-tabs-nav .ant-tabs-ink-bar, -.ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-ink-bar { - top: 0; -} - -.ant-tabs-bottom > .ant-tabs-content-holder, -.ant-tabs-bottom > div > .ant-tabs-content-holder { - order: 0; -} - -.ant-tabs-left > .ant-tabs-nav, -.ant-tabs-right > .ant-tabs-nav, -.ant-tabs-left > div > .ant-tabs-nav, -.ant-tabs-right > div > .ant-tabs-nav { - flex-direction: column; - min-width: 50px; -} - -.ant-tabs-left > .ant-tabs-nav .ant-tabs-tab, -.ant-tabs-right > .ant-tabs-nav .ant-tabs-tab, -.ant-tabs-left > div > .ant-tabs-nav .ant-tabs-tab, -.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-tab { - padding: 8px 24px; - text-align: center; -} - -.ant-tabs-left > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, -.ant-tabs-right > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, -.ant-tabs-left > div > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, -.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab { - margin: 16px 0 0 0; -} - -.ant-tabs-left > .ant-tabs-nav .ant-tabs-nav-wrap, -.ant-tabs-right > .ant-tabs-nav .ant-tabs-nav-wrap, -.ant-tabs-left > div > .ant-tabs-nav .ant-tabs-nav-wrap, -.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-nav-wrap { - flex-direction: column; -} - -.ant-tabs-left > .ant-tabs-nav .ant-tabs-nav-wrap::before, -.ant-tabs-right > .ant-tabs-nav .ant-tabs-nav-wrap::before, -.ant-tabs-left > div > .ant-tabs-nav .ant-tabs-nav-wrap::before, -.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-nav-wrap::before, -.ant-tabs-left > .ant-tabs-nav .ant-tabs-nav-wrap::after, -.ant-tabs-right > .ant-tabs-nav .ant-tabs-nav-wrap::after, -.ant-tabs-left > div > .ant-tabs-nav .ant-tabs-nav-wrap::after, -.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-nav-wrap::after { - right: 0; - left: 0; - height: 30px; -} - -.ant-tabs-left > .ant-tabs-nav .ant-tabs-nav-wrap::before, -.ant-tabs-right > .ant-tabs-nav .ant-tabs-nav-wrap::before, -.ant-tabs-left > div > .ant-tabs-nav .ant-tabs-nav-wrap::before, -.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-nav-wrap::before { - top: 0; - box-shadow: inset 0 10px 8px -8px rgba(0, 0, 0, 0.08); -} - -.ant-tabs-left > .ant-tabs-nav .ant-tabs-nav-wrap::after, -.ant-tabs-right > .ant-tabs-nav .ant-tabs-nav-wrap::after, -.ant-tabs-left > div > .ant-tabs-nav .ant-tabs-nav-wrap::after, -.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-nav-wrap::after { - bottom: 0; - box-shadow: inset 0 -10px 8px -8px rgba(0, 0, 0, 0.08); -} - -.ant-tabs-left > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-top::before, -.ant-tabs-right > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-top::before, -.ant-tabs-left > div > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-top::before, -.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-top::before { - opacity: 1; -} - -.ant-tabs-left > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-bottom::after, -.ant-tabs-right > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-bottom::after, -.ant-tabs-left > div > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-bottom::after, -.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-bottom::after { - opacity: 1; -} - -.ant-tabs-left > .ant-tabs-nav .ant-tabs-ink-bar, -.ant-tabs-right > .ant-tabs-nav .ant-tabs-ink-bar, -.ant-tabs-left > div > .ant-tabs-nav .ant-tabs-ink-bar, -.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-ink-bar { - width: 2px; -} - -.ant-tabs-left > .ant-tabs-nav .ant-tabs-ink-bar-animated, -.ant-tabs-right > .ant-tabs-nav .ant-tabs-ink-bar-animated, -.ant-tabs-left > div > .ant-tabs-nav .ant-tabs-ink-bar-animated, -.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-ink-bar-animated { - transition: height 0.3s, top 0.3s; -} - -.ant-tabs-left > .ant-tabs-nav .ant-tabs-nav-list, -.ant-tabs-right > .ant-tabs-nav .ant-tabs-nav-list, -.ant-tabs-left > div > .ant-tabs-nav .ant-tabs-nav-list, -.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-nav-list, -.ant-tabs-left > .ant-tabs-nav .ant-tabs-nav-operations, -.ant-tabs-right > .ant-tabs-nav .ant-tabs-nav-operations, -.ant-tabs-left > div > .ant-tabs-nav .ant-tabs-nav-operations, -.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-nav-operations { - flex: 1 0 auto; - flex-direction: column; -} - -.ant-tabs-left > .ant-tabs-nav .ant-tabs-ink-bar, -.ant-tabs-left > div > .ant-tabs-nav .ant-tabs-ink-bar { - right: 0; -} - -.ant-tabs-left > .ant-tabs-content-holder, -.ant-tabs-left > div > .ant-tabs-content-holder { - margin-left: -1px; - border-left: 1px solid #f0f0f0; -} - -.ant-tabs-left > .ant-tabs-content-holder > .ant-tabs-content > .ant-tabs-tabpane, -.ant-tabs-left > div > .ant-tabs-content-holder > .ant-tabs-content > .ant-tabs-tabpane { - padding-left: 24px; -} - -.ant-tabs-right > .ant-tabs-nav, -.ant-tabs-right > div > .ant-tabs-nav { - order: 1; -} - -.ant-tabs-right > .ant-tabs-nav .ant-tabs-ink-bar, -.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-ink-bar { - left: 0; -} - -.ant-tabs-right > .ant-tabs-content-holder, -.ant-tabs-right > div > .ant-tabs-content-holder { - order: 0; - margin-right: -1px; - border-right: 1px solid #f0f0f0; -} - -.ant-tabs-right > .ant-tabs-content-holder > .ant-tabs-content > .ant-tabs-tabpane, -.ant-tabs-right > div > .ant-tabs-content-holder > .ant-tabs-content > .ant-tabs-tabpane { - padding-right: 24px; -} - -.ant-tabs-dropdown { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: absolute; - top: -9999px; - left: -9999px; - z-index: 1050; - display: block; -} - -.ant-tabs-dropdown-hidden { - display: none; -} - -.ant-tabs-dropdown-menu { - max-height: 200px; - margin: 0; - padding: 4px 0; - overflow-x: hidden; - overflow-y: auto; - text-align: left; - list-style-type: none; - background-color: #fff; - background-clip: padding-box; - border-radius: 2px; - outline: none; - box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); -} - -.ant-tabs-dropdown-menu-item { - display: flex; - align-items: center; - min-width: 120px; - margin: 0; - padding: 5px 12px; - overflow: hidden; - color: rgba(0, 0, 0, 0.85); - font-weight: normal; - font-size: 14px; - line-height: 22px; - white-space: nowrap; - text-overflow: ellipsis; - cursor: pointer; - transition: all 0.3s; -} - -.ant-tabs-dropdown-menu-item > span { - flex: 1; - white-space: nowrap; -} - -.ant-tabs-dropdown-menu-item-remove { - flex: none; - margin-left: 12px; - color: rgba(0, 0, 0, 0.45); - font-size: 12px; - background: transparent; - border: 0; - cursor: pointer; -} - -.ant-tabs-dropdown-menu-item-remove:hover { - color: #40a9ff; -} - -.ant-tabs-dropdown-menu-item:hover { - background: #f5f5f5; -} - -.ant-tabs-dropdown-menu-item-disabled, -.ant-tabs-dropdown-menu-item-disabled:hover { - color: rgba(0, 0, 0, 0.25); - background: transparent; - cursor: not-allowed; -} - -.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab, -.ant-tabs-card > div > .ant-tabs-nav .ant-tabs-tab { - margin: 0; - padding: 8px 16px; - background: #fafafa; - border: 1px solid #f0f0f0; - transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); -} - -.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab-active, -.ant-tabs-card > div > .ant-tabs-nav .ant-tabs-tab-active { - color: #1890ff; - background: #fff; -} - -.ant-tabs-card > .ant-tabs-nav .ant-tabs-ink-bar, -.ant-tabs-card > div > .ant-tabs-nav .ant-tabs-ink-bar { - visibility: hidden; -} - -.ant-tabs-card.ant-tabs-top > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, -.ant-tabs-card.ant-tabs-bottom > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, -.ant-tabs-card.ant-tabs-top > div > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, -.ant-tabs-card.ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab { - margin-left: 2px; -} - -.ant-tabs-card.ant-tabs-top > .ant-tabs-nav .ant-tabs-tab, -.ant-tabs-card.ant-tabs-top > div > .ant-tabs-nav .ant-tabs-tab { - border-radius: 2px 2px 0 0; -} - -.ant-tabs-card.ant-tabs-top > .ant-tabs-nav .ant-tabs-tab-active, -.ant-tabs-card.ant-tabs-top > div > .ant-tabs-nav .ant-tabs-tab-active { - border-bottom-color: #fff; -} - -.ant-tabs-card.ant-tabs-bottom > .ant-tabs-nav .ant-tabs-tab, -.ant-tabs-card.ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-tab { - border-radius: 0 0 2px 2px; -} - -.ant-tabs-card.ant-tabs-bottom > .ant-tabs-nav .ant-tabs-tab-active, -.ant-tabs-card.ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-tab-active { - border-top-color: #fff; -} - -.ant-tabs-card.ant-tabs-left > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, -.ant-tabs-card.ant-tabs-right > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, -.ant-tabs-card.ant-tabs-left > div > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, -.ant-tabs-card.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab { - margin-top: 2px; -} - -.ant-tabs-card.ant-tabs-left > .ant-tabs-nav .ant-tabs-tab, -.ant-tabs-card.ant-tabs-left > div > .ant-tabs-nav .ant-tabs-tab { - border-radius: 2px 0 0 2px; -} - -.ant-tabs-card.ant-tabs-left > .ant-tabs-nav .ant-tabs-tab-active, -.ant-tabs-card.ant-tabs-left > div > .ant-tabs-nav .ant-tabs-tab-active { - border-right-color: #fff; -} - -.ant-tabs-card.ant-tabs-right > .ant-tabs-nav .ant-tabs-tab, -.ant-tabs-card.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-tab { - border-radius: 0 2px 2px 0; -} - -.ant-tabs-card.ant-tabs-right > .ant-tabs-nav .ant-tabs-tab-active, -.ant-tabs-card.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-tab-active { - border-left-color: #fff; -} - -.ant-tabs { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - display: flex; - overflow: hidden; -} - -.ant-tabs > .ant-tabs-nav, -.ant-tabs > div > .ant-tabs-nav { - position: relative; - display: flex; - flex: none; - align-items: center; -} - -.ant-tabs > .ant-tabs-nav .ant-tabs-nav-wrap, -.ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-wrap { - position: relative; - display: inline-block; - display: flex; - flex: auto; - align-self: stretch; - overflow: hidden; - white-space: nowrap; - transform: translate(0); -} - -.ant-tabs > .ant-tabs-nav .ant-tabs-nav-wrap::before, -.ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-wrap::before, -.ant-tabs > .ant-tabs-nav .ant-tabs-nav-wrap::after, -.ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-wrap::after { - position: absolute; - z-index: 1; - opacity: 0; - transition: opacity 0.3s; - content: ''; - pointer-events: none; -} - -.ant-tabs > .ant-tabs-nav .ant-tabs-nav-list, -.ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-list { - position: relative; - display: flex; - transition: transform 0.3s; -} - -.ant-tabs > .ant-tabs-nav .ant-tabs-nav-operations, -.ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-operations { - display: flex; - align-self: stretch; -} - -.ant-tabs > .ant-tabs-nav .ant-tabs-nav-operations-hidden, -.ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-operations-hidden { - position: absolute; - visibility: hidden; - pointer-events: none; -} - -.ant-tabs > .ant-tabs-nav .ant-tabs-nav-more, -.ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-more { - position: relative; - padding: 8px 16px; - background: transparent; - border: 0; -} - -.ant-tabs > .ant-tabs-nav .ant-tabs-nav-more::after, -.ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-more::after { - position: absolute; - right: 0; - bottom: 0; - left: 0; - height: 5px; - transform: translateY(100%); - content: ''; -} - -.ant-tabs > .ant-tabs-nav .ant-tabs-nav-add, -.ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-add { - min-width: 40px; - margin-left: 2px; - padding: 0 8px; - background: #fafafa; - border: 1px solid #f0f0f0; - border-radius: 2px 2px 0 0; - outline: none; - cursor: pointer; - transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); -} - -.ant-tabs > .ant-tabs-nav .ant-tabs-nav-add:hover, -.ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-add:hover { - color: #40a9ff; -} - -.ant-tabs > .ant-tabs-nav .ant-tabs-nav-add:active, -.ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-add:active, -.ant-tabs > .ant-tabs-nav .ant-tabs-nav-add:focus, -.ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-add:focus { - color: #096dd9; -} - -.ant-tabs-extra-content { - flex: none; -} - -.ant-tabs-centered > .ant-tabs-nav .ant-tabs-nav-wrap:not([class*='ant-tabs-nav-wrap-ping']), -.ant-tabs-centered > div > .ant-tabs-nav .ant-tabs-nav-wrap:not([class*='ant-tabs-nav-wrap-ping']) { - justify-content: center; -} - -.ant-tabs-ink-bar { - position: absolute; - background: #1890ff; - pointer-events: none; -} - -.ant-tabs-tab { - position: relative; - display: inline-flex; - align-items: center; - padding: 12px 0; - font-size: 14px; - background: transparent; - border: 0; - outline: none; - cursor: pointer; -} - -.ant-tabs-tab-btn:focus, -.ant-tabs-tab-remove:focus, -.ant-tabs-tab-btn:active, -.ant-tabs-tab-remove:active { - color: #096dd9; -} - -.ant-tabs-tab-btn { - outline: none; - transition: all 0.3s; -} - -.ant-tabs-tab-remove { - flex: none; - margin-right: -4px; - margin-left: 8px; - color: rgba(0, 0, 0, 0.45); - font-size: 12px; - background: transparent; - border: none; - outline: none; - cursor: pointer; - transition: all 0.3s; -} - -.ant-tabs-tab-remove:hover { - color: rgba(0, 0, 0, 0.85); -} - -.ant-tabs-tab:hover { - color: #40a9ff; -} - -.ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn { - color: #1890ff; - text-shadow: 0 0 0.25px currentColor; -} - -.ant-tabs-tab.ant-tabs-tab-disabled { - color: rgba(0, 0, 0, 0.25); - cursor: not-allowed; -} - -.ant-tabs-tab.ant-tabs-tab-disabled .ant-tabs-tab-btn:focus, -.ant-tabs-tab.ant-tabs-tab-disabled .ant-tabs-tab-remove:focus, -.ant-tabs-tab.ant-tabs-tab-disabled .ant-tabs-tab-btn:active, -.ant-tabs-tab.ant-tabs-tab-disabled .ant-tabs-tab-remove:active { - color: rgba(0, 0, 0, 0.25); -} - -.ant-tabs-tab .ant-tabs-tab-remove .anticon { - margin: 0; -} - -.ant-tabs-tab .anticon { - margin-right: 12px; -} - -.ant-tabs-tab + .ant-tabs-tab { - margin: 0 0 0 32px; -} - -.ant-tabs-content { - display: flex; - width: 100%; -} - -.ant-tabs-content-holder { - flex: auto; - min-width: 0; - min-height: 0; -} - -.ant-tabs-content-animated { - transition: margin 0.3s; -} - -.ant-tabs-tabpane { - flex: none; - width: 100%; - outline: none; -} - - -@-webkit-keyframes antCheckboxEffect { - 0% { - transform: scale(1); - opacity: 0.5; - } - 100% { - transform: scale(1.6); - opacity: 0; - } -} - -@keyframes antCheckboxEffect { - 0% { - transform: scale(1); - opacity: 0.5; - } - 100% { - transform: scale(1.6); - opacity: 0; - } -} - -.ant-cascader-checkbox { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - top: 0.2em; - line-height: 1; - white-space: nowrap; - outline: none; - cursor: pointer; -} - -.ant-cascader-checkbox-wrapper:hover .ant-cascader-checkbox-inner, -.ant-cascader-checkbox:hover .ant-cascader-checkbox-inner, -.ant-cascader-checkbox-input:focus + .ant-cascader-checkbox-inner { - border-color: #1890ff; -} - -.ant-cascader-checkbox-checked::after { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border: 1px solid #1890ff; - border-radius: 2px; - visibility: hidden; - -webkit-animation: antCheckboxEffect 0.36s ease-in-out; - animation: antCheckboxEffect 0.36s ease-in-out; - -webkit-animation-fill-mode: backwards; - animation-fill-mode: backwards; - content: ''; -} - -.ant-cascader-checkbox:hover::after, -.ant-cascader-checkbox-wrapper:hover .ant-cascader-checkbox::after { - visibility: visible; -} - -.ant-cascader-checkbox-inner { - position: relative; - top: 0; - left: 0; - display: block; - width: 16px; - height: 16px; - direction: ltr; - background-color: #fff; - border: 1px solid #d9d9d9; - border-radius: 2px; - border-collapse: separate; - transition: all 0.3s; -} - -.ant-cascader-checkbox-inner::after { - position: absolute; - top: 50%; - left: 21.5%; - display: table; - width: 5.71428571px; - height: 9.14285714px; - border: 2px solid #fff; - border-top: 0; - border-left: 0; - transform: rotate(45deg) scale(0) translate(-50%, -50%); - opacity: 0; - transition: all 0.1s cubic-bezier(0.71, -0.46, 0.88, 0.6), opacity 0.1s; - content: ' '; -} - -.ant-cascader-checkbox-input { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - width: 100%; - height: 100%; - cursor: pointer; - opacity: 0; -} - -.ant-cascader-checkbox-checked .ant-cascader-checkbox-inner::after { - position: absolute; - display: table; - border: 2px solid #fff; - border-top: 0; - border-left: 0; - transform: rotate(45deg) scale(1) translate(-50%, -50%); - opacity: 1; - transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s; - content: ' '; -} - -.ant-cascader-checkbox-checked .ant-cascader-checkbox-inner { - background-color: #1890ff; - border-color: #1890ff; -} - -.ant-cascader-checkbox-disabled { - cursor: not-allowed; -} - -.ant-cascader-checkbox-disabled.ant-cascader-checkbox-checked .ant-cascader-checkbox-inner::after { - border-color: rgba(0, 0, 0, 0.25); - -webkit-animation-name: none; - animation-name: none; -} - -.ant-cascader-checkbox-disabled .ant-cascader-checkbox-input { - cursor: not-allowed; -} - -.ant-cascader-checkbox-disabled .ant-cascader-checkbox-inner { - background-color: #f5f5f5; - border-color: #d9d9d9 !important; -} - -.ant-cascader-checkbox-disabled .ant-cascader-checkbox-inner::after { - border-color: #f5f5f5; - border-collapse: separate; - -webkit-animation-name: none; - animation-name: none; -} - -.ant-cascader-checkbox-disabled + span { - color: rgba(0, 0, 0, 0.25); - cursor: not-allowed; -} - -.ant-cascader-checkbox-disabled:hover::after, -.ant-cascader-checkbox-wrapper:hover .ant-cascader-checkbox-disabled::after { - visibility: hidden; -} - -.ant-cascader-checkbox-wrapper { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - display: inline-flex; - align-items: baseline; - line-height: unset; - cursor: pointer; -} - -.ant-cascader-checkbox-wrapper::after { - display: inline-block; - width: 0; - overflow: hidden; - content: '\a0'; -} - -.ant-cascader-checkbox-wrapper.ant-cascader-checkbox-wrapper-disabled { - cursor: not-allowed; -} - -.ant-cascader-checkbox-wrapper + .ant-cascader-checkbox-wrapper { - margin-left: 8px; -} - -.ant-cascader-checkbox + span { - padding-right: 8px; - padding-left: 8px; -} - -.ant-cascader-checkbox-group { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - display: inline-block; -} - -.ant-cascader-checkbox-group-item { - margin-right: 8px; -} - -.ant-cascader-checkbox-group-item:last-child { - margin-right: 0; -} - -.ant-cascader-checkbox-group-item + .ant-cascader-checkbox-group-item { - margin-left: 0; -} - -.ant-cascader-checkbox-indeterminate .ant-cascader-checkbox-inner { - background-color: #fff; - border-color: #d9d9d9; -} - -.ant-cascader-checkbox-indeterminate .ant-cascader-checkbox-inner::after { - top: 50%; - left: 50%; - width: 8px; - height: 8px; - background-color: #1890ff; - border: 0; - transform: translate(-50%, -50%) scale(1); - opacity: 1; - content: ' '; -} - -.ant-cascader-checkbox-indeterminate.ant-cascader-checkbox-disabled .ant-cascader-checkbox-inner::after { - background-color: rgba(0, 0, 0, 0.25); - border-color: rgba(0, 0, 0, 0.25); -} - -.ant-cascader { - width: 184px; -} - -.ant-cascader-checkbox { - top: 0; - margin-right: 8px; -} - -.ant-cascader-menus { - display: flex; - flex-wrap: nowrap; - align-items: flex-start; -} - -.ant-cascader-menus.ant-cascader-menu-empty .ant-cascader-menu { - width: 100%; - height: auto; -} - -.ant-cascader-menu { - min-width: 111px; - height: 180px; - margin: 0; - margin: -4px 0; - padding: 4px 0; - overflow: auto; - vertical-align: top; - list-style: none; - border-right: 1px solid #f0f0f0; - -ms-overflow-style: -ms-autohiding-scrollbar; -} - -.ant-cascader-menu-item { - display: flex; - flex-wrap: nowrap; - align-items: center; - padding: 5px 12px; - overflow: hidden; - line-height: 22px; - white-space: nowrap; - text-overflow: ellipsis; - cursor: pointer; - transition: all 0.3s; -} - -.ant-cascader-menu-item:hover { - background: #f5f5f5; -} - -.ant-cascader-menu-item-disabled { - color: rgba(0, 0, 0, 0.25); - cursor: not-allowed; -} - -.ant-cascader-menu-item-disabled:hover { - background: transparent; -} - -.ant-cascader-menu-empty .ant-cascader-menu-item { - color: rgba(0, 0, 0, 0.25); - cursor: default; - pointer-events: none; -} - -.ant-cascader-menu-item-active:not(.ant-cascader-menu-item-disabled), -.ant-cascader-menu-item-active:not(.ant-cascader-menu-item-disabled):hover { - font-weight: 600; - background-color: #e6f7ff; -} - -.ant-cascader-menu-item-content { - flex: auto; -} - -.ant-cascader-menu-item-expand .ant-cascader-menu-item-expand-icon, -.ant-cascader-menu-item-loading-icon { - margin-left: 4px; - color: rgba(0, 0, 0, 0.45); - font-size: 10px; -} - -.ant-cascader-menu-item-disabled.ant-cascader-menu-item-expand .ant-cascader-menu-item-expand-icon, -.ant-cascader-menu-item-disabled.ant-cascader-menu-item-loading-icon { - color: rgba(0, 0, 0, 0.25); -} - -.ant-cascader-menu-item-keyword { - color: #ff4d4f; -} - -@-webkit-keyframes antCheckboxEffect { - 0% { - transform: scale(1); - opacity: 0.5; - } - 100% { - transform: scale(1.6); - opacity: 0; - } -} - -@keyframes antCheckboxEffect { - 0% { - transform: scale(1); - opacity: 0.5; - } - 100% { - transform: scale(1.6); - opacity: 0; - } -} - -.ant-checkbox { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - top: 0.2em; - line-height: 1; - white-space: nowrap; - outline: none; - cursor: pointer; -} - -.ant-checkbox-wrapper:hover .ant-checkbox-inner, -.ant-checkbox:hover .ant-checkbox-inner, -.ant-checkbox-input:focus + .ant-checkbox-inner { - border-color: #1890ff; -} - -.ant-checkbox-checked::after { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border: 1px solid #1890ff; - border-radius: 2px; - visibility: hidden; - -webkit-animation: antCheckboxEffect 0.36s ease-in-out; - animation: antCheckboxEffect 0.36s ease-in-out; - -webkit-animation-fill-mode: backwards; - animation-fill-mode: backwards; - content: ''; -} - -.ant-checkbox:hover::after, -.ant-checkbox-wrapper:hover .ant-checkbox::after { - visibility: visible; -} - -.ant-checkbox-inner { - position: relative; - top: 0; - left: 0; - display: block; - width: 16px; - height: 16px; - direction: ltr; - background-color: #fff; - border: 1px solid #d9d9d9; - border-radius: 2px; - border-collapse: separate; - transition: all 0.3s; -} - -.ant-checkbox-inner::after { - position: absolute; - top: 50%; - left: 21.5%; - display: table; - width: 5.71428571px; - height: 9.14285714px; - border: 2px solid #fff; - border-top: 0; - border-left: 0; - transform: rotate(45deg) scale(0) translate(-50%, -50%); - opacity: 0; - transition: all 0.1s cubic-bezier(0.71, -0.46, 0.88, 0.6), opacity 0.1s; - content: ' '; -} - -.ant-checkbox-input { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - width: 100%; - height: 100%; - cursor: pointer; - opacity: 0; -} - -.ant-checkbox-checked .ant-checkbox-inner::after { - position: absolute; - display: table; - border: 2px solid #fff; - border-top: 0; - border-left: 0; - transform: rotate(45deg) scale(1) translate(-50%, -50%); - opacity: 1; - transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s; - content: ' '; -} - -.ant-checkbox-checked .ant-checkbox-inner { - background-color: #1890ff; - border-color: #1890ff; -} - -.ant-checkbox-disabled { - cursor: not-allowed; -} - -.ant-checkbox-disabled.ant-checkbox-checked .ant-checkbox-inner::after { - border-color: rgba(0, 0, 0, 0.25); - -webkit-animation-name: none; - animation-name: none; -} - -.ant-checkbox-disabled .ant-checkbox-input { - cursor: not-allowed; -} - -.ant-checkbox-disabled .ant-checkbox-inner { - background-color: #f5f5f5; - border-color: #d9d9d9 !important; -} - -.ant-checkbox-disabled .ant-checkbox-inner::after { - border-color: #f5f5f5; - border-collapse: separate; - -webkit-animation-name: none; - animation-name: none; -} - -.ant-checkbox-disabled + span { - color: rgba(0, 0, 0, 0.25); - cursor: not-allowed; -} - -.ant-checkbox-disabled:hover::after, -.ant-checkbox-wrapper:hover .ant-checkbox-disabled::after { - visibility: hidden; -} - -.ant-checkbox-wrapper { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - display: inline-flex; - align-items: baseline; - line-height: unset; - cursor: pointer; -} - -.ant-checkbox-wrapper::after { - display: inline-block; - width: 0; - overflow: hidden; - content: '\a0'; -} - -.ant-checkbox-wrapper.ant-checkbox-wrapper-disabled { - cursor: not-allowed; -} - -.ant-checkbox-wrapper + .ant-checkbox-wrapper { - margin-left: 8px; -} - -.ant-checkbox + span { - padding-right: 8px; - padding-left: 8px; -} - -.ant-checkbox-group { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - display: inline-block; -} - -.ant-checkbox-group-item { - margin-right: 8px; -} - -.ant-checkbox-group-item:last-child { - margin-right: 0; -} - -.ant-checkbox-group-item + .ant-checkbox-group-item { - margin-left: 0; -} - -.ant-checkbox-indeterminate .ant-checkbox-inner { - background-color: #fff; - border-color: #d9d9d9; -} - -.ant-checkbox-indeterminate .ant-checkbox-inner::after { - top: 50%; - left: 50%; - width: 8px; - height: 8px; - background-color: #1890ff; - border: 0; - transform: translate(-50%, -50%) scale(1); - opacity: 1; - content: ' '; -} - -.ant-checkbox-indeterminate.ant-checkbox-disabled .ant-checkbox-inner::after { - background-color: rgba(0, 0, 0, 0.25); - border-color: rgba(0, 0, 0, 0.25); -} - -.ant-collapse { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - background-color: #fafafa; - border: 1px solid #d9d9d9; - border-bottom: 0; - border-radius: 2px; -} - -.ant-collapse > .ant-collapse-item { - border-bottom: 1px solid #d9d9d9; -} - -.ant-collapse > .ant-collapse-item:last-child, -.ant-collapse > .ant-collapse-item:last-child > .ant-collapse-header { - border-radius: 0 0 2px 2px; -} - -.ant-collapse > .ant-collapse-item > .ant-collapse-header { - position: relative; - display: flex; - flex-wrap: nowrap; - align-items: flex-start; - padding: 12px 16px; - color: rgba(0, 0, 0, 0.85); - line-height: 1.5715; - cursor: pointer; - transition: all 0.3s, visibility 0s; -} - -.ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow { - display: inline-block; - margin-right: 12px; - font-size: 12px; - vertical-align: -1px; -} - -.ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow svg { - transition: transform 0.24s; -} - -.ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-extra { - margin-left: auto; -} - -.ant-collapse > .ant-collapse-item > .ant-collapse-header:focus { - outline: none; -} - -.ant-collapse > .ant-collapse-item .ant-collapse-header-collapsible-only { - cursor: default; -} - -.ant-collapse > .ant-collapse-item .ant-collapse-header-collapsible-only .ant-collapse-header-text { - cursor: pointer; -} - -.ant-collapse > .ant-collapse-item.ant-collapse-no-arrow > .ant-collapse-header { - padding-left: 12px; -} - -.ant-collapse-icon-position-right > .ant-collapse-item > .ant-collapse-header { - position: relative; - padding: 12px 16px; - padding-right: 40px; -} - -.ant-collapse-icon-position-right > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow { - position: absolute; - top: 50%; - right: 16px; - left: auto; - margin: 0; - transform: translateY(-50%); -} - -.ant-collapse-content { - color: rgba(0, 0, 0, 0.85); - background-color: #fff; - border-top: 1px solid #d9d9d9; -} - -.ant-collapse-content > .ant-collapse-content-box { - padding: 16px; -} - -.ant-collapse-content-hidden { - display: none; -} - -.ant-collapse-item:last-child > .ant-collapse-content { - border-radius: 0 0 2px 2px; -} - -.ant-collapse-borderless { - background-color: #fafafa; - border: 0; -} - -.ant-collapse-borderless > .ant-collapse-item { - border-bottom: 1px solid #d9d9d9; -} - -.ant-collapse-borderless > .ant-collapse-item:last-child, -.ant-collapse-borderless > .ant-collapse-item:last-child .ant-collapse-header { - border-radius: 0; -} - -.ant-collapse-borderless > .ant-collapse-item > .ant-collapse-content { - background-color: transparent; - border-top: 0; -} - -.ant-collapse-borderless > .ant-collapse-item > .ant-collapse-content > .ant-collapse-content-box { - padding-top: 4px; -} - -.ant-collapse-ghost { - background-color: transparent; - border: 0; -} - -.ant-collapse-ghost > .ant-collapse-item { - border-bottom: 0; -} - -.ant-collapse-ghost > .ant-collapse-item > .ant-collapse-content { - background-color: transparent; - border-top: 0; -} - -.ant-collapse-ghost > .ant-collapse-item > .ant-collapse-content > .ant-collapse-content-box { - padding-top: 12px; - padding-bottom: 12px; -} - -.ant-collapse .ant-collapse-item-disabled > .ant-collapse-header, -.ant-collapse .ant-collapse-item-disabled > .ant-collapse-header > .arrow { - color: rgba(0, 0, 0, 0.25); - cursor: not-allowed; -} - -.ant-comment { - position: relative; - background-color: inherit; -} - -.ant-comment-inner { - display: flex; - padding: 16px 0; -} - -.ant-comment-avatar { - position: relative; - flex-shrink: 0; - margin-right: 12px; - cursor: pointer; -} - -.ant-comment-avatar img { - width: 32px; - height: 32px; - border-radius: 50%; -} - -.ant-comment-content { - position: relative; - flex: 1 1 auto; - min-width: 1px; - font-size: 14px; - word-wrap: break-word; -} - -.ant-comment-content-author { - display: flex; - flex-wrap: wrap; - justify-content: flex-start; - margin-bottom: 4px; - font-size: 14px; -} - -.ant-comment-content-author > a, -.ant-comment-content-author > span { - padding-right: 8px; - font-size: 12px; - line-height: 18px; -} - -.ant-comment-content-author-name { - color: rgba(0, 0, 0, 0.45); - font-size: 14px; - transition: color 0.3s; -} - -.ant-comment-content-author-name > * { - color: rgba(0, 0, 0, 0.45); -} - -.ant-comment-content-author-name > *:hover { - color: rgba(0, 0, 0, 0.45); -} - -.ant-comment-content-author-time { - color: #ccc; - white-space: nowrap; - cursor: auto; -} - -.ant-comment-content-detail p { - margin-bottom: inherit; - white-space: pre-wrap; -} - -.ant-comment-actions { - margin-top: 12px; - margin-bottom: inherit; - padding-left: 0; -} - -.ant-comment-actions > li { - display: inline-block; - color: rgba(0, 0, 0, 0.45); -} - -.ant-comment-actions > li > span { - margin-right: 10px; - color: rgba(0, 0, 0, 0.45); - font-size: 12px; - cursor: pointer; - transition: color 0.3s; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-comment-actions > li > span:hover { - color: #595959; -} - -.ant-comment-nested { - margin-left: 44px; -} - - -.ant-descriptions-header { - display: flex; - align-items: center; - margin-bottom: 20px; -} - -.ant-descriptions-title { - flex: auto; - overflow: hidden; - color: rgba(0, 0, 0, 0.85); - font-weight: bold; - font-size: 16px; - line-height: 1.5715; - white-space: nowrap; - text-overflow: ellipsis; -} - -.ant-descriptions-extra { - margin-left: auto; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; -} - -.ant-descriptions-view { - width: 100%; - overflow: hidden; - border-radius: 2px; -} - -.ant-descriptions-view table { - width: 100%; - table-layout: fixed; -} - -.ant-descriptions-row > th, -.ant-descriptions-row > td { - padding-bottom: 16px; -} - -.ant-descriptions-row:last-child { - border-bottom: none; -} - -.ant-descriptions-item-label { - color: rgba(0, 0, 0, 0.85); - font-weight: normal; - font-size: 14px; - line-height: 1.5715; - text-align: start; -} - -.ant-descriptions-item-label::after { - content: ':'; - position: relative; - top: -0.5px; - margin: 0 8px 0 2px; -} - -.ant-descriptions-item-label.ant-descriptions-item-no-colon::after { - content: ' '; -} - -.ant-descriptions-item-no-label::after { - margin: 0; - content: ''; -} - -.ant-descriptions-item-content { - display: table-cell; - flex: 1; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - line-height: 1.5715; - word-break: break-word; - overflow-wrap: break-word; -} - -.ant-descriptions-item { - padding-bottom: 0; - vertical-align: top; -} - -.ant-descriptions-item-container { - display: flex; -} - -.ant-descriptions-item-container .ant-descriptions-item-label, -.ant-descriptions-item-container .ant-descriptions-item-content { - display: inline-flex; - align-items: baseline; -} - -.ant-descriptions-middle .ant-descriptions-row > th, -.ant-descriptions-middle .ant-descriptions-row > td { - padding-bottom: 12px; -} - -.ant-descriptions-small .ant-descriptions-row > th, -.ant-descriptions-small .ant-descriptions-row > td { - padding-bottom: 8px; -} - -.ant-descriptions-bordered .ant-descriptions-view { - border: 1px solid #f0f0f0; -} - -.ant-descriptions-bordered .ant-descriptions-view > table { - table-layout: auto; - border-collapse: collapse; -} - -.ant-descriptions-bordered .ant-descriptions-item-label, -.ant-descriptions-bordered .ant-descriptions-item-content { - padding: 16px 24px; - border-right: 1px solid #f0f0f0; -} - -.ant-descriptions-bordered .ant-descriptions-item-label:last-child, -.ant-descriptions-bordered .ant-descriptions-item-content:last-child { - border-right: none; -} - -.ant-descriptions-bordered .ant-descriptions-item-label { - background-color: #fafafa; -} - -.ant-descriptions-bordered .ant-descriptions-item-label::after { - display: none; -} - -.ant-descriptions-bordered .ant-descriptions-row { - border-bottom: 1px solid #f0f0f0; -} - -.ant-descriptions-bordered .ant-descriptions-row:last-child { - border-bottom: none; -} - -.ant-descriptions-bordered.ant-descriptions-middle .ant-descriptions-item-label, -.ant-descriptions-bordered.ant-descriptions-middle .ant-descriptions-item-content { - padding: 12px 24px; -} - -.ant-descriptions-bordered.ant-descriptions-small .ant-descriptions-item-label, -.ant-descriptions-bordered.ant-descriptions-small .ant-descriptions-item-content { - padding: 8px 16px; -} - - -.ant-divider { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - border-top: 1px solid rgba(0, 0, 0, 0.06); -} - -.ant-divider-vertical { - position: relative; - top: -0.06em; - display: inline-block; - height: 0.9em; - margin: 0 8px; - vertical-align: middle; - border-top: 0; - border-left: 1px solid rgba(0, 0, 0, 0.06); -} - -.ant-divider-horizontal { - display: flex; - clear: both; - width: 100%; - min-width: 100%; - margin: 24px 0; -} - -.ant-divider-horizontal.ant-divider-with-text { - display: flex; - margin: 16px 0; - color: rgba(0, 0, 0, 0.85); - font-weight: 500; - font-size: 16px; - white-space: nowrap; - text-align: center; - border-top: 0; - border-top-color: rgba(0, 0, 0, 0.06); -} - -.ant-divider-horizontal.ant-divider-with-text::before, -.ant-divider-horizontal.ant-divider-with-text::after { - position: relative; - top: 50%; - width: 50%; - border-top: 1px solid transparent; - border-top-color: inherit; - border-bottom: 0; - transform: translateY(50%); - content: ''; -} - -.ant-divider-horizontal.ant-divider-with-text-left::before { - top: 50%; - width: 5%; -} - -.ant-divider-horizontal.ant-divider-with-text-left::after { - top: 50%; - width: 95%; -} - -.ant-divider-horizontal.ant-divider-with-text-right::before { - top: 50%; - width: 95%; -} - -.ant-divider-horizontal.ant-divider-with-text-right::after { - top: 50%; - width: 5%; -} - -.ant-divider-inner-text { - display: inline-block; - padding: 0 1em; -} - -.ant-divider-dashed { - background: none; - border-color: rgba(0, 0, 0, 0.06); - border-style: dashed; - border-width: 1px 0 0; -} - -.ant-divider-horizontal.ant-divider-with-text.ant-divider-dashed { - border-top: 0; -} - -.ant-divider-horizontal.ant-divider-with-text.ant-divider-dashed::before, -.ant-divider-horizontal.ant-divider-with-text.ant-divider-dashed::after { - border-style: dashed none none; -} - -.ant-divider-vertical.ant-divider-dashed { - border-width: 0 0 0 1px; -} - -.ant-divider-plain.ant-divider-with-text { - color: rgba(0, 0, 0, 0.85); - font-weight: normal; - font-size: 14px; -} - -.ant-drawer { - position: fixed; - z-index: 1000; - width: 0%; - height: 100%; - transition: width 0s ease 0.3s, height 0s ease 0.3s; -} - -.ant-drawer-content-wrapper { - position: absolute; - width: 100%; - height: 100%; - transition: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1), box-shadow 0.3s cubic-bezier(0.23, 1, 0.32, 1); -} - -.ant-drawer .ant-drawer-content { - width: 100%; - height: 100%; -} - -.ant-drawer-left, -.ant-drawer-right { - top: 0; - width: 0%; - height: 100%; -} - -.ant-drawer-left .ant-drawer-content-wrapper, -.ant-drawer-right .ant-drawer-content-wrapper { - height: 100%; -} - -.ant-drawer-left.ant-drawer-open, -.ant-drawer-right.ant-drawer-open { - width: 100%; - transition: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1); -} - -.ant-drawer-left { - left: 0; -} - -.ant-drawer-left .ant-drawer-content-wrapper { - left: 0; -} - -.ant-drawer-left.ant-drawer-open .ant-drawer-content-wrapper { - box-shadow: 6px 0 16px -8px rgba(0, 0, 0, 0.08), 9px 0 28px 0 rgba(0, 0, 0, 0.05), 12px 0 48px 16px rgba(0, 0, 0, 0.03); -} - -.ant-drawer-right { - right: 0; -} - -.ant-drawer-right .ant-drawer-content-wrapper { - right: 0; -} - -.ant-drawer-right.ant-drawer-open .ant-drawer-content-wrapper { - box-shadow: -6px 0 16px -8px rgba(0, 0, 0, 0.08), -9px 0 28px 0 rgba(0, 0, 0, 0.05), -12px 0 48px 16px rgba(0, 0, 0, 0.03); -} - -.ant-drawer-right.ant-drawer-open.no-mask { - right: 1px; - transform: translateX(1px); -} - -.ant-drawer-top, -.ant-drawer-bottom { - left: 0; - width: 100%; - height: 0%; -} - -.ant-drawer-top .ant-drawer-content-wrapper, -.ant-drawer-bottom .ant-drawer-content-wrapper { - width: 100%; -} - -.ant-drawer-top.ant-drawer-open, -.ant-drawer-bottom.ant-drawer-open { - height: 100%; - transition: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1); -} - -.ant-drawer-top { - top: 0; -} - -.ant-drawer-top.ant-drawer-open .ant-drawer-content-wrapper { - box-shadow: 0 6px 16px -8px rgba(0, 0, 0, 0.08), 0 9px 28px 0 rgba(0, 0, 0, 0.05), 0 12px 48px 16px rgba(0, 0, 0, 0.03); -} - -.ant-drawer-bottom { - bottom: 0; -} - -.ant-drawer-bottom .ant-drawer-content-wrapper { - bottom: 0; -} - -.ant-drawer-bottom.ant-drawer-open .ant-drawer-content-wrapper { - box-shadow: 0 -6px 16px -8px rgba(0, 0, 0, 0.08), 0 -9px 28px 0 rgba(0, 0, 0, 0.05), 0 -12px 48px 16px rgba(0, 0, 0, 0.03); -} - -.ant-drawer-bottom.ant-drawer-open.no-mask { - bottom: 1px; - transform: translateY(1px); -} - -.ant-drawer.ant-drawer-open .ant-drawer-mask { - height: 100%; - opacity: 1; - transition: none; - -webkit-animation: antdDrawerFadeIn 0.3s cubic-bezier(0.23, 1, 0.32, 1); - animation: antdDrawerFadeIn 0.3s cubic-bezier(0.23, 1, 0.32, 1); - pointer-events: auto; -} - -.ant-drawer-title { - flex: 1; - margin: 0; - color: rgba(0, 0, 0, 0.85); - font-weight: 500; - font-size: 16px; - line-height: 22px; -} - -.ant-drawer-content { - position: relative; - z-index: 1; - overflow: auto; - background-color: #fff; - background-clip: padding-box; - border: 0; -} - -.ant-drawer-close { - display: inline-block; - margin-right: 12px; - color: rgba(0, 0, 0, 0.45); - font-weight: 700; - font-size: 16px; - font-style: normal; - line-height: 1; - text-align: center; - text-transform: none; - text-decoration: none; - background: transparent; - border: 0; - outline: 0; - cursor: pointer; - transition: color 0.3s; - text-rendering: auto; -} - -.ant-drawer-close:focus, -.ant-drawer-close:hover { - color: rgba(0, 0, 0, 0.75); - text-decoration: none; -} - -.ant-drawer-header { - position: relative; - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px 24px; - color: rgba(0, 0, 0, 0.85); - background: #fff; - border-bottom: 1px solid #f0f0f0; - border-radius: 2px 2px 0 0; -} - -.ant-drawer-header-title { - display: flex; - flex: 1; - align-items: center; - justify-content: space-between; -} - -.ant-drawer-header-close-only { - padding-bottom: 0; - border: none; -} - -.ant-drawer-wrapper-body { - display: flex; - flex-flow: column nowrap; - width: 100%; - height: 100%; -} - -.ant-drawer-body { - flex-grow: 1; - padding: 24px; - overflow: auto; - font-size: 14px; - line-height: 1.5715; - word-wrap: break-word; -} - -.ant-drawer-footer { - flex-shrink: 0; - padding: 10px 16px; - border-top: 1px solid #f0f0f0; -} - -.ant-drawer-mask { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 0; - background-color: rgba(0, 0, 0, 0.45); - opacity: 0; - transition: opacity 0.3s linear, height 0s ease 0.3s; - pointer-events: none; -} - -.ant-drawer .ant-picker-clear { - background: #fff; -} - -@-webkit-keyframes antdDrawerFadeIn { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - -@keyframes antdDrawerFadeIn { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - -.ant-form-item .ant-upload { - background: transparent; -} - -.ant-form-item .ant-upload.ant-upload-drag { - background: #fafafa; -} - -.ant-form-item input[type='radio'], -.ant-form-item input[type='checkbox'] { - width: 14px; - height: 14px; -} - -.ant-form-item .ant-radio-inline, -.ant-form-item .ant-checkbox-inline { - display: inline-block; - margin-left: 8px; - font-weight: normal; - vertical-align: middle; - cursor: pointer; -} - -.ant-form-item .ant-radio-inline:first-child, -.ant-form-item .ant-checkbox-inline:first-child { - margin-left: 0; -} - -.ant-form-item .ant-checkbox-vertical, -.ant-form-item .ant-radio-vertical { - display: block; -} - -.ant-form-item .ant-checkbox-vertical + .ant-checkbox-vertical, -.ant-form-item .ant-radio-vertical + .ant-radio-vertical { - margin-left: 0; -} - -.ant-form-item .ant-input-number + .ant-form-text { - margin-left: 8px; -} - -.ant-form-item .ant-input-number-handler-wrap { - z-index: 2; -} - -.ant-form-item .ant-select, -.ant-form-item .ant-cascader-picker { - width: 100%; -} - -.ant-form-item .ant-picker-calendar-year-select, -.ant-form-item .ant-picker-calendar-month-select, -.ant-form-item .ant-input-group .ant-select, -.ant-form-item .ant-input-group .ant-cascader-picker, -.ant-form-item .ant-input-number-group .ant-select, -.ant-form-item .ant-input-number-group .ant-cascader-picker { - width: auto; -} - -.ant-form-inline { - display: flex; - flex-wrap: wrap; -} - -.ant-form-inline .ant-form-item { - flex: none; - flex-wrap: nowrap; - margin-right: 16px; - margin-bottom: 0; -} - -.ant-form-inline .ant-form-item-with-help { - margin-bottom: 24px; -} - -.ant-form-inline .ant-form-item > .ant-form-item-label, -.ant-form-inline .ant-form-item > .ant-form-item-control { - display: inline-block; - vertical-align: top; -} - -.ant-form-inline .ant-form-item > .ant-form-item-label { - flex: none; -} - -.ant-form-inline .ant-form-item .ant-form-text { - display: inline-block; -} - -.ant-form-inline .ant-form-item .ant-form-item-has-feedback { - display: inline-block; -} - -.ant-form-horizontal .ant-form-item-label { - flex-grow: 0; -} - -.ant-form-horizontal .ant-form-item-control { - flex: 1 1 0; -} - -.ant-form-horizontal .ant-form-item-control:not(.ant-col) { - min-width: 0; -} - -.ant-form-vertical .ant-form-item { - flex-direction: column; -} - -.ant-form-vertical .ant-form-item-label > label { - height: auto; -} - -.ant-form-vertical .ant-form-item-label, -.ant-col-24.ant-form-item-label, -.ant-col-xl-24.ant-form-item-label { - padding: 0 0 8px; - line-height: 1.5715; - white-space: initial; - text-align: left; -} - -.ant-form-vertical .ant-form-item-label > label, -.ant-col-24.ant-form-item-label > label, -.ant-col-xl-24.ant-form-item-label > label { - margin: 0; -} - -.ant-form-vertical .ant-form-item-label > label::after, -.ant-col-24.ant-form-item-label > label::after, -.ant-col-xl-24.ant-form-item-label > label::after { - display: none; -} - -@media (max-width: 575px) { - .ant-form-item .ant-form-item-label { - padding: 0 0 8px; - line-height: 1.5715; - white-space: initial; - text-align: left; - } - .ant-form-item .ant-form-item-label > label { - margin: 0; - } - .ant-form-item .ant-form-item-label > label::after { - display: none; - } - .ant-form .ant-form-item { - flex-wrap: wrap; - } - .ant-form .ant-form-item .ant-form-item-label, - .ant-form .ant-form-item .ant-form-item-control { - flex: 0 0 100%; - max-width: 100%; - } - .ant-col-xs-24.ant-form-item-label { - padding: 0 0 8px; - line-height: 1.5715; - white-space: initial; - text-align: left; - } - .ant-col-xs-24.ant-form-item-label > label { - margin: 0; - } - .ant-col-xs-24.ant-form-item-label > label::after { - display: none; - } -} - -@media (max-width: 767px) { - .ant-col-sm-24.ant-form-item-label { - padding: 0 0 8px; - line-height: 1.5715; - white-space: initial; - text-align: left; - } - .ant-col-sm-24.ant-form-item-label > label { - margin: 0; - } - .ant-col-sm-24.ant-form-item-label > label::after { - display: none; - } -} - -@media (max-width: 991px) { - .ant-col-md-24.ant-form-item-label { - padding: 0 0 8px; - line-height: 1.5715; - white-space: initial; - text-align: left; - } - .ant-col-md-24.ant-form-item-label > label { - margin: 0; - } - .ant-col-md-24.ant-form-item-label > label::after { - display: none; - } -} - -@media (max-width: 1199px) { - .ant-col-lg-24.ant-form-item-label { - padding: 0 0 8px; - line-height: 1.5715; - white-space: initial; - text-align: left; - } - .ant-col-lg-24.ant-form-item-label > label { - margin: 0; - } - .ant-col-lg-24.ant-form-item-label > label::after { - display: none; - } -} - -@media (max-width: 1599px) { - .ant-col-xl-24.ant-form-item-label { - padding: 0 0 8px; - line-height: 1.5715; - white-space: initial; - text-align: left; - } - .ant-col-xl-24.ant-form-item-label > label { - margin: 0; - } - .ant-col-xl-24.ant-form-item-label > label::after { - display: none; - } -} - -.ant-form-item { - /* Some non-status related component style is in `components.less` */ - /* To support leave along ErrorList. We add additional className to handle explain style */ -} - -.ant-form-item-explain-error { - color: #ff4d4f; -} - -.ant-form-item-explain-warning { - color: #faad14; -} - -.ant-form-item-has-feedback .ant-input { - padding-right: 24px; -} - -.ant-form-item-has-feedback .ant-input-affix-wrapper .ant-input-suffix { - padding-right: 18px; -} - -.ant-form-item-has-feedback .ant-input-search:not(.ant-input-search-enter-button) .ant-input-suffix { - right: 28px; -} - -.ant-form-item-has-feedback .ant-switch { - margin: 2px 0 4px; -} - -.ant-form-item-has-feedback > .ant-select .ant-select-arrow, -.ant-form-item-has-feedback > .ant-select .ant-select-clear, -.ant-form-item-has-feedback :not(.ant-input-group-addon) > .ant-select .ant-select-arrow, -.ant-form-item-has-feedback :not(.ant-input-group-addon) > .ant-select .ant-select-clear, -.ant-form-item-has-feedback :not(.ant-input-number-group-addon) > .ant-select .ant-select-arrow, -.ant-form-item-has-feedback :not(.ant-input-number-group-addon) > .ant-select .ant-select-clear { - right: 32px; -} - -.ant-form-item-has-feedback > .ant-select .ant-select-selection-selected-value, -.ant-form-item-has-feedback :not(.ant-input-group-addon) > .ant-select .ant-select-selection-selected-value, -.ant-form-item-has-feedback :not(.ant-input-number-group-addon) > .ant-select .ant-select-selection-selected-value { - padding-right: 42px; -} - -.ant-form-item-has-feedback .ant-cascader-picker-arrow { - margin-right: 19px; -} - -.ant-form-item-has-feedback .ant-cascader-picker-clear { - right: 32px; -} - -.ant-form-item-has-feedback .ant-picker { - padding-right: 29.2px; -} - -.ant-form-item-has-feedback .ant-picker-large { - padding-right: 29.2px; -} - -.ant-form-item-has-feedback .ant-picker-small { - padding-right: 25.2px; -} - -.ant-form-item-has-feedback.ant-form-item-has-success .ant-form-item-children-icon, -.ant-form-item-has-feedback.ant-form-item-has-warning .ant-form-item-children-icon, -.ant-form-item-has-feedback.ant-form-item-has-error .ant-form-item-children-icon, -.ant-form-item-has-feedback.ant-form-item-is-validating .ant-form-item-children-icon { - position: absolute; - top: 50%; - right: 0; - z-index: 1; - width: 32px; - height: 20px; - margin-top: -10px; - font-size: 14px; - line-height: 20px; - text-align: center; - visibility: visible; - -webkit-animation: zoomIn 0.3s cubic-bezier(0.12, 0.4, 0.29, 1.46); - animation: zoomIn 0.3s cubic-bezier(0.12, 0.4, 0.29, 1.46); - pointer-events: none; -} - -.ant-form-item-has-success.ant-form-item-has-feedback .ant-form-item-children-icon { - color: #52c41a; - -webkit-animation-name: diffZoomIn1 !important; - animation-name: diffZoomIn1 !important; -} - -.ant-form-item-has-warning .ant-form-item-split { - color: #faad14; -} - -.ant-form-item-has-warning :not(.ant-input-disabled):not(.ant-input-borderless).ant-input, -.ant-form-item-has-warning :not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper, -.ant-form-item-has-warning :not(.ant-input-disabled):not(.ant-input-borderless).ant-input:hover, -.ant-form-item-has-warning :not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper:hover { - background-color: #fff; - border-color: #faad14; -} - -.ant-form-item-has-warning :not(.ant-input-disabled):not(.ant-input-borderless).ant-input:focus, -.ant-form-item-has-warning :not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper:focus, -.ant-form-item-has-warning :not(.ant-input-disabled):not(.ant-input-borderless).ant-input-focused, -.ant-form-item-has-warning :not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper-focused { - border-color: #ffc53d; - box-shadow: 0 0 0 2px rgba(250, 173, 20, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-form-item-has-warning .ant-calendar-picker-open .ant-calendar-picker-input { - border-color: #ffc53d; - box-shadow: 0 0 0 2px rgba(250, 173, 20, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-form-item-has-warning .ant-input-prefix { - color: #faad14; -} - -.ant-form-item-has-warning .ant-input-group-addon, -.ant-form-item-has-warning .ant-input-number-group-addon { - color: #faad14; - border-color: #faad14; -} - -.ant-form-item-has-warning .has-feedback { - color: #faad14; -} - -.ant-form-item-has-warning.ant-form-item-has-feedback .ant-form-item-children-icon { - color: #faad14; - -webkit-animation-name: diffZoomIn3 !important; - animation-name: diffZoomIn3 !important; -} - -.ant-form-item-has-warning .ant-select:not(.ant-select-disabled):not(.ant-select-customize-input) .ant-select-selector { - background-color: #fff; - border-color: #faad14 !important; -} - -.ant-form-item-has-warning .ant-select:not(.ant-select-disabled):not(.ant-select-customize-input).ant-select-open .ant-select-selector, -.ant-form-item-has-warning .ant-select:not(.ant-select-disabled):not(.ant-select-customize-input).ant-select-focused .ant-select-selector { - border-color: #ffc53d; - box-shadow: 0 0 0 2px rgba(250, 173, 20, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-form-item-has-warning .ant-input-number, -.ant-form-item-has-warning .ant-picker { - background-color: #fff; - border-color: #faad14; -} - -.ant-form-item-has-warning .ant-input-number-focused, -.ant-form-item-has-warning .ant-picker-focused, -.ant-form-item-has-warning .ant-input-number:focus, -.ant-form-item-has-warning .ant-picker:focus { - border-color: #ffc53d; - box-shadow: 0 0 0 2px rgba(250, 173, 20, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-form-item-has-warning .ant-input-number:not([disabled]):hover, -.ant-form-item-has-warning .ant-picker:not([disabled]):hover { - background-color: #fff; - border-color: #faad14; -} - -.ant-form-item-has-warning .ant-cascader-picker:focus .ant-cascader-input { - border-color: #ffc53d; - box-shadow: 0 0 0 2px rgba(250, 173, 20, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-form-item-has-error .ant-form-item-split { - color: #ff4d4f; -} - -.ant-form-item-has-error :not(.ant-input-disabled):not(.ant-input-borderless).ant-input, -.ant-form-item-has-error :not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper, -.ant-form-item-has-error :not(.ant-input-disabled):not(.ant-input-borderless).ant-input:hover, -.ant-form-item-has-error :not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper:hover { - background-color: #fff; - border-color: #ff4d4f; -} - -.ant-form-item-has-error :not(.ant-input-disabled):not(.ant-input-borderless).ant-input:focus, -.ant-form-item-has-error :not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper:focus, -.ant-form-item-has-error :not(.ant-input-disabled):not(.ant-input-borderless).ant-input-focused, -.ant-form-item-has-error :not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper-focused { - border-color: #ff7875; - box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-form-item-has-error .ant-calendar-picker-open .ant-calendar-picker-input { - border-color: #ff7875; - box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-form-item-has-error .ant-input-prefix { - color: #ff4d4f; -} - -.ant-form-item-has-error .ant-input-group-addon, -.ant-form-item-has-error .ant-input-number-group-addon { - color: #ff4d4f; - border-color: #ff4d4f; -} - -.ant-form-item-has-error .has-feedback { - color: #ff4d4f; -} - -.ant-form-item-has-error.ant-form-item-has-feedback .ant-form-item-children-icon { - color: #ff4d4f; - -webkit-animation-name: diffZoomIn2 !important; - animation-name: diffZoomIn2 !important; -} - -.ant-form-item-has-error .ant-select:not(.ant-select-disabled):not(.ant-select-customize-input) .ant-select-selector { - background-color: #fff; - border-color: #ff4d4f !important; -} - -.ant-form-item-has-error .ant-select:not(.ant-select-disabled):not(.ant-select-customize-input).ant-select-open .ant-select-selector, -.ant-form-item-has-error .ant-select:not(.ant-select-disabled):not(.ant-select-customize-input).ant-select-focused .ant-select-selector { - border-color: #ff7875; - box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-form-item-has-error .ant-input-group-addon .ant-select.ant-select-single:not(.ant-select-customize-input) .ant-select-selector, -.ant-form-item-has-error .ant-input-number-group-addon .ant-select.ant-select-single:not(.ant-select-customize-input) .ant-select-selector { - background-color: inherit; - border: 0; - box-shadow: none; -} - -.ant-form-item-has-error .ant-select.ant-select-auto-complete .ant-input:focus { - border-color: #ff4d4f; -} - -.ant-form-item-has-error .ant-input-number, -.ant-form-item-has-error .ant-picker { - background-color: #fff; - border-color: #ff4d4f; -} - -.ant-form-item-has-error .ant-input-number-focused, -.ant-form-item-has-error .ant-picker-focused, -.ant-form-item-has-error .ant-input-number:focus, -.ant-form-item-has-error .ant-picker:focus { - border-color: #ff7875; - box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-form-item-has-error .ant-input-number:not([disabled]):hover, -.ant-form-item-has-error .ant-picker:not([disabled]):hover { - background-color: #fff; - border-color: #ff4d4f; -} - -.ant-form-item-has-error .ant-mention-wrapper .ant-mention-editor, -.ant-form-item-has-error .ant-mention-wrapper .ant-mention-editor:not([disabled]):hover { - background-color: #fff; - border-color: #ff4d4f; -} - -.ant-form-item-has-error .ant-mention-wrapper.ant-mention-active:not([disabled]) .ant-mention-editor, -.ant-form-item-has-error .ant-mention-wrapper .ant-mention-editor:not([disabled]):focus { - border-color: #ff7875; - box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-form-item-has-error .ant-cascader-picker:hover .ant-cascader-picker-label:hover + .ant-cascader-input.ant-input { - border-color: #ff4d4f; -} - -.ant-form-item-has-error .ant-cascader-picker:focus .ant-cascader-input { - background-color: #fff; - border-color: #ff7875; - box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-form-item-has-error .ant-transfer-list { - border-color: #ff4d4f; -} - -.ant-form-item-has-error .ant-transfer-list-search:not([disabled]) { - border-color: #d9d9d9; -} - -.ant-form-item-has-error .ant-transfer-list-search:not([disabled]):hover { - border-color: #40a9ff; - border-right-width: 1px !important; -} - -.ant-form-item-has-error .ant-transfer-list-search:not([disabled]):focus { - border-color: #40a9ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-form-item-has-error .ant-radio-button-wrapper { - border-color: #ff4d4f !important; -} - -.ant-form-item-has-error .ant-radio-button-wrapper:not(:first-child)::before { - background-color: #ff4d4f; -} - -.ant-form-item-has-error .ant-mentions { - border-color: #ff4d4f !important; -} - -.ant-form-item-has-error .ant-mentions-focused, -.ant-form-item-has-error .ant-mentions:focus { - border-color: #ff7875; - box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-form-item-is-validating.ant-form-item-has-feedback .ant-form-item-children-icon { - display: inline-block; - color: #1890ff; -} - -.ant-form { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; -} - -.ant-form legend { - display: block; - width: 100%; - margin-bottom: 20px; - padding: 0; - color: rgba(0, 0, 0, 0.45); - font-size: 16px; - line-height: inherit; - border: 0; - border-bottom: 1px solid #d9d9d9; -} - -.ant-form label { - font-size: 14px; -} - -.ant-form input[type='search'] { - box-sizing: border-box; -} - -.ant-form input[type='radio'], -.ant-form input[type='checkbox'] { - line-height: normal; -} - -.ant-form input[type='file'] { - display: block; -} - -.ant-form input[type='range'] { - display: block; - width: 100%; -} - -.ant-form select[multiple], -.ant-form select[size] { - height: auto; -} - -.ant-form input[type='file']:focus, -.ant-form input[type='radio']:focus, -.ant-form input[type='checkbox']:focus { - outline: thin dotted; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} - -.ant-form output { - display: block; - padding-top: 15px; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - line-height: 1.5715; -} - -.ant-form .ant-form-text { - display: inline-block; - padding-right: 8px; -} - -.ant-form-small .ant-form-item-label > label { - height: 24px; -} - -.ant-form-small .ant-form-item-control-input { - min-height: 24px; -} - -.ant-form-large .ant-form-item-label > label { - height: 40px; -} - -.ant-form-large .ant-form-item-control-input { - min-height: 40px; -} - -.ant-form-item { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - margin-bottom: 24px; - vertical-align: top; - transition: margin-bottom 0.3s 0.017s linear; -} - -.ant-form-item-with-help { - margin-bottom: 0; - transition: none; -} - -.ant-form-item-hidden, -.ant-form-item-hidden.ant-row { - display: none; -} - -.ant-form-item-label { - display: inline-block; - flex-grow: 0; - overflow: hidden; - white-space: nowrap; - text-align: right; - vertical-align: middle; -} - -.ant-form-item-label-left { - text-align: left; -} - -.ant-form-item-label > label { - position: relative; - display: inline-flex; - align-items: center; - max-width: 100%; - height: 32px; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; -} - -.ant-form-item-label > label > .anticon { - font-size: 14px; - vertical-align: top; -} - -.ant-form-item-label > label.ant-form-item-required:not(.ant-form-item-required-mark-optional)::before { - display: inline-block; - margin-right: 4px; - color: #ff4d4f; - font-size: 14px; - font-family: SimSun, sans-serif; - line-height: 1; - content: '*'; -} - -.ant-form-hide-required-mark .ant-form-item-label > label.ant-form-item-required:not(.ant-form-item-required-mark-optional)::before { - display: none; -} - -.ant-form-item-label > label .ant-form-item-optional { - display: inline-block; - margin-left: 4px; - color: rgba(0, 0, 0, 0.45); -} - -.ant-form-hide-required-mark .ant-form-item-label > label .ant-form-item-optional { - display: none; -} - -.ant-form-item-label > label .ant-form-item-tooltip { - color: rgba(0, 0, 0, 0.45); - cursor: help; - -ms-writing-mode: lr-tb; - writing-mode: horizontal-tb; - -webkit-margin-start: 4px; - margin-inline-start: 4px; -} - -.ant-form-item-label > label::after { - content: ':'; - position: relative; - top: -0.5px; - margin: 0 8px 0 2px; -} - -.ant-form-item-label > label.ant-form-item-no-colon::after { - content: ' '; -} - -.ant-form-item-control { - display: flex; - flex-direction: column; - flex-grow: 1; -} - -.ant-form-item-control:first-child:not([class^='ant-col-']):not([class*=' ant-col-']) { - width: 100%; -} - -.ant-form-item-control-input { - position: relative; - display: flex; - align-items: center; - min-height: 32px; -} - -.ant-form-item-control-input-content { - flex: auto; - max-width: 100%; -} - -.ant-form-item-explain, -.ant-form-item-extra { - clear: both; - color: rgba(0, 0, 0, 0.45); - font-size: 14px; - line-height: 1.5715; - transition: color 0.3s cubic-bezier(0.215, 0.61, 0.355, 1); -} - -.ant-form-item-explain-connected { - height: 0; - min-height: 0; - opacity: 0; -} - -.ant-form-item-extra { - min-height: 24px; -} - -.ant-form-item .ant-input-textarea-show-count::after { - margin-bottom: -22px; -} - -.ant-form-item-with-help .ant-form-item-explain { - height: auto; - min-height: 24px; - opacity: 1; -} - -.ant-show-help { - transition: height 0.3s linear, min-height 0.3s linear, margin-bottom 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), opacity 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); -} - -.ant-show-help-leave { - min-height: 24px; -} - -.ant-show-help-leave-active { - min-height: 0; -} - -.ant-show-help-item { - overflow: hidden; - transition: height 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), opacity 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1) !important; -} - -.ant-show-help-item-appear, -.ant-show-help-item-enter { - transform: translateY(-5px); - opacity: 0; -} - -.ant-show-help-item-appear-active, -.ant-show-help-item-enter-active { - transform: translateY(0); - opacity: 1; -} - -.ant-show-help-item-leave-active { - transform: translateY(-5px); -} - -@-webkit-keyframes diffZoomIn1 { - 0% { - transform: scale(0); - opacity: 0; - } - 100% { - transform: scale(1); - opacity: 1; - } -} - -@keyframes diffZoomIn1 { - 0% { - transform: scale(0); - opacity: 0; - } - 100% { - transform: scale(1); - opacity: 1; - } -} - -@-webkit-keyframes diffZoomIn2 { - 0% { - transform: scale(0); - opacity: 0; - } - 100% { - transform: scale(1); - opacity: 1; - } -} - -@keyframes diffZoomIn2 { - 0% { - transform: scale(0); - opacity: 0; - } - 100% { - transform: scale(1); - opacity: 1; - } -} - -@-webkit-keyframes diffZoomIn3 { - 0% { - transform: scale(0); - opacity: 0; - } - 100% { - transform: scale(1); - opacity: 1; - } -} - -@keyframes diffZoomIn3 { - 0% { - transform: scale(0); - opacity: 0; - } - 100% { - transform: scale(1); - opacity: 1; - } -} - -.ant-image { - position: relative; - display: inline-block; -} - -.ant-image-img { - display: block; - width: 100%; - height: auto; -} - -.ant-image-img-placeholder { - background-color: #f5f5f5; - background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTQuNSAyLjVoLTEzQS41LjUgMCAwIDAgMSAzdjEwYS41LjUgMCAwIDAgLjUuNWgxM2EuNS41IDAgMCAwIC41LS41VjNhLjUuNSAwIDAgMC0uNS0uNXpNNS4yODEgNC43NWExIDEgMCAwIDEgMCAyIDEgMSAwIDAgMSAwLTJ6bTguMDMgNi44M2EuMTI3LjEyNyAwIDAgMS0uMDgxLjAzSDIuNzY5YS4xMjUuMTI1IDAgMCAxLS4wOTYtLjIwN2wyLjY2MS0zLjE1NmEuMTI2LjEyNiAwIDAgMSAuMTc3LS4wMTZsLjAxNi4wMTZMNy4wOCAxMC4wOWwyLjQ3LTIuOTNhLjEyNi4xMjYgMCAwIDEgLjE3Ny0uMDE2bC4wMTUuMDE2IDMuNTg4IDQuMjQ0YS4xMjcuMTI3IDAgMCAxLS4wMi4xNzV6IiBmaWxsPSIjOEM4QzhDIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz48L3N2Zz4='); - background-repeat: no-repeat; - background-position: center center; - background-size: 30%; -} - -.ant-image-mask { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - display: flex; - align-items: center; - justify-content: center; - color: #fff; - background: rgba(0, 0, 0, 0.5); - cursor: pointer; - opacity: 0; - transition: opacity 0.3s; -} - -.ant-image-mask-info { - padding: 0 4px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - -.ant-image-mask-info .anticon { - -webkit-margin-end: 4px; - margin-inline-end: 4px; -} - -.ant-image-mask:hover { - opacity: 1; -} - -.ant-image-placeholder { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; -} - -.ant-image-preview { - pointer-events: none; - height: 100%; - text-align: center; -} - -.ant-image-preview.ant-zoom-enter, -.ant-image-preview.antzoom-appear { - transform: none; - opacity: 0; - -webkit-animation-duration: 0.3s; - animation-duration: 0.3s; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-image-preview-mask { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1000; - height: 100%; - background-color: rgba(0, 0, 0, 0.45); -} - -.ant-image-preview-mask-hidden { - display: none; -} - -.ant-image-preview-wrap { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - overflow: auto; - outline: 0; - -webkit-overflow-scrolling: touch; -} - -.ant-image-preview-body { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - overflow: hidden; -} - -.ant-image-preview-img { - max-width: 100%; - max-height: 100%; - vertical-align: middle; - transform: scale3d(1, 1, 1); - cursor: -webkit-grab; - cursor: grab; - transition: transform 0.3s cubic-bezier(0.215, 0.61, 0.355, 1) 0s; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - pointer-events: auto; -} - -.ant-image-preview-img-wrapper { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - transition: transform 0.3s cubic-bezier(0.215, 0.61, 0.355, 1) 0s; -} - -.ant-image-preview-img-wrapper::before { - display: inline-block; - width: 1px; - height: 50%; - margin-right: -1px; - content: ''; -} - -.ant-image-preview-moving .ant-image-preview-img { - cursor: -webkit-grabbing; - cursor: grabbing; -} - -.ant-image-preview-moving .ant-image-preview-img-wrapper { - transition-duration: 0s; -} - -.ant-image-preview-wrap { - z-index: 1080; -} - -.ant-image-preview-operations { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - font-feature-settings: 'tnum'; - position: absolute; - top: 0; - right: 0; - z-index: 1; - display: flex; - flex-direction: row-reverse; - align-items: center; - width: 100%; - color: rgba(255, 255, 255, 0.85); - list-style: none; - background: rgba(0, 0, 0, 0.1); - pointer-events: auto; -} - -.ant-image-preview-operations-operation { - margin-left: 12px; - padding: 12px; - cursor: pointer; -} - -.ant-image-preview-operations-operation-disabled { - color: rgba(255, 255, 255, 0.25); - pointer-events: none; -} - -.ant-image-preview-operations-operation:last-of-type { - margin-left: 0; -} - -.ant-image-preview-operations-icon { - font-size: 18px; -} - -.ant-image-preview-switch-left, -.ant-image-preview-switch-right { - position: absolute; - top: 50%; - right: 10px; - z-index: 1; - display: flex; - align-items: center; - justify-content: center; - width: 44px; - height: 44px; - margin-top: -22px; - color: rgba(255, 255, 255, 0.85); - background: rgba(0, 0, 0, 0.1); - border-radius: 50%; - cursor: pointer; - pointer-events: auto; -} - -.ant-image-preview-switch-left-disabled, -.ant-image-preview-switch-right-disabled { - color: rgba(255, 255, 255, 0.25); - cursor: not-allowed; -} - -.ant-image-preview-switch-left-disabled > .anticon, -.ant-image-preview-switch-right-disabled > .anticon { - cursor: not-allowed; -} - -.ant-image-preview-switch-left > .anticon, -.ant-image-preview-switch-right > .anticon { - font-size: 18px; -} - -.ant-image-preview-switch-left { - left: 10px; -} - -.ant-image-preview-switch-right { - right: 10px; -} - -.ant-input-number { - box-sizing: border-box; - font-variant: tabular-nums; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - width: 100%; - min-width: 0; - padding: 4px 11px; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - line-height: 1.5715; - background-color: #fff; - background-image: none; - transition: all 0.3s; - display: inline-block; - width: 90px; - margin: 0; - padding: 0; - border: 1px solid #d9d9d9; - border-radius: 2px; -} - -.ant-input-number::-moz-placeholder { - opacity: 1; -} - -.ant-input-number:-ms-input-placeholder { - color: #bfbfbf; - -ms-user-select: none; - user-select: none; -} - -.ant-input-number::placeholder { - color: #bfbfbf; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-input-number:-moz-placeholder-shown { - text-overflow: ellipsis; -} - -.ant-input-number:-ms-input-placeholder { - text-overflow: ellipsis; -} - -.ant-input-number:placeholder-shown { - text-overflow: ellipsis; -} - -.ant-input-number:hover { - border-color: #40a9ff; - border-right-width: 1px !important; -} - -.ant-input-number:focus, -.ant-input-number-focused { - border-color: #40a9ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-input-number-disabled { - color: rgba(0, 0, 0, 0.25); - background-color: #f5f5f5; - border-color: #d9d9d9; - box-shadow: none; - cursor: not-allowed; - opacity: 1; -} - -.ant-input-number-disabled:hover { - border-color: #d9d9d9; - border-right-width: 1px !important; -} - -.ant-input-number[disabled] { - color: rgba(0, 0, 0, 0.25); - background-color: #f5f5f5; - border-color: #d9d9d9; - box-shadow: none; - cursor: not-allowed; - opacity: 1; -} - -.ant-input-number[disabled]:hover { - border-color: #d9d9d9; - border-right-width: 1px !important; -} - -.ant-input-number-borderless, -.ant-input-number-borderless:hover, -.ant-input-number-borderless:focus, -.ant-input-number-borderless-focused, -.ant-input-number-borderless-disabled, -.ant-input-number-borderless[disabled] { - background-color: transparent; - border: none; - box-shadow: none; -} - -textarea.ant-input-number { - max-width: 100%; - height: auto; - min-height: 32px; - line-height: 1.5715; - vertical-align: bottom; - transition: all 0.3s, height 0s; -} - -.ant-input-number-lg { - padding: 6.5px 11px; - font-size: 16px; -} - -.ant-input-number-sm { - padding: 0px 7px; -} - -.ant-input-number-group { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - display: table; - width: 100%; - border-collapse: separate; - border-spacing: 0; -} - -.ant-input-number-group[class*='col-'] { - float: none; - padding-right: 0; - padding-left: 0; -} - -.ant-input-number-group > [class*='col-'] { - padding-right: 8px; -} - -.ant-input-number-group > [class*='col-']:last-child { - padding-right: 0; -} - -.ant-input-number-group-addon, -.ant-input-number-group-wrap, -.ant-input-number-group > .ant-input-number { - display: table-cell; -} - -.ant-input-number-group-addon:not(:first-child):not(:last-child), -.ant-input-number-group-wrap:not(:first-child):not(:last-child), -.ant-input-number-group > .ant-input-number:not(:first-child):not(:last-child) { - border-radius: 0; -} - -.ant-input-number-group-addon, -.ant-input-number-group-wrap { - width: 1px; - white-space: nowrap; - vertical-align: middle; -} - -.ant-input-number-group-wrap > * { - display: block !important; -} - -.ant-input-number-group .ant-input-number { - float: left; - width: 100%; - margin-bottom: 0; - text-align: inherit; -} - -.ant-input-number-group .ant-input-number:focus { - z-index: 1; - border-right-width: 1px; -} - -.ant-input-number-group .ant-input-number:hover { - z-index: 1; - border-right-width: 1px; -} - -.ant-input-search-with-button .ant-input-number-group .ant-input-number:hover { - z-index: 0; -} - -.ant-input-number-group-addon { - position: relative; - padding: 0 11px; - color: rgba(0, 0, 0, 0.85); - font-weight: normal; - font-size: 14px; - text-align: center; - background-color: #fafafa; - border: 1px solid #d9d9d9; - border-radius: 2px; - transition: all 0.3s; -} - -.ant-input-number-group-addon .ant-select { - margin: -5px -11px; -} - -.ant-input-number-group-addon .ant-select.ant-select-single:not(.ant-select-customize-input) .ant-select-selector { - background-color: inherit; - border: 1px solid transparent; - box-shadow: none; -} - -.ant-input-number-group-addon .ant-select-open .ant-select-selector, -.ant-input-number-group-addon .ant-select-focused .ant-select-selector { - color: #1890ff; -} - -.ant-input-number-group-addon .ant-cascader-picker { - margin: -9px -12px; - background-color: transparent; -} - -.ant-input-number-group-addon .ant-cascader-picker .ant-cascader-input { - text-align: left; - border: 0; - box-shadow: none; -} - -.ant-input-number-group > .ant-input-number:first-child, -.ant-input-number-group-addon:first-child { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.ant-input-number-group > .ant-input-number:first-child .ant-select .ant-select-selector, -.ant-input-number-group-addon:first-child .ant-select .ant-select-selector { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.ant-input-number-group > .ant-input-number-affix-wrapper:not(:first-child) .ant-input-number { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.ant-input-number-group > .ant-input-number-affix-wrapper:not(:last-child) .ant-input-number { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.ant-input-number-group-addon:first-child { - border-right: 0; -} - -.ant-input-number-group-addon:last-child { - border-left: 0; -} - -.ant-input-number-group > .ant-input-number:last-child, -.ant-input-number-group-addon:last-child { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.ant-input-number-group > .ant-input-number:last-child .ant-select .ant-select-selector, -.ant-input-number-group-addon:last-child .ant-select .ant-select-selector { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.ant-input-number-group-lg .ant-input-number, -.ant-input-number-group-lg > .ant-input-number-group-addon { - padding: 6.5px 11px; - font-size: 16px; -} - -.ant-input-number-group-sm .ant-input-number, -.ant-input-number-group-sm > .ant-input-number-group-addon { - padding: 0px 7px; -} - -.ant-input-number-group-lg .ant-select-single .ant-select-selector { - height: 40px; -} - -.ant-input-number-group-sm .ant-select-single .ant-select-selector { - height: 24px; -} - -.ant-input-number-group .ant-input-number-affix-wrapper:not(:last-child) { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.ant-input-search .ant-input-number-group .ant-input-number-affix-wrapper:not(:last-child) { - border-top-left-radius: 2px; - border-bottom-left-radius: 2px; -} - -.ant-input-number-group .ant-input-number-affix-wrapper:not(:first-child), -.ant-input-search .ant-input-number-group .ant-input-number-affix-wrapper:not(:first-child) { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.ant-input-number-group.ant-input-number-group-compact { - display: block; -} - -.ant-input-number-group.ant-input-number-group-compact::before { - display: table; - content: ''; -} - -.ant-input-number-group.ant-input-number-group-compact::after { - display: table; - clear: both; - content: ''; -} - -.ant-input-number-group.ant-input-number-group-compact-addon:not(:first-child):not(:last-child), -.ant-input-number-group.ant-input-number-group-compact-wrap:not(:first-child):not(:last-child), -.ant-input-number-group.ant-input-number-group-compact > .ant-input-number:not(:first-child):not(:last-child) { - border-right-width: 1px; -} - -.ant-input-number-group.ant-input-number-group-compact-addon:not(:first-child):not(:last-child):hover, -.ant-input-number-group.ant-input-number-group-compact-wrap:not(:first-child):not(:last-child):hover, -.ant-input-number-group.ant-input-number-group-compact > .ant-input-number:not(:first-child):not(:last-child):hover { - z-index: 1; -} - -.ant-input-number-group.ant-input-number-group-compact-addon:not(:first-child):not(:last-child):focus, -.ant-input-number-group.ant-input-number-group-compact-wrap:not(:first-child):not(:last-child):focus, -.ant-input-number-group.ant-input-number-group-compact > .ant-input-number:not(:first-child):not(:last-child):focus { - z-index: 1; -} - -.ant-input-number-group.ant-input-number-group-compact > * { - display: inline-block; - float: none; - vertical-align: top; - border-radius: 0; -} - -.ant-input-number-group.ant-input-number-group-compact > .ant-input-number-affix-wrapper { - display: inline-flex; -} - -.ant-input-number-group.ant-input-number-group-compact > .ant-picker-range { - display: inline-flex; -} - -.ant-input-number-group.ant-input-number-group-compact > *:not(:last-child) { - margin-right: -1px; - border-right-width: 1px; -} - -.ant-input-number-group.ant-input-number-group-compact .ant-input-number { - float: none; -} - -.ant-input-number-group.ant-input-number-group-compact > .ant-select > .ant-select-selector, -.ant-input-number-group.ant-input-number-group-compact > .ant-select-auto-complete .ant-input, -.ant-input-number-group.ant-input-number-group-compact > .ant-cascader-picker .ant-input, -.ant-input-number-group.ant-input-number-group-compact > .ant-input-group-wrapper .ant-input { - border-right-width: 1px; - border-radius: 0; -} - -.ant-input-number-group.ant-input-number-group-compact > .ant-select > .ant-select-selector:hover, -.ant-input-number-group.ant-input-number-group-compact > .ant-select-auto-complete .ant-input:hover, -.ant-input-number-group.ant-input-number-group-compact > .ant-cascader-picker .ant-input:hover, -.ant-input-number-group.ant-input-number-group-compact > .ant-input-group-wrapper .ant-input:hover { - z-index: 1; -} - -.ant-input-number-group.ant-input-number-group-compact > .ant-select > .ant-select-selector:focus, -.ant-input-number-group.ant-input-number-group-compact > .ant-select-auto-complete .ant-input:focus, -.ant-input-number-group.ant-input-number-group-compact > .ant-cascader-picker .ant-input:focus, -.ant-input-number-group.ant-input-number-group-compact > .ant-input-group-wrapper .ant-input:focus { - z-index: 1; -} - -.ant-input-number-group.ant-input-number-group-compact > .ant-select-focused { - z-index: 1; -} - -.ant-input-number-group.ant-input-number-group-compact > .ant-select > .ant-select-arrow { - z-index: 1; -} - -.ant-input-number-group.ant-input-number-group-compact > *:first-child, -.ant-input-number-group.ant-input-number-group-compact > .ant-select:first-child > .ant-select-selector, -.ant-input-number-group.ant-input-number-group-compact > .ant-select-auto-complete:first-child .ant-input, -.ant-input-number-group.ant-input-number-group-compact > .ant-cascader-picker:first-child .ant-input { - border-top-left-radius: 2px; - border-bottom-left-radius: 2px; -} - -.ant-input-number-group.ant-input-number-group-compact > *:last-child, -.ant-input-number-group.ant-input-number-group-compact > .ant-select:last-child > .ant-select-selector, -.ant-input-number-group.ant-input-number-group-compact > .ant-cascader-picker:last-child .ant-input, -.ant-input-number-group.ant-input-number-group-compact > .ant-cascader-picker-focused:last-child .ant-input { - border-right-width: 1px; - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; -} - -.ant-input-number-group.ant-input-number-group-compact > .ant-select-auto-complete .ant-input { - vertical-align: top; -} - -.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper + .ant-input-group-wrapper { - margin-left: -1px; -} - -.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper + .ant-input-group-wrapper .ant-input-affix-wrapper { - border-radius: 0; -} - -.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper:not(:last-child).ant-input-search > .ant-input-group > .ant-input-group-addon > .ant-input-search-button { - border-radius: 0; -} - -.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper:not(:last-child).ant-input-search > .ant-input-group > .ant-input { - border-radius: 2px 0 0 2px; -} - -.ant-input-number-group-wrapper { - display: inline-block; - text-align: start; - vertical-align: top; -} - -.ant-input-number-handler { - position: relative; - display: block; - width: 100%; - height: 50%; - overflow: hidden; - color: rgba(0, 0, 0, 0.45); - font-weight: bold; - line-height: 0; - text-align: center; - border-left: 1px solid #d9d9d9; - transition: all 0.1s linear; -} - -.ant-input-number-handler:active { - background: #f4f4f4; -} - -.ant-input-number-handler:hover .ant-input-number-handler-up-inner, -.ant-input-number-handler:hover .ant-input-number-handler-down-inner { - color: #40a9ff; -} - -.ant-input-number-handler-up-inner, -.ant-input-number-handler-down-inner { - display: inline-block; - color: inherit; - font-style: normal; - line-height: 0; - text-align: center; - text-transform: none; - vertical-align: -0.125em; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - position: absolute; - right: 4px; - width: 12px; - height: 12px; - color: rgba(0, 0, 0, 0.45); - line-height: 12px; - transition: all 0.1s linear; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-input-number-handler-up-inner > *, -.ant-input-number-handler-down-inner > * { - line-height: 1; -} - -.ant-input-number-handler-up-inner svg, -.ant-input-number-handler-down-inner svg { - display: inline-block; -} - -.ant-input-number-handler-up-inner::before, -.ant-input-number-handler-down-inner::before { - display: none; -} - -.ant-input-number-handler-up-inner .ant-input-number-handler-up-inner-icon, -.ant-input-number-handler-up-inner .ant-input-number-handler-down-inner-icon, -.ant-input-number-handler-down-inner .ant-input-number-handler-up-inner-icon, -.ant-input-number-handler-down-inner .ant-input-number-handler-down-inner-icon { - display: block; -} - -.ant-input-number:hover { - border-color: #40a9ff; - border-right-width: 1px !important; -} - -.ant-input-number:hover + .ant-form-item-children-icon { - opacity: 0; - transition: opacity 0.24s linear 0.24s; -} - -.ant-input-number-focused { - border-color: #40a9ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-input-number-disabled { - color: rgba(0, 0, 0, 0.25); - background-color: #f5f5f5; - border-color: #d9d9d9; - box-shadow: none; - cursor: not-allowed; - opacity: 1; -} - -.ant-input-number-disabled:hover { - border-color: #d9d9d9; - border-right-width: 1px !important; -} - -.ant-input-number-disabled .ant-input-number-input { - cursor: not-allowed; -} - -.ant-input-number-disabled .ant-input-number-handler-wrap { - display: none; -} - -.ant-input-number-readonly .ant-input-number-handler-wrap { - display: none; -} - -.ant-input-number-input { - width: 100%; - height: 30px; - padding: 0 11px; - text-align: left; - background-color: transparent; - border: 0; - border-radius: 2px; - outline: 0; - transition: all 0.3s linear; - -webkit-appearance: textfield !important; - -moz-appearance: textfield !important; - appearance: textfield !important; -} - -.ant-input-number-input::-moz-placeholder { - opacity: 1; -} - -.ant-input-number-input:-ms-input-placeholder { - color: #bfbfbf; - -ms-user-select: none; - user-select: none; -} - -.ant-input-number-input::placeholder { - color: #bfbfbf; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-input-number-input:-moz-placeholder-shown { - text-overflow: ellipsis; -} - -.ant-input-number-input:-ms-input-placeholder { - text-overflow: ellipsis; -} - -.ant-input-number-input:placeholder-shown { - text-overflow: ellipsis; -} - -.ant-input-number-input[type='number']::-webkit-inner-spin-button, -.ant-input-number-input[type='number']::-webkit-outer-spin-button { - margin: 0; - -webkit-appearance: none; - appearance: none; -} - -.ant-input-number-lg { - padding: 0; - font-size: 16px; -} - -.ant-input-number-lg input { - height: 38px; -} - -.ant-input-number-sm { - padding: 0; -} - -.ant-input-number-sm input { - height: 22px; - padding: 0 7px; -} - -.ant-input-number-handler-wrap { - position: absolute; - top: 0; - right: 0; - width: 22px; - height: 100%; - background: #fff; - border-radius: 0 2px 2px 0; - opacity: 0; - transition: opacity 0.24s linear 0.1s; -} - -.ant-input-number-handler-wrap .ant-input-number-handler .ant-input-number-handler-up-inner, -.ant-input-number-handler-wrap .ant-input-number-handler .ant-input-number-handler-down-inner { - display: flex; - align-items: center; - justify-content: center; - min-width: auto; - margin-right: 0; - font-size: 7px; -} - -.ant-input-number-borderless .ant-input-number-handler-wrap { - border-left-width: 0; -} - -.ant-input-number-handler-wrap:hover .ant-input-number-handler { - height: 40%; -} - -.ant-input-number:hover .ant-input-number-handler-wrap, -.ant-input-number-focused .ant-input-number-handler-wrap { - opacity: 1; -} - -.ant-input-number-handler-up { - border-top-right-radius: 2px; - cursor: pointer; -} - -.ant-input-number-handler-up-inner { - top: 50%; - margin-top: -5px; - text-align: center; -} - -.ant-input-number-handler-up:hover { - height: 60% !important; -} - -.ant-input-number-handler-down { - top: 0; - border-top: 1px solid #d9d9d9; - border-bottom-right-radius: 2px; - cursor: pointer; -} - -.ant-input-number-handler-down-inner { - top: 50%; - text-align: center; - transform: translateY(-50%); -} - -.ant-input-number-handler-down:hover { - height: 60% !important; -} - -.ant-input-number-borderless .ant-input-number-handler-down { - border-top-width: 0; -} - -.ant-input-number-handler-up-disabled, -.ant-input-number-handler-down-disabled { - cursor: not-allowed; -} - -.ant-input-number-handler-up-disabled:hover .ant-input-number-handler-up-inner, -.ant-input-number-handler-down-disabled:hover .ant-input-number-handler-down-inner { - color: rgba(0, 0, 0, 0.25); -} - -.ant-input-number-borderless { - box-shadow: none; -} - -.ant-input-number-out-of-range input { - color: #ff4d4f; -} - -.ant-input-affix-wrapper { - position: relative; - display: inline-block; - width: 100%; - min-width: 0; - padding: 4px 11px; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - line-height: 1.5715; - background-color: #fff; - background-image: none; - border: 1px solid #d9d9d9; - border-radius: 2px; - transition: all 0.3s; - display: inline-flex; -} - -.ant-input-affix-wrapper::-moz-placeholder { - opacity: 1; -} - -.ant-input-affix-wrapper:-ms-input-placeholder { - color: #bfbfbf; - -ms-user-select: none; - user-select: none; -} - -.ant-input-affix-wrapper::placeholder { - color: #bfbfbf; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-input-affix-wrapper:-moz-placeholder-shown { - text-overflow: ellipsis; -} - -.ant-input-affix-wrapper:-ms-input-placeholder { - text-overflow: ellipsis; -} - -.ant-input-affix-wrapper:placeholder-shown { - text-overflow: ellipsis; -} - -.ant-input-affix-wrapper:hover { - border-color: #40a9ff; - border-right-width: 1px !important; -} - -.ant-input-affix-wrapper:focus, -.ant-input-affix-wrapper-focused { - border-color: #40a9ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-input-affix-wrapper-disabled { - color: rgba(0, 0, 0, 0.25); - background-color: #f5f5f5; - border-color: #d9d9d9; - box-shadow: none; - cursor: not-allowed; - opacity: 1; -} - -.ant-input-affix-wrapper-disabled:hover { - border-color: #d9d9d9; - border-right-width: 1px !important; -} - -.ant-input-affix-wrapper[disabled] { - color: rgba(0, 0, 0, 0.25); - background-color: #f5f5f5; - border-color: #d9d9d9; - box-shadow: none; - cursor: not-allowed; - opacity: 1; -} - -.ant-input-affix-wrapper[disabled]:hover { - border-color: #d9d9d9; - border-right-width: 1px !important; -} - -.ant-input-affix-wrapper-borderless, -.ant-input-affix-wrapper-borderless:hover, -.ant-input-affix-wrapper-borderless:focus, -.ant-input-affix-wrapper-borderless-focused, -.ant-input-affix-wrapper-borderless-disabled, -.ant-input-affix-wrapper-borderless[disabled] { - background-color: transparent; - border: none; - box-shadow: none; -} - -textarea.ant-input-affix-wrapper { - max-width: 100%; - height: auto; - min-height: 32px; - line-height: 1.5715; - vertical-align: bottom; - transition: all 0.3s, height 0s; -} - -.ant-input-affix-wrapper-lg { - padding: 6.5px 11px; - font-size: 16px; -} - -.ant-input-affix-wrapper-sm { - padding: 0px 7px; -} - -.ant-input-affix-wrapper:not(.ant-input-affix-wrapper-disabled):hover { - border-color: #40a9ff; - border-right-width: 1px !important; - z-index: 1; -} - -.ant-input-search-with-button .ant-input-affix-wrapper:not(.ant-input-affix-wrapper-disabled):hover { - z-index: 0; -} - -.ant-input-affix-wrapper-focused, -.ant-input-affix-wrapper:focus { - z-index: 1; -} - -.ant-input-affix-wrapper-disabled .ant-input[disabled] { - background: transparent; -} - -.ant-input-affix-wrapper > input.ant-input { - padding: 0; - border: none; - outline: none; -} - -.ant-input-affix-wrapper > input.ant-input:focus { - box-shadow: none !important; -} - -.ant-input-affix-wrapper::before { - width: 0; - visibility: hidden; - content: '\a0'; -} - -.ant-input-prefix, -.ant-input-suffix { - display: flex; - flex: none; - align-items: center; -} - -.ant-input-prefix { - margin-right: 4px; -} - -.ant-input-suffix { - margin-left: 4px; -} - -.anticon.ant-input-clear-icon { - margin: 0; - color: rgba(0, 0, 0, 0.25); - font-size: 12px; - vertical-align: -1px; - cursor: pointer; - transition: color 0.3s; -} - -.anticon.ant-input-clear-icon:hover { - color: rgba(0, 0, 0, 0.45); -} - -.anticon.ant-input-clear-icon:active { - color: rgba(0, 0, 0, 0.85); -} - -.anticon.ant-input-clear-icon-hidden { - visibility: hidden; -} - -.anticon.ant-input-clear-icon-has-suffix { - margin: 0 4px; -} - -.ant-input-affix-wrapper-textarea-with-clear-btn { - padding: 0 !important; - border: 0 !important; -} - -.ant-input-affix-wrapper-textarea-with-clear-btn .ant-input-clear-icon { - position: absolute; - top: 8px; - right: 8px; - z-index: 1; -} - -.ant-input { - box-sizing: border-box; - margin: 0; - padding: 0; - font-variant: tabular-nums; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - display: inline-block; - width: 100%; - min-width: 0; - padding: 4px 11px; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - line-height: 1.5715; - background-color: #fff; - background-image: none; - border: 1px solid #d9d9d9; - border-radius: 2px; - transition: all 0.3s; -} - -.ant-input::-moz-placeholder { - opacity: 1; -} - -.ant-input:-ms-input-placeholder { - color: #bfbfbf; - -ms-user-select: none; - user-select: none; -} - -.ant-input::placeholder { - color: #bfbfbf; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-input:-moz-placeholder-shown { - text-overflow: ellipsis; -} - -.ant-input:-ms-input-placeholder { - text-overflow: ellipsis; -} - -.ant-input:placeholder-shown { - text-overflow: ellipsis; -} - -.ant-input:hover { - border-color: #40a9ff; - border-right-width: 1px !important; -} - - -.ant-input:focus, -.ant-input-focused { - border-color: #40a9ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-input-disabled { - color: rgba(0, 0, 0, 0.25); - background-color: #f5f5f5; - border-color: #d9d9d9; - box-shadow: none; - cursor: not-allowed; - opacity: 1; -} - -.ant-input-disabled:hover { - border-color: #d9d9d9; - border-right-width: 1px !important; -} - -.ant-input[disabled] { - color: rgba(0, 0, 0, 0.25); - background-color: #f5f5f5; - border-color: #d9d9d9; - box-shadow: none; - cursor: not-allowed; - opacity: 1; -} - -.ant-input[disabled]:hover { - border-color: #d9d9d9; - border-right-width: 1px !important; -} - -.ant-input-borderless, -.ant-input-borderless:hover, -.ant-input-borderless:focus, -.ant-input-borderless-focused, -.ant-input-borderless-disabled, -.ant-input-borderless[disabled] { - background-color: transparent; - border: none; - box-shadow: none; -} - -textarea.ant-input { - max-width: 100%; - height: auto; - min-height: 32px; - line-height: 1.5715; - vertical-align: bottom; - transition: all 0.3s, height 0s; -} - -.ant-input-lg { - padding: 6.5px 11px; - font-size: 16px; -} - -.ant-input-sm { - padding: 0px 7px; -} - -.ant-input-group { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - display: table; - width: 100%; - border-collapse: separate; - border-spacing: 0; -} - -.ant-input-group[class*='col-'] { - float: none; - padding-right: 0; - padding-left: 0; -} - -.ant-input-group > [class*='col-'] { - padding-right: 8px; -} - -.ant-input-group > [class*='col-']:last-child { - padding-right: 0; -} - -.ant-input-group-addon, -.ant-input-group-wrap, -.ant-input-group > .ant-input { - display: table-cell; -} - -.ant-input-group-addon:not(:first-child):not(:last-child), -.ant-input-group-wrap:not(:first-child):not(:last-child), -.ant-input-group > .ant-input:not(:first-child):not(:last-child) { - border-radius: 0; -} - -.ant-input-group-addon, -.ant-input-group-wrap { - width: 1px; - white-space: nowrap; - vertical-align: middle; -} - -.ant-input-group-wrap > * { - display: block !important; -} - -.ant-input-group .ant-input { - float: left; - width: 100%; - margin-bottom: 0; - text-align: inherit; -} - -.ant-input-group .ant-input:focus { - z-index: 1; - border-right-width: 1px; -} - -.ant-input-group .ant-input:hover { - z-index: 1; - border-right-width: 1px; -} - -.ant-input-search-with-button .ant-input-group .ant-input:hover { - z-index: 0; -} - -.ant-input-group-addon { - position: relative; - padding: 0 11px; - color: rgba(0, 0, 0, 0.85); - font-weight: normal; - font-size: 14px; - text-align: center; - background-color: #fafafa; - border: 1px solid #d9d9d9; - border-radius: 2px; - transition: all 0.3s; -} - -.ant-input-group-addon .ant-select { - margin: -5px -11px; -} - -.ant-input-group-addon .ant-select.ant-select-single:not(.ant-select-customize-input) .ant-select-selector { - background-color: inherit; - border: 1px solid transparent; - box-shadow: none; -} - -.ant-input-group-addon .ant-select-open .ant-select-selector, -.ant-input-group-addon .ant-select-focused .ant-select-selector { - color: #1890ff; -} - -.ant-input-group-addon .ant-cascader-picker { - margin: -9px -12px; - background-color: transparent; -} - -.ant-input-group-addon .ant-cascader-picker .ant-cascader-input { - text-align: left; - border: 0; - box-shadow: none; -} - -.ant-input-group > .ant-input:first-child, -.ant-input-group-addon:first-child { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.ant-input-group > .ant-input:first-child .ant-select .ant-select-selector, -.ant-input-group-addon:first-child .ant-select .ant-select-selector { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.ant-input-group > .ant-input-affix-wrapper:not(:first-child) .ant-input { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.ant-input-group > .ant-input-affix-wrapper:not(:last-child) .ant-input { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.ant-input-group-addon:first-child { - border-right: 0; -} - -.ant-input-group-addon:last-child { - border-left: 0; -} - -.ant-input-group > .ant-input:last-child, -.ant-input-group-addon:last-child { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.ant-input-group > .ant-input:last-child .ant-select .ant-select-selector, -.ant-input-group-addon:last-child .ant-select .ant-select-selector { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.ant-input-group-lg .ant-input, -.ant-input-group-lg > .ant-input-group-addon { - padding: 6.5px 11px; - font-size: 16px; -} - -.ant-input-group-sm .ant-input, -.ant-input-group-sm > .ant-input-group-addon { - padding: 0px 7px; -} - -.ant-input-group-lg .ant-select-single .ant-select-selector { - height: 40px; -} - -.ant-input-group-sm .ant-select-single .ant-select-selector { - height: 24px; -} - -.ant-input-group .ant-input-affix-wrapper:not(:last-child) { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.ant-input-search .ant-input-group .ant-input-affix-wrapper:not(:last-child) { - border-top-left-radius: 2px; - border-bottom-left-radius: 2px; -} - -.ant-input-group .ant-input-affix-wrapper:not(:first-child), -.ant-input-search .ant-input-group .ant-input-affix-wrapper:not(:first-child) { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.ant-input-group.ant-input-group-compact { - display: block; -} - -.ant-input-group.ant-input-group-compact::before { - display: table; - content: ''; -} - -.ant-input-group.ant-input-group-compact::after { - display: table; - clear: both; - content: ''; -} - -.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(:last-child), -.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(:last-child), -.ant-input-group.ant-input-group-compact > .ant-input:not(:first-child):not(:last-child) { - border-right-width: 1px; -} - -.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(:last-child):hover, -.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(:last-child):hover, -.ant-input-group.ant-input-group-compact > .ant-input:not(:first-child):not(:last-child):hover { - z-index: 1; -} - -.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(:last-child):focus, -.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(:last-child):focus, -.ant-input-group.ant-input-group-compact > .ant-input:not(:first-child):not(:last-child):focus { - z-index: 1; -} - -.ant-input-group.ant-input-group-compact > * { - display: inline-block; - float: none; - vertical-align: top; - border-radius: 0; -} - -.ant-input-group.ant-input-group-compact > .ant-input-affix-wrapper { - display: inline-flex; -} - -.ant-input-group.ant-input-group-compact > .ant-picker-range { - display: inline-flex; -} - -.ant-input-group.ant-input-group-compact > *:not(:last-child) { - margin-right: -1px; - border-right-width: 1px; -} - -.ant-input-group.ant-input-group-compact .ant-input { - float: none; -} - -.ant-input-group.ant-input-group-compact > .ant-select > .ant-select-selector, -.ant-input-group.ant-input-group-compact > .ant-select-auto-complete .ant-input, -.ant-input-group.ant-input-group-compact > .ant-cascader-picker .ant-input, -.ant-input-group.ant-input-group-compact > .ant-input-group-wrapper .ant-input { - border-right-width: 1px; - border-radius: 0; -} - -.ant-input-group.ant-input-group-compact > .ant-select > .ant-select-selector:hover, -.ant-input-group.ant-input-group-compact > .ant-select-auto-complete .ant-input:hover, -.ant-input-group.ant-input-group-compact > .ant-cascader-picker .ant-input:hover, -.ant-input-group.ant-input-group-compact > .ant-input-group-wrapper .ant-input:hover { - z-index: 1; -} - -.ant-input-group.ant-input-group-compact > .ant-select > .ant-select-selector:focus, -.ant-input-group.ant-input-group-compact > .ant-select-auto-complete .ant-input:focus, -.ant-input-group.ant-input-group-compact > .ant-cascader-picker .ant-input:focus, -.ant-input-group.ant-input-group-compact > .ant-input-group-wrapper .ant-input:focus { - z-index: 1; -} - -.ant-input-group.ant-input-group-compact > .ant-select-focused { - z-index: 1; -} - -.ant-input-group.ant-input-group-compact > .ant-select > .ant-select-arrow { - z-index: 1; -} - -.ant-input-group.ant-input-group-compact > *:first-child, -.ant-input-group.ant-input-group-compact > .ant-select:first-child > .ant-select-selector, -.ant-input-group.ant-input-group-compact > .ant-select-auto-complete:first-child .ant-input, -.ant-input-group.ant-input-group-compact > .ant-cascader-picker:first-child .ant-input { - border-top-left-radius: 2px; - border-bottom-left-radius: 2px; -} - -.ant-input-group.ant-input-group-compact > *:last-child, -.ant-input-group.ant-input-group-compact > .ant-select:last-child > .ant-select-selector, -.ant-input-group.ant-input-group-compact > .ant-cascader-picker:last-child .ant-input, -.ant-input-group.ant-input-group-compact > .ant-cascader-picker-focused:last-child .ant-input { - border-right-width: 1px; - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; -} - -.ant-input-group.ant-input-group-compact > .ant-select-auto-complete .ant-input { - vertical-align: top; -} - -.ant-input-group.ant-input-group-compact .ant-input-group-wrapper + .ant-input-group-wrapper { - margin-left: -1px; -} - -.ant-input-group.ant-input-group-compact .ant-input-group-wrapper + .ant-input-group-wrapper .ant-input-affix-wrapper { - border-radius: 0; -} - -.ant-input-group.ant-input-group-compact .ant-input-group-wrapper:not(:last-child).ant-input-search > .ant-input-group > .ant-input-group-addon > .ant-input-search-button { - border-radius: 0; -} - -.ant-input-group.ant-input-group-compact .ant-input-group-wrapper:not(:last-child).ant-input-search > .ant-input-group > .ant-input { - border-radius: 2px 0 0 2px; -} - -.ant-input-group-wrapper { - display: inline-block; - width: 100%; - text-align: start; - vertical-align: top; -} - -.ant-input-password-icon { - color: rgba(0, 0, 0, 0.45); - cursor: pointer; - transition: all 0.3s; -} - -.ant-input-password-icon:hover { - color: rgba(0, 0, 0, 0.85); -} - -.ant-input[type='color'] { - height: 32px; -} - -.ant-input[type='color'].ant-input-lg { - height: 40px; -} - -.ant-input[type='color'].ant-input-sm { - height: 24px; - padding-top: 3px; - padding-bottom: 3px; -} - -.ant-input-textarea-show-count > .ant-input { - height: 100%; -} - -.ant-input-textarea-show-count::after { - float: right; - color: rgba(0, 0, 0, 0.45); - white-space: nowrap; - content: attr(data-count); - pointer-events: none; -} - -.ant-input-search .ant-input:hover, -.ant-input-search .ant-input:focus { - border-color: #40a9ff; -} - -.ant-input-search .ant-input:hover + .ant-input-group-addon .ant-input-search-button:not(.ant-btn-primary), -.ant-input-search .ant-input:focus + .ant-input-group-addon .ant-input-search-button:not(.ant-btn-primary) { - border-left-color: #40a9ff; -} - -.ant-input-search .ant-input-affix-wrapper { - border-radius: 0; -} - -.ant-input-search .ant-input-lg { - line-height: 1.5713; -} - -.ant-input-search > .ant-input-group > .ant-input-group-addon:last-child { - left: -1px; - padding: 0; - border: 0; -} - -.ant-input-search > .ant-input-group > .ant-input-group-addon:last-child .ant-input-search-button { - padding-top: 0; - padding-bottom: 0; - border-radius: 0 2px 2px 0; -} - -.ant-input-search > .ant-input-group > .ant-input-group-addon:last-child .ant-input-search-button:not(.ant-btn-primary) { - color: rgba(0, 0, 0, 0.45); -} - -.ant-input-search > .ant-input-group > .ant-input-group-addon:last-child .ant-input-search-button:not(.ant-btn-primary).ant-btn-loading::before { - top: 0; - right: 0; - bottom: 0; - left: 0; -} - -.ant-input-search-button { - height: 32px; -} - -.ant-input-search-button:hover, -.ant-input-search-button:focus { - z-index: 1; -} - -.ant-input-search-large .ant-input-search-button { - height: 40px; -} - -.ant-input-search-small .ant-input-search-button { - height: 24px; -} - -@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { - .ant-input { - height: 32px; - } - .ant-input-lg { - height: 40px; - } - .ant-input-sm { - height: 24px; - } - .ant-input-affix-wrapper > input.ant-input { - height: auto; - } -} - -.ant-layout { - display: flex; - flex: auto; - flex-direction: column; - /* fix firefox can't set height smaller than content on flex item */ - min-height: 0; - background: #f0f2f5; -} - -.ant-layout, -.ant-layout * { - box-sizing: border-box; -} - -.ant-layout.ant-layout-has-sider { - flex-direction: row; -} - -.ant-layout.ant-layout-has-sider > .ant-layout, -.ant-layout.ant-layout-has-sider > .ant-layout-content { - width: 0; -} - -.ant-layout-header, -.ant-layout-footer { - flex: 0 0 auto; -} - -.ant-layout-header { - height: 64px; - padding: 0 50px; - color: rgba(0, 0, 0, 0.85); - line-height: 64px; - background: #001529; -} - -.ant-layout-footer { - padding: 24px 50px; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - background: #f0f2f5; -} - -.ant-layout-content { - flex: auto; - /* fix firefox can't set height smaller than content on flex item */ - min-height: 0; -} - -.ant-layout-sider { - position: relative; - /* fix firefox can't set width smaller than content on flex item */ - min-width: 0; - background: #001529; - transition: all 0.2s; -} - -.ant-layout-sider-children { - height: 100%; - margin-top: -0.1px; - padding-top: 0.1px; -} - -.ant-layout-sider-children .ant-menu.ant-menu-inline-collapsed { - width: auto; -} - -.ant-layout-sider-has-trigger { - padding-bottom: 48px; -} - -.ant-layout-sider-right { - order: 1; -} - -.ant-layout-sider-trigger { - position: fixed; - bottom: 0; - z-index: 1; - height: 48px; - color: #fff; - line-height: 48px; - text-align: center; - background: #002140; - cursor: pointer; - transition: all 0.2s; -} - -.ant-layout-sider-zero-width > * { - overflow: hidden; -} - -.ant-layout-sider-zero-width-trigger { - position: absolute; - top: 64px; - right: -36px; - z-index: 1; - width: 36px; - height: 42px; - color: #fff; - font-size: 18px; - line-height: 42px; - text-align: center; - background: #001529; - border-radius: 0 2px 2px 0; - cursor: pointer; - transition: background 0.3s ease; -} - -.ant-layout-sider-zero-width-trigger::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent; - transition: all 0.3s; - content: ''; -} - -.ant-layout-sider-zero-width-trigger:hover::after { - background: rgba(255, 255, 255, 0.1); -} - -.ant-layout-sider-zero-width-trigger-right { - left: -36px; - border-radius: 2px 0 0 2px; -} - -.ant-layout-sider-light { - background: #fff; -} - -.ant-layout-sider-light .ant-layout-sider-trigger { - color: rgba(0, 0, 0, 0.85); - background: #fff; -} - -.ant-layout-sider-light .ant-layout-sider-zero-width-trigger { - color: rgba(0, 0, 0, 0.85); - background: #fff; -} - -.ant-list { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; -} - -.ant-list * { - outline: none; -} - -.ant-list-pagination { - margin-top: 24px; - text-align: right; -} - -.ant-list-pagination .ant-pagination-options { - text-align: left; -} - -.ant-list-more { - margin-top: 12px; - text-align: center; -} - -.ant-list-more button { - padding-right: 32px; - padding-left: 32px; -} - -.ant-list-spin { - min-height: 40px; - text-align: center; -} - -.ant-list-empty-text { - padding: 16px; - color: rgba(0, 0, 0, 0.25); - font-size: 14px; - text-align: center; -} - -.ant-list-items { - margin: 0; - padding: 0; - list-style: none; -} - -.ant-list-item { - display: flex; - align-items: center; - justify-content: space-between; - padding: 12px 0; - color: rgba(0, 0, 0, 0.85); -} - -.ant-list-item-meta { - display: flex; - flex: 1; - align-items: flex-start; - max-width: 100%; -} - -.ant-list-item-meta-avatar { - margin-right: 16px; -} - -.ant-list-item-meta-content { - flex: 1 0; - width: 0; - color: rgba(0, 0, 0, 0.85); -} - -.ant-list-item-meta-title { - margin-bottom: 4px; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - line-height: 1.5715; -} - -.ant-list-item-meta-title > a { - color: rgba(0, 0, 0, 0.85); - transition: all 0.3s; -} - -.ant-list-item-meta-title > a:hover { - color: #1890ff; -} - -.ant-list-item-meta-description { - color: rgba(0, 0, 0, 0.45); - font-size: 14px; - line-height: 1.5715; -} - -.ant-list-item-action { - flex: 0 0 auto; - margin-left: 48px; - padding: 0; - font-size: 0; - list-style: none; -} - -.ant-list-item-action > li { - position: relative; - display: inline-block; - padding: 0 8px; - color: rgba(0, 0, 0, 0.45); - font-size: 14px; - line-height: 1.5715; - text-align: center; -} - -.ant-list-item-action > li:first-child { - padding-left: 0; -} - -.ant-list-item-action-split { - position: absolute; - top: 50%; - right: 0; - width: 1px; - height: 14px; - margin-top: -7px; - background-color: #f0f0f0; -} - -.ant-list-header { - background: transparent; -} - -.ant-list-footer { - background: transparent; -} - -.ant-list-header, -.ant-list-footer { - padding-top: 12px; - padding-bottom: 12px; -} - -.ant-list-empty { - padding: 16px 0; - color: rgba(0, 0, 0, 0.45); - font-size: 12px; - text-align: center; -} - -.ant-list-split .ant-list-item { - border-bottom: 1px solid #f0f0f0; -} - -.ant-list-split .ant-list-item:last-child { - border-bottom: none; -} - -.ant-list-split .ant-list-header { - border-bottom: 1px solid #f0f0f0; -} - -.ant-list-split.ant-list-empty .ant-list-footer { - border-top: 1px solid #f0f0f0; -} - -.ant-list-loading .ant-list-spin-nested-loading { - min-height: 32px; -} - -.ant-list-split.ant-list-something-after-last-item .ant-spin-container > .ant-list-items > .ant-list-item:last-child { - border-bottom: 1px solid #f0f0f0; -} - -.ant-list-lg .ant-list-item { - padding: 16px 24px; -} - -.ant-list-sm .ant-list-item { - padding: 8px 16px; -} - -.ant-list-vertical .ant-list-item { - align-items: initial; -} - -.ant-list-vertical .ant-list-item-main { - display: block; - flex: 1; -} - -.ant-list-vertical .ant-list-item-extra { - margin-left: 40px; -} - -.ant-list-vertical .ant-list-item-meta { - margin-bottom: 16px; -} - -.ant-list-vertical .ant-list-item-meta-title { - margin-bottom: 12px; - color: rgba(0, 0, 0, 0.85); - font-size: 16px; - line-height: 24px; -} - -.ant-list-vertical .ant-list-item-action { - margin-top: 16px; - margin-left: auto; -} - -.ant-list-vertical .ant-list-item-action > li { - padding: 0 16px; -} - -.ant-list-vertical .ant-list-item-action > li:first-child { - padding-left: 0; -} - -.ant-list-grid .ant-col > .ant-list-item { - display: block; - max-width: 100%; - margin-bottom: 16px; - padding-top: 0; - padding-bottom: 0; - border-bottom: none; -} - -.ant-list-item-no-flex { - display: block; -} - -.ant-list:not(.ant-list-vertical) .ant-list-item-no-flex .ant-list-item-action { - float: right; -} - -.ant-list-bordered { - border: 1px solid #d9d9d9; - border-radius: 2px; -} - -.ant-list-bordered .ant-list-header { - padding-right: 24px; - padding-left: 24px; -} - -.ant-list-bordered .ant-list-footer { - padding-right: 24px; - padding-left: 24px; -} - -.ant-list-bordered .ant-list-item { - padding-right: 24px; - padding-left: 24px; -} - -.ant-list-bordered .ant-list-pagination { - margin: 16px 24px; -} - -.ant-list-bordered.ant-list-sm .ant-list-item { - padding: 8px 16px; -} - -.ant-list-bordered.ant-list-sm .ant-list-header, -.ant-list-bordered.ant-list-sm .ant-list-footer { - padding: 8px 16px; -} - -.ant-list-bordered.ant-list-lg .ant-list-item { - padding: 16px 24px; -} - -.ant-list-bordered.ant-list-lg .ant-list-header, -.ant-list-bordered.ant-list-lg .ant-list-footer { - padding: 16px 24px; -} - -@media screen and (max-width: 768px) { - .ant-list-item-action { - margin-left: 24px; - } - .ant-list-vertical .ant-list-item-extra { - margin-left: 24px; - } -} - -@media screen and (max-width: 576px) { - .ant-list-item { - flex-wrap: wrap; - } - .ant-list-item-action { - margin-left: 12px; - } - .ant-list-vertical .ant-list-item { - flex-wrap: wrap-reverse; - } - .ant-list-vertical .ant-list-item-main { - min-width: 220px; - } - .ant-list-vertical .ant-list-item-extra { - margin: auto auto 16px; - } -} - -.ant-spin { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: absolute; - display: none; - color: #1890ff; - text-align: center; - vertical-align: middle; - opacity: 0; - transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86); -} - -.ant-spin-spinning { - position: static; - display: inline-block; - opacity: 1; -} - -.ant-spin-nested-loading { - position: relative; -} - -.ant-spin-nested-loading > div > .ant-spin { - position: absolute; - top: 0; - left: 0; - z-index: 4; - display: block; - width: 100%; - height: 100%; - max-height: 400px; -} - -.ant-spin-nested-loading > div > .ant-spin .ant-spin-dot { - position: absolute; - top: 50%; - left: 50%; - margin: -10px; -} - -.ant-spin-nested-loading > div > .ant-spin .ant-spin-text { - position: absolute; - top: 50%; - width: 100%; - padding-top: 5px; - text-shadow: 0 1px 2px #fff; -} - -.ant-spin-nested-loading > div > .ant-spin.ant-spin-show-text .ant-spin-dot { - margin-top: -20px; -} - -.ant-spin-nested-loading > div > .ant-spin-sm .ant-spin-dot { - margin: -7px; -} - -.ant-spin-nested-loading > div > .ant-spin-sm .ant-spin-text { - padding-top: 2px; -} - -.ant-spin-nested-loading > div > .ant-spin-sm.ant-spin-show-text .ant-spin-dot { - margin-top: -17px; -} - -.ant-spin-nested-loading > div > .ant-spin-lg .ant-spin-dot { - margin: -16px; -} - -.ant-spin-nested-loading > div > .ant-spin-lg .ant-spin-text { - padding-top: 11px; -} - -.ant-spin-nested-loading > div > .ant-spin-lg.ant-spin-show-text .ant-spin-dot { - margin-top: -26px; -} - -.ant-spin-container { - position: relative; - transition: opacity 0.3s; -} - -.ant-spin-container::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 10; - display: none \9 -; - width: 100%; - height: 100%; - background: #fff; - opacity: 0; - transition: all 0.3s; - content: ''; - pointer-events: none; -} - -.ant-spin-blur { - clear: both; - opacity: 0.5; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - pointer-events: none; -} - -.ant-spin-blur::after { - opacity: 0.4; - pointer-events: auto; -} - -.ant-spin-tip { - color: rgba(0, 0, 0, 0.45); -} - -.ant-spin-dot { - position: relative; - display: inline-block; - font-size: 20px; - width: 1em; - height: 1em; -} - -.ant-spin-dot-item { - position: absolute; - display: block; - width: 9px; - height: 9px; - background-color: #1890ff; - border-radius: 100%; - transform: scale(0.75); - transform-origin: 50% 50%; - opacity: 0.3; - -webkit-animation: antSpinMove 1s infinite linear alternate; - animation: antSpinMove 1s infinite linear alternate; -} - -.ant-spin-dot-item:nth-child(1) { - top: 0; - left: 0; -} - -.ant-spin-dot-item:nth-child(2) { - top: 0; - right: 0; - -webkit-animation-delay: 0.4s; - animation-delay: 0.4s; -} - -.ant-spin-dot-item:nth-child(3) { - right: 0; - bottom: 0; - -webkit-animation-delay: 0.8s; - animation-delay: 0.8s; -} - -.ant-spin-dot-item:nth-child(4) { - bottom: 0; - left: 0; - -webkit-animation-delay: 1.2s; - animation-delay: 1.2s; -} - -.ant-spin-dot-spin { - transform: rotate(45deg); - -webkit-animation: antRotate 1.2s infinite linear; - animation: antRotate 1.2s infinite linear; -} - -.ant-spin-sm .ant-spin-dot { - font-size: 14px; -} - -.ant-spin-sm .ant-spin-dot i { - width: 6px; - height: 6px; -} - -.ant-spin-lg .ant-spin-dot { - font-size: 32px; -} - -.ant-spin-lg .ant-spin-dot i { - width: 14px; - height: 14px; -} - -.ant-spin.ant-spin-show-text .ant-spin-text { - display: block; -} - -@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { - /* IE10+ */ - .ant-spin-blur { - background: #fff; - opacity: 0.5; - } -} - -@-webkit-keyframes antSpinMove { - to { - opacity: 1; - } -} - -@keyframes antSpinMove { - to { - opacity: 1; - } -} - -@-webkit-keyframes antRotate { - to { - transform: rotate(405deg); - } -} - -@keyframes antRotate { - to { - transform: rotate(405deg); - } -} - -@-webkit-keyframes antRotateRtl { - to { - transform: rotate(-405deg); - } -} - -@keyframes antRotateRtl { - to { - transform: rotate(-405deg); - } -} - -.ant-pagination { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; -} - -.ant-pagination ul, -.ant-pagination ol { - margin: 0; - padding: 0; - list-style: none; -} - -.ant-pagination::after { - display: block; - clear: both; - height: 0; - overflow: hidden; - visibility: hidden; - content: ' '; -} - -.ant-pagination-total-text { - display: inline-block; - height: 32px; - margin-right: 8px; - line-height: 30px; - vertical-align: middle; -} - -.ant-pagination-item { - display: inline-block; - min-width: 32px; - height: 32px; - margin-right: 8px; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; - line-height: 30px; - text-align: center; - vertical-align: middle; - list-style: none; - background-color: #fff; - border: 1px solid #d9d9d9; - border-radius: 2px; - outline: 0; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-pagination-item a { - display: block; - padding: 0 6px; - color: rgba(0, 0, 0, 0.85); - transition: none; -} - -.ant-pagination-item a:hover { - text-decoration: none; -} - -.ant-pagination-item:focus-visible, -.ant-pagination-item:hover { - border-color: #1890ff; - transition: all 0.3s; -} - -.ant-pagination-item:focus-visible a, -.ant-pagination-item:hover a { - color: #1890ff; -} - -.ant-pagination-item-active { - font-weight: 500; - background: #fff; - border-color: #1890ff; -} - -.ant-pagination-item-active a { - color: #1890ff; -} - -.ant-pagination-item-active:focus-visible, -.ant-pagination-item-active:hover { - border-color: #40a9ff; -} - -.ant-pagination-item-active:focus-visible a, -.ant-pagination-item-active:hover a { - color: #40a9ff; -} - -.ant-pagination-jump-prev, -.ant-pagination-jump-next { - outline: 0; -} - -.ant-pagination-jump-prev .ant-pagination-item-container, -.ant-pagination-jump-next .ant-pagination-item-container { - position: relative; -} - -.ant-pagination-jump-prev .ant-pagination-item-container .ant-pagination-item-link-icon, -.ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-link-icon { - color: #1890ff; - font-size: 12px; - letter-spacing: -1px; - opacity: 0; - transition: all 0.2s; -} - -.ant-pagination-jump-prev .ant-pagination-item-container .ant-pagination-item-link-icon-svg, -.ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-link-icon-svg { - top: 0; - right: 0; - bottom: 0; - left: 0; - margin: auto; -} - -.ant-pagination-jump-prev .ant-pagination-item-container .ant-pagination-item-ellipsis, -.ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-ellipsis { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - display: block; - margin: auto; - color: rgba(0, 0, 0, 0.25); - font-family: Arial, Helvetica, sans-serif; - letter-spacing: 2px; - text-align: center; - text-indent: 0.13em; - opacity: 1; - transition: all 0.2s; -} - -.ant-pagination-jump-prev:focus-visible .ant-pagination-item-link-icon, -.ant-pagination-jump-next:focus-visible .ant-pagination-item-link-icon, -.ant-pagination-jump-prev:hover .ant-pagination-item-link-icon, -.ant-pagination-jump-next:hover .ant-pagination-item-link-icon { - opacity: 1; -} - -.ant-pagination-jump-prev:focus-visible .ant-pagination-item-ellipsis, -.ant-pagination-jump-next:focus-visible .ant-pagination-item-ellipsis, -.ant-pagination-jump-prev:hover .ant-pagination-item-ellipsis, -.ant-pagination-jump-next:hover .ant-pagination-item-ellipsis { - opacity: 0; -} - -.ant-pagination-prev, -.ant-pagination-jump-prev, -.ant-pagination-jump-next { - margin-right: 8px; -} - -.ant-pagination-prev, -.ant-pagination-next, -.ant-pagination-jump-prev, -.ant-pagination-jump-next { - display: inline-block; - min-width: 32px; - height: 32px; - color: rgba(0, 0, 0, 0.85); - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; - line-height: 32px; - text-align: center; - vertical-align: middle; - list-style: none; - border-radius: 2px; - cursor: pointer; - transition: all 0.3s; -} - -.ant-pagination-prev, -.ant-pagination-next { - font-family: Arial, Helvetica, sans-serif; - outline: 0; -} - -.ant-pagination-prev button, -.ant-pagination-next button { - color: rgba(0, 0, 0, 0.85); - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-pagination-prev:hover button, -.ant-pagination-next:hover button { - border-color: #40a9ff; -} - -.ant-pagination-prev .ant-pagination-item-link, -.ant-pagination-next .ant-pagination-item-link { - display: block; - width: 100%; - height: 100%; - padding: 0; - font-size: 12px; - text-align: center; - background-color: #fff; - border: 1px solid #d9d9d9; - border-radius: 2px; - outline: none; - transition: all 0.3s; -} - -.ant-pagination-prev:focus-visible .ant-pagination-item-link, -.ant-pagination-next:focus-visible .ant-pagination-item-link, -.ant-pagination-prev:hover .ant-pagination-item-link, -.ant-pagination-next:hover .ant-pagination-item-link { - color: #1890ff; - border-color: #1890ff; -} - -.ant-pagination-disabled, -.ant-pagination-disabled:hover, -.ant-pagination-disabled:focus-visible { - cursor: not-allowed; -} - -.ant-pagination-disabled .ant-pagination-item-link, -.ant-pagination-disabled:hover .ant-pagination-item-link, -.ant-pagination-disabled:focus-visible .ant-pagination-item-link { - color: rgba(0, 0, 0, 0.25); - border-color: #d9d9d9; - cursor: not-allowed; -} - -.ant-pagination-slash { - margin: 0 10px 0 5px; -} - -.ant-pagination-options { - display: inline-block; - margin-left: 16px; - vertical-align: middle; -} - -@media all and (-ms-high-contrast: none) { - .ant-pagination-options *::-ms-backdrop, - .ant-pagination-options { - vertical-align: top; - } -} - -.ant-pagination-options-size-changer.ant-select { - display: inline-block; - width: auto; -} - -.ant-pagination-options-quick-jumper { - display: inline-block; - height: 32px; - margin-left: 8px; - line-height: 32px; - vertical-align: top; -} - -.ant-pagination-options-quick-jumper input { - position: relative; - display: inline-block; - width: 100%; - min-width: 0; - padding: 4px 11px; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - line-height: 1.5715; - background-color: #fff; - background-image: none; - border: 1px solid #d9d9d9; - border-radius: 2px; - transition: all 0.3s; - width: 50px; - height: 32px; - margin: 0 8px; -} - -.ant-pagination-options-quick-jumper input::-moz-placeholder { - opacity: 1; -} - -.ant-pagination-options-quick-jumper input:-ms-input-placeholder { - color: #bfbfbf; - -ms-user-select: none; - user-select: none; -} - -.ant-pagination-options-quick-jumper input::placeholder { - color: #bfbfbf; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-pagination-options-quick-jumper input:-moz-placeholder-shown { - text-overflow: ellipsis; -} - -.ant-pagination-options-quick-jumper input:-ms-input-placeholder { - text-overflow: ellipsis; -} - -.ant-pagination-options-quick-jumper input:placeholder-shown { - text-overflow: ellipsis; -} - -.ant-pagination-options-quick-jumper input:hover { - border-color: #40a9ff; - border-right-width: 1px !important; -} - -.ant-pagination-options-quick-jumper input:focus, -.ant-pagination-options-quick-jumper input-focused { - border-color: #40a9ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-pagination-options-quick-jumper input-disabled { - color: rgba(0, 0, 0, 0.25); - background-color: #f5f5f5; - border-color: #d9d9d9; - box-shadow: none; - cursor: not-allowed; - opacity: 1; -} - -.ant-pagination-options-quick-jumper input-disabled:hover { - border-color: #d9d9d9; - border-right-width: 1px !important; -} - -.ant-pagination-options-quick-jumper input[disabled] { - color: rgba(0, 0, 0, 0.25); - background-color: #f5f5f5; - border-color: #d9d9d9; - box-shadow: none; - cursor: not-allowed; - opacity: 1; -} - -.ant-pagination-options-quick-jumper input[disabled]:hover { - border-color: #d9d9d9; - border-right-width: 1px !important; -} - -.ant-pagination-options-quick-jumper input-borderless, -.ant-pagination-options-quick-jumper input-borderless:hover, -.ant-pagination-options-quick-jumper input-borderless:focus, -.ant-pagination-options-quick-jumper input-borderless-focused, -.ant-pagination-options-quick-jumper input-borderless-disabled, -.ant-pagination-options-quick-jumper input-borderless[disabled] { - background-color: transparent; - border: none; - box-shadow: none; -} - -textarea.ant-pagination-options-quick-jumper input { - max-width: 100%; - height: auto; - min-height: 32px; - line-height: 1.5715; - vertical-align: bottom; - transition: all 0.3s, height 0s; -} - -.ant-pagination-options-quick-jumper input-lg { - padding: 6.5px 11px; - font-size: 16px; -} - -.ant-pagination-options-quick-jumper input-sm { - padding: 0px 7px; -} - -.ant-pagination-simple .ant-pagination-prev, -.ant-pagination-simple .ant-pagination-next { - height: 24px; - line-height: 24px; - vertical-align: top; -} - -.ant-pagination-simple .ant-pagination-prev .ant-pagination-item-link, -.ant-pagination-simple .ant-pagination-next .ant-pagination-item-link { - height: 24px; - background-color: transparent; - border: 0; -} - -.ant-pagination-simple .ant-pagination-prev .ant-pagination-item-link::after, -.ant-pagination-simple .ant-pagination-next .ant-pagination-item-link::after { - height: 24px; - line-height: 24px; -} - -.ant-pagination-simple .ant-pagination-simple-pager { - display: inline-block; - height: 24px; - margin-right: 8px; -} - -.ant-pagination-simple .ant-pagination-simple-pager input { - box-sizing: border-box; - height: 100%; - margin-right: 8px; - padding: 0 6px; - text-align: center; - background-color: #fff; - border: 1px solid #d9d9d9; - border-radius: 2px; - outline: none; - transition: border-color 0.3s; -} - -.ant-pagination-simple .ant-pagination-simple-pager input:hover { - border-color: #1890ff; -} - -.ant-pagination-simple .ant-pagination-simple-pager input:focus { - border-color: #40a9ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); -} - -.ant-pagination-simple .ant-pagination-simple-pager input[disabled] { - color: rgba(0, 0, 0, 0.25); - background: #f5f5f5; - border-color: #d9d9d9; - cursor: not-allowed; -} - -.ant-pagination.mini .ant-pagination-total-text, -.ant-pagination.mini .ant-pagination-simple-pager { - height: 24px; - line-height: 24px; -} - -.ant-pagination.mini .ant-pagination-item { - min-width: 24px; - height: 24px; - margin: 0; - line-height: 22px; -} - -.ant-pagination.mini .ant-pagination-item:not(.ant-pagination-item-active) { - background: transparent; - border-color: transparent; -} - -.ant-pagination.mini .ant-pagination-prev, -.ant-pagination.mini .ant-pagination-next { - min-width: 24px; - height: 24px; - margin: 0; - line-height: 24px; -} - -.ant-pagination.mini .ant-pagination-prev .ant-pagination-item-link, -.ant-pagination.mini .ant-pagination-next .ant-pagination-item-link { - background: transparent; - border-color: transparent; -} - -.ant-pagination.mini .ant-pagination-prev .ant-pagination-item-link::after, -.ant-pagination.mini .ant-pagination-next .ant-pagination-item-link::after { - height: 24px; - line-height: 24px; -} - -.ant-pagination.mini .ant-pagination-jump-prev, -.ant-pagination.mini .ant-pagination-jump-next { - height: 24px; - margin-right: 0; - line-height: 24px; -} - -.ant-pagination.mini .ant-pagination-options { - margin-left: 2px; -} - -.ant-pagination.mini .ant-pagination-options-size-changer { - top: 0px; -} - -.ant-pagination.mini .ant-pagination-options-quick-jumper { - height: 24px; - line-height: 24px; -} - -.ant-pagination.mini .ant-pagination-options-quick-jumper input { - padding: 0px 7px; - width: 44px; - height: 24px; -} - -.ant-pagination.ant-pagination-disabled { - cursor: not-allowed; -} - -.ant-pagination.ant-pagination-disabled .ant-pagination-item { - background: #f5f5f5; - border-color: #d9d9d9; - cursor: not-allowed; -} - -.ant-pagination.ant-pagination-disabled .ant-pagination-item a { - color: rgba(0, 0, 0, 0.25); - background: transparent; - border: none; - cursor: not-allowed; -} - -.ant-pagination.ant-pagination-disabled .ant-pagination-item-active { - background: #e6e6e6; -} - -.ant-pagination.ant-pagination-disabled .ant-pagination-item-active a { - color: rgba(0, 0, 0, 0.25); -} - -.ant-pagination.ant-pagination-disabled .ant-pagination-item-link { - color: rgba(0, 0, 0, 0.25); - background: #f5f5f5; - border-color: #d9d9d9; - cursor: not-allowed; -} - -.ant-pagination-simple.ant-pagination.ant-pagination-disabled .ant-pagination-item-link { - background: transparent; -} - -.ant-pagination.ant-pagination-disabled .ant-pagination-item-link-icon { - opacity: 0; -} - -.ant-pagination.ant-pagination-disabled .ant-pagination-item-ellipsis { - opacity: 1; -} - -.ant-pagination.ant-pagination-disabled .ant-pagination-simple-pager { - color: rgba(0, 0, 0, 0.25); -} - -@media only screen and (max-width: 992px) { - .ant-pagination-item-after-jump-prev, - .ant-pagination-item-before-jump-next { - display: none; - } -} - -@media only screen and (max-width: 576px) { - .ant-pagination-options { - display: none; - } -} - - -.ant-mentions { - box-sizing: border-box; - margin: 0; - font-variant: tabular-nums; - list-style: none; - font-feature-settings: 'tnum'; - width: 100%; - min-width: 0; - padding: 4px 11px; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - background-color: #fff; - background-image: none; - border: 1px solid #d9d9d9; - border-radius: 2px; - transition: all 0.3s; - position: relative; - display: inline-block; - height: auto; - padding: 0; - overflow: hidden; - line-height: 1.5715; - white-space: pre-wrap; - vertical-align: bottom; -} - -.ant-mentions::-moz-placeholder { - opacity: 1; -} - -.ant-mentions:-ms-input-placeholder { - color: #bfbfbf; - -ms-user-select: none; - user-select: none; -} - -.ant-mentions::placeholder { - color: #bfbfbf; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-mentions:-moz-placeholder-shown { - text-overflow: ellipsis; -} - -.ant-mentions:-ms-input-placeholder { - text-overflow: ellipsis; -} - -.ant-mentions:placeholder-shown { - text-overflow: ellipsis; -} - -.ant-mentions:hover { - border-color: #40a9ff; - border-right-width: 1px !important; -} - -.ant-mentions:focus, -.ant-mentions-focused { - border-color: #40a9ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-mentions-disabled { - color: rgba(0, 0, 0, 0.25); - background-color: #f5f5f5; - border-color: #d9d9d9; - box-shadow: none; - cursor: not-allowed; - opacity: 1; -} - -.ant-mentions-disabled:hover { - border-color: #d9d9d9; - border-right-width: 1px !important; -} - -.ant-mentions[disabled] { - color: rgba(0, 0, 0, 0.25); - background-color: #f5f5f5; - border-color: #d9d9d9; - box-shadow: none; - cursor: not-allowed; - opacity: 1; -} - -.ant-mentions[disabled]:hover { - border-color: #d9d9d9; - border-right-width: 1px !important; -} - -.ant-mentions-borderless, -.ant-mentions-borderless:hover, -.ant-mentions-borderless:focus, -.ant-mentions-borderless-focused, -.ant-mentions-borderless-disabled, -.ant-mentions-borderless[disabled] { - background-color: transparent; - border: none; - box-shadow: none; -} - -textarea.ant-mentions { - max-width: 100%; - height: auto; - min-height: 32px; - line-height: 1.5715; - vertical-align: bottom; - transition: all 0.3s, height 0s; -} - -.ant-mentions-lg { - padding: 6.5px 11px; - font-size: 16px; -} - -.ant-mentions-sm { - padding: 0px 7px; -} - -.ant-mentions-disabled > textarea { - color: rgba(0, 0, 0, 0.25); - background-color: #f5f5f5; - border-color: #d9d9d9; - box-shadow: none; - cursor: not-allowed; - opacity: 1; -} - -.ant-mentions-disabled > textarea:hover { - border-color: #d9d9d9; - border-right-width: 1px !important; -} - -.ant-mentions-focused { - border-color: #40a9ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); - border-right-width: 1px !important; - outline: 0; -} - -.ant-mentions > textarea, -.ant-mentions-measure { - min-height: 30px; - margin: 0; - padding: 4px 11px; - overflow: inherit; - overflow-x: hidden; - overflow-y: auto; - font-weight: inherit; - font-size: inherit; - font-family: inherit; - font-style: inherit; - font-variant: inherit; - font-size-adjust: inherit; - font-stretch: inherit; - line-height: inherit; - /* stylelint-enable declaration-block-no-redundant-longhand-properties */ - direction: inherit; - letter-spacing: inherit; - white-space: inherit; - text-align: inherit; - vertical-align: top; - word-wrap: break-word; - word-break: inherit; - -moz-tab-size: inherit; - -o-tab-size: inherit; - tab-size: inherit; -} - -.ant-mentions > textarea { - width: 100%; - border: none; - outline: none; - resize: none; -} - -.ant-mentions > textarea::-moz-placeholder { - opacity: 1; -} - -.ant-mentions > textarea:-ms-input-placeholder { - color: #bfbfbf; - -ms-user-select: none; - user-select: none; -} - -.ant-mentions > textarea::placeholder { - color: #bfbfbf; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-mentions > textarea:-moz-placeholder-shown { - text-overflow: ellipsis; -} - -.ant-mentions > textarea:-ms-input-placeholder { - text-overflow: ellipsis; -} - -.ant-mentions > textarea:placeholder-shown { - text-overflow: ellipsis; -} - -.ant-mentions-measure { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: -1; - color: transparent; - pointer-events: none; -} - -.ant-mentions-measure > span { - display: inline-block; - min-height: 1em; -} - -.ant-mentions-dropdown { - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: absolute; - top: -9999px; - left: -9999px; - z-index: 1050; - box-sizing: border-box; - font-size: 14px; - font-variant: initial; - background-color: #fff; - border-radius: 2px; - outline: none; - box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); -} - -.ant-mentions-dropdown-hidden { - display: none; -} - -.ant-mentions-dropdown-menu { - max-height: 250px; - margin-bottom: 0; - padding-left: 0; - overflow: auto; - list-style: none; - outline: none; -} - -.ant-mentions-dropdown-menu-item { - position: relative; - display: block; - min-width: 100px; - padding: 5px 12px; - overflow: hidden; - color: rgba(0, 0, 0, 0.85); - font-weight: normal; - line-height: 1.5715; - white-space: nowrap; - text-overflow: ellipsis; - cursor: pointer; - transition: background 0.3s ease; -} - -.ant-mentions-dropdown-menu-item:hover { - background-color: #f5f5f5; -} - -.ant-mentions-dropdown-menu-item:first-child { - border-radius: 2px 2px 0 0; -} - -.ant-mentions-dropdown-menu-item:last-child { - border-radius: 0 0 2px 2px; -} - -.ant-mentions-dropdown-menu-item-disabled { - color: rgba(0, 0, 0, 0.25); - cursor: not-allowed; -} - -.ant-mentions-dropdown-menu-item-disabled:hover { - color: rgba(0, 0, 0, 0.25); - background-color: #fff; - cursor: not-allowed; -} - -.ant-mentions-dropdown-menu-item-selected { - color: rgba(0, 0, 0, 0.85); - font-weight: 600; - background-color: #fafafa; -} - -.ant-mentions-dropdown-menu-item-active { - background-color: #f5f5f5; -} - -.ant-message { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: fixed; - top: 8px; - left: 0; - z-index: 1010; - width: 100%; - pointer-events: none; -} - -.ant-message-notice { - padding: 8px; - text-align: center; -} - -.ant-message-notice-content { - display: inline-block; - padding: 10px 16px; - background: #fff; - border-radius: 2px; - box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); - pointer-events: all; -} - -.ant-message-success .anticon { - color: #52c41a; -} - -.ant-message-error .anticon { - color: #ff4d4f; -} - -.ant-message-warning .anticon { - color: #faad14; -} - -.ant-message-info .anticon, -.ant-message-loading .anticon { - color: #1890ff; -} - -.ant-message .anticon { - position: relative; - top: 1px; - margin-right: 8px; - font-size: 16px; -} - -.ant-message-notice.ant-move-up-leave.ant-move-up-leave-active { - -webkit-animation-name: MessageMoveOut; - animation-name: MessageMoveOut; - -webkit-animation-duration: 0.3s; - animation-duration: 0.3s; -} - -@-webkit-keyframes MessageMoveOut { - 0% { - max-height: 150px; - padding: 8px; - opacity: 1; - } - 100% { - max-height: 0; - padding: 0; - opacity: 0; - } -} - -@keyframes MessageMoveOut { - 0% { - max-height: 150px; - padding: 8px; - opacity: 1; - } - 100% { - max-height: 0; - padding: 0; - opacity: 0; - } -} - -.ant-modal { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - pointer-events: none; - position: relative; - top: 100px; - width: auto; - max-width: calc(100vw - 32px); - margin: 0 auto; - padding-bottom: 24px; -} - -.ant-modal.ant-zoom-enter, -.ant-modal.antzoom-appear { - transform: none; - opacity: 0; - -webkit-animation-duration: 0.3s; - animation-duration: 0.3s; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-modal-mask { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1000; - height: 100%; - background-color: rgba(0, 0, 0, 0.45); -} - -.ant-modal-mask-hidden { - display: none; -} - -.ant-modal-wrap { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - overflow: auto; - outline: 0; - -webkit-overflow-scrolling: touch; -} - -.ant-modal-wrap { - z-index: 1000; -} - -.ant-modal-title { - margin: 0; - color: rgba(0, 0, 0, 0.85); - font-weight: 500; - font-size: 16px; - line-height: 22px; - word-wrap: break-word; -} - -.ant-modal-content { - position: relative; - background-color: #fff; - background-clip: padding-box; - border: 0; - border-radius: 2px; - box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); - pointer-events: auto; -} - -.ant-modal-close { - position: absolute; - top: 0; - right: 0; - z-index: 10; - padding: 0; - color: rgba(0, 0, 0, 0.45); - font-weight: 700; - line-height: 1; - text-decoration: none; - background: transparent; - border: 0; - outline: 0; - cursor: pointer; - transition: color 0.3s; -} - -.ant-modal-close-x { - display: block; - width: 56px; - height: 56px; - font-size: 16px; - font-style: normal; - line-height: 56px; - text-align: center; - text-transform: none; - text-rendering: auto; -} - -.ant-modal-close:focus, -.ant-modal-close:hover { - color: rgba(0, 0, 0, 0.75); - text-decoration: none; -} - -.ant-modal-header { - padding: 16px 24px; - color: rgba(0, 0, 0, 0.85); - background: #fff; - border-bottom: 1px solid #f0f0f0; - border-radius: 2px 2px 0 0; -} - -.ant-modal-body { - padding: 24px; - font-size: 14px; - line-height: 1.5715; - word-wrap: break-word; -} - -.ant-modal-footer { - padding: 10px 16px; - text-align: right; - background: transparent; - border-top: 1px solid #f0f0f0; - border-radius: 0 0 2px 2px; -} - -.ant-modal-footer .ant-btn + .ant-btn:not(.ant-dropdown-trigger) { - margin-bottom: 0; - margin-left: 8px; -} - -.ant-modal-open { - overflow: hidden; -} - -.ant-modal-centered { - text-align: center; -} - -.ant-modal-centered::before { - display: inline-block; - width: 0; - height: 100%; - vertical-align: middle; - content: ''; -} - -.ant-modal-centered .ant-modal { - top: 0; - display: inline-block; - padding-bottom: 0; - text-align: left; - vertical-align: middle; -} - -@media (max-width: 767px) { - .ant-modal { - max-width: calc(100vw - 16px); - margin: 8px auto; - } - .ant-modal-centered .ant-modal { - flex: 1; - } -} - -.ant-modal-confirm .ant-modal-header { - display: none; -} - -.ant-modal-confirm .ant-modal-body { - padding: 32px 32px 24px; -} - -.ant-modal-confirm-body-wrapper::before { - display: table; - content: ''; -} - -.ant-modal-confirm-body-wrapper::after { - display: table; - clear: both; - content: ''; -} - -.ant-modal-confirm-body .ant-modal-confirm-title { - display: block; - overflow: hidden; - color: rgba(0, 0, 0, 0.85); - font-weight: 500; - font-size: 16px; - line-height: 1.4; -} - -.ant-modal-confirm-body .ant-modal-confirm-content { - margin-top: 8px; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; -} - -.ant-modal-confirm-body > .anticon { - float: left; - margin-right: 16px; - font-size: 22px; -} - -.ant-modal-confirm-body > .anticon + .ant-modal-confirm-title + .ant-modal-confirm-content { - margin-left: 38px; -} - -.ant-modal-confirm .ant-modal-confirm-btns { - float: right; - margin-top: 24px; -} - -.ant-modal-confirm .ant-modal-confirm-btns .ant-btn + .ant-btn { - margin-bottom: 0; - margin-left: 8px; -} - -.ant-modal-confirm-error .ant-modal-confirm-body > .anticon { - color: #ff4d4f; -} - -.ant-modal-confirm-warning .ant-modal-confirm-body > .anticon, -.ant-modal-confirm-confirm .ant-modal-confirm-body > .anticon { - color: #faad14; -} - -.ant-modal-confirm-info .ant-modal-confirm-body > .anticon { - color: #1890ff; -} - -.ant-modal-confirm-success .ant-modal-confirm-body > .anticon { - color: #52c41a; -} - -.ant-notification { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: fixed; - z-index: 1010; - margin-right: 24px; -} - -.ant-notification-topLeft, -.ant-notification-bottomLeft { - margin-right: 0; - margin-left: 24px; -} - -.ant-notification-topLeft .ant-notification-fade-enter.ant-notification-fade-enter-active, -.ant-notification-bottomLeft .ant-notification-fade-enter.ant-notification-fade-enter-active, -.ant-notification-topLeft .ant-notification-fade-appear.ant-notification-fade-appear-active, -.ant-notification-bottomLeft .ant-notification-fade-appear.ant-notification-fade-appear-active { - -webkit-animation-name: NotificationLeftFadeIn; - animation-name: NotificationLeftFadeIn; -} - -.ant-notification-close-icon { - font-size: 14px; - cursor: pointer; -} - -.ant-notification-hook-holder { - position: relative; -} - -.ant-notification-notice { - position: relative; - width: 384px; - max-width: calc(100vw - 24px * 2); - margin-bottom: 16px; - margin-left: auto; - padding: 16px 24px; - overflow: hidden; - line-height: 1.5715; - word-wrap: break-word; - background: #fff; - border-radius: 2px; - box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); -} - -.ant-notification-topLeft .ant-notification-notice, -.ant-notification-bottomLeft .ant-notification-notice { - margin-right: auto; - margin-left: 0; -} - -.ant-notification-notice-message { - margin-bottom: 8px; - color: rgba(0, 0, 0, 0.85); - font-size: 16px; - line-height: 24px; -} - -.ant-notification-notice-message-single-line-auto-margin { - display: block; - width: calc(384px - 24px * 2 - 24px - 48px - 100%); - max-width: 4px; - background-color: transparent; - pointer-events: none; -} - -.ant-notification-notice-message-single-line-auto-margin::before { - display: block; - content: ''; -} - -.ant-notification-notice-description { - font-size: 14px; -} - -.ant-notification-notice-closable .ant-notification-notice-message { - padding-right: 24px; -} - -.ant-notification-notice-with-icon .ant-notification-notice-message { - margin-bottom: 4px; - margin-left: 48px; - font-size: 16px; -} - -.ant-notification-notice-with-icon .ant-notification-notice-description { - margin-left: 48px; - font-size: 14px; -} - -.ant-notification-notice-icon { - position: absolute; - margin-left: 4px; - font-size: 24px; - line-height: 24px; -} - -.anticon.ant-notification-notice-icon-success { - color: #52c41a; -} - -.anticon.ant-notification-notice-icon-info { - color: #1890ff; -} - -.anticon.ant-notification-notice-icon-warning { - color: #faad14; -} - -.anticon.ant-notification-notice-icon-error { - color: #ff4d4f; -} - -.ant-notification-notice-close { - position: absolute; - top: 16px; - right: 22px; - color: rgba(0, 0, 0, 0.45); - outline: none; -} - -.ant-notification-notice-close:hover { - color: rgba(0, 0, 0, 0.67); -} - -.ant-notification-notice-btn { - float: right; - margin-top: 16px; -} - -.ant-notification .notification-fade-effect { - -webkit-animation-duration: 0.24s; - animation-duration: 0.24s; - -webkit-animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); - animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); - -webkit-animation-fill-mode: both; - animation-fill-mode: both; -} - -.ant-notification-fade-enter, -.ant-notification-fade-appear { - -webkit-animation-duration: 0.24s; - animation-duration: 0.24s; - -webkit-animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); - animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - opacity: 0; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-notification-fade-leave { - -webkit-animation-duration: 0.24s; - animation-duration: 0.24s; - -webkit-animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); - animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-play-state: paused; - animation-play-state: paused; -} - -.ant-notification-fade-enter.ant-notification-fade-enter-active, -.ant-notification-fade-appear.ant-notification-fade-appear-active { - -webkit-animation-name: NotificationFadeIn; - animation-name: NotificationFadeIn; - -webkit-animation-play-state: running; - animation-play-state: running; -} - -.ant-notification-fade-leave.ant-notification-fade-leave-active { - -webkit-animation-name: NotificationFadeOut; - animation-name: NotificationFadeOut; - -webkit-animation-play-state: running; - animation-play-state: running; -} - -@-webkit-keyframes NotificationFadeIn { - 0% { - left: 384px; - opacity: 0; - } - 100% { - left: 0; - opacity: 1; - } -} - -@keyframes NotificationFadeIn { - 0% { - left: 384px; - opacity: 0; - } - 100% { - left: 0; - opacity: 1; - } -} - -@-webkit-keyframes NotificationLeftFadeIn { - 0% { - right: 384px; - opacity: 0; - } - 100% { - right: 0; - opacity: 1; - } -} - -@keyframes NotificationLeftFadeIn { - 0% { - right: 384px; - opacity: 0; - } - 100% { - right: 0; - opacity: 1; - } -} - -@-webkit-keyframes NotificationFadeOut { - 0% { - max-height: 150px; - margin-bottom: 16px; - opacity: 1; - } - 100% { - max-height: 0; - margin-bottom: 0; - padding-top: 0; - padding-bottom: 0; - opacity: 0; - } -} - -@keyframes NotificationFadeOut { - 0% { - max-height: 150px; - margin-bottom: 16px; - opacity: 1; - } - 100% { - max-height: 0; - margin-bottom: 0; - padding-top: 0; - padding-bottom: 0; - opacity: 0; - } -} - -.ant-page-header { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - padding: 16px 24px; - background-color: #fff; -} - -.ant-page-header-ghost { - background-color: inherit; -} - -.ant-page-header.has-breadcrumb { - padding-top: 12px; -} - -.ant-page-header.has-footer { - padding-bottom: 0; -} - -.ant-page-header-back { - margin-right: 16px; - font-size: 16px; - line-height: 1; -} - -.ant-page-header-back-button { - color: #1890ff; - text-decoration: none; - outline: none; - transition: color 0.3s; - color: #000; - cursor: pointer; -} - -.ant-page-header-back-button:focus, -.ant-page-header-back-button:hover { - color: #40a9ff; -} - -.ant-page-header-back-button:active { - color: #096dd9; -} - -.ant-page-header .ant-divider-vertical { - height: 14px; - margin: 0 12px; - vertical-align: middle; -} - -.ant-breadcrumb + .ant-page-header-heading { - margin-top: 8px; -} - -.ant-page-header-heading { - display: flex; - justify-content: space-between; -} - -.ant-page-header-heading-left { - display: flex; - align-items: center; - margin: 4px 0; - overflow: hidden; -} - -.ant-page-header-heading-title { - margin-right: 12px; - margin-bottom: 0; - color: rgba(0, 0, 0, 0.85); - font-weight: 600; - font-size: 20px; - line-height: 32px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - -.ant-page-header-heading .ant-avatar { - margin-right: 12px; -} - -.ant-page-header-heading-sub-title { - margin-right: 12px; - color: rgba(0, 0, 0, 0.45); - font-size: 14px; - line-height: 1.5715; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - -.ant-page-header-heading-extra { - margin: 4px 0; - white-space: nowrap; -} - -.ant-page-header-heading-extra > * { - margin-left: 12px; - white-space: unset; -} - -.ant-page-header-heading-extra > *:first-child { - margin-left: 0; -} - -.ant-page-header-content { - padding-top: 12px; -} - -.ant-page-header-footer { - margin-top: 16px; -} - -.ant-page-header-footer .ant-tabs > .ant-tabs-nav { - margin: 0; -} - -.ant-page-header-footer .ant-tabs > .ant-tabs-nav::before { - border: none; -} - -.ant-page-header-footer .ant-tabs .ant-tabs-tab { - padding-top: 8px; - padding-bottom: 8px; - font-size: 16px; -} - -.ant-page-header-compact .ant-page-header-heading { - flex-wrap: wrap; -} - -.ant-popconfirm { - z-index: 1060; -} - -.ant-progress { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - display: inline-block; -} - -.ant-progress-line { - position: relative; - width: 100%; - font-size: 14px; -} - -.ant-progress-steps { - display: inline-block; -} - -.ant-progress-steps-outer { - display: flex; - flex-direction: row; - align-items: center; -} - -.ant-progress-steps-item { - flex-shrink: 0; - min-width: 2px; - margin-right: 2px; - background: #f3f3f3; - transition: all 0.3s; -} - -.ant-progress-steps-item-active { - background: #1890ff; -} - -.ant-progress-small.ant-progress-line, -.ant-progress-small.ant-progress-line .ant-progress-text .anticon { - font-size: 12px; -} - -.ant-progress-outer { - display: inline-block; - width: 100%; - margin-right: 0; - padding-right: 0; -} - -.ant-progress-show-info .ant-progress-outer { - margin-right: calc(-2em - 8px); - padding-right: calc(2em + 8px); -} - -.ant-progress-inner { - position: relative; - display: inline-block; - width: 100%; - overflow: hidden; - vertical-align: middle; - background-color: #f5f5f5; - border-radius: 100px; -} - -.ant-progress-circle-trail { - stroke: #f5f5f5; -} - -.ant-progress-circle-path { - -webkit-animation: ant-progress-appear 0.3s; - animation: ant-progress-appear 0.3s; -} - -.ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path { - stroke: #1890ff; -} - -.ant-progress-success-bg, -.ant-progress-bg { - position: relative; - background-color: #1890ff; - border-radius: 100px; - transition: all 0.4s cubic-bezier(0.08, 0.82, 0.17, 1) 0s; -} - -.ant-progress-success-bg { - position: absolute; - top: 0; - left: 0; - background-color: #52c41a; -} - -.ant-progress-text { - display: inline-block; - width: 2em; - margin-left: 8px; - color: rgba(0, 0, 0, 0.85); - font-size: 1em; - line-height: 1; - white-space: nowrap; - text-align: left; - vertical-align: middle; - word-break: normal; -} - -.ant-progress-text .anticon { - font-size: 14px; -} - -.ant-progress-status-active .ant-progress-bg::before { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: #fff; - border-radius: 10px; - opacity: 0; - -webkit-animation: ant-progress-active 2.4s cubic-bezier(0.23, 1, 0.32, 1) infinite; - animation: ant-progress-active 2.4s cubic-bezier(0.23, 1, 0.32, 1) infinite; - content: ''; -} - -.ant-progress-status-exception .ant-progress-bg { - background-color: #ff4d4f; -} - -.ant-progress-status-exception .ant-progress-text { - color: #ff4d4f; -} - -.ant-progress-status-exception .ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path { - stroke: #ff4d4f; -} - -.ant-progress-status-success .ant-progress-bg { - background-color: #52c41a; -} - -.ant-progress-status-success .ant-progress-text { - color: #52c41a; -} - -.ant-progress-status-success .ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path { - stroke: #52c41a; -} - -.ant-progress-circle .ant-progress-inner { - position: relative; - line-height: 1; - background-color: transparent; -} - -.ant-progress-circle .ant-progress-text { - position: absolute; - top: 50%; - left: 50%; - width: 100%; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 1em; - line-height: 1; - white-space: normal; - text-align: center; - transform: translate(-50%, -50%); -} - -.ant-progress-circle .ant-progress-text .anticon { - font-size: 1.16666667em; -} - -.ant-progress-circle.ant-progress-status-exception .ant-progress-text { - color: #ff4d4f; -} - -.ant-progress-circle.ant-progress-status-success .ant-progress-text { - color: #52c41a; -} - -@-webkit-keyframes ant-progress-active { - 0% { - transform: translateX(-100%) scaleX(0); - opacity: 0.1; - } - 20% { - transform: translateX(-100%) scaleX(0); - opacity: 0.5; - } - 100% { - transform: translateX(0) scaleX(1); - opacity: 0; - } -} - -@keyframes ant-progress-active { - 0% { - transform: translateX(-100%) scaleX(0); - opacity: 0.1; - } - 20% { - transform: translateX(-100%) scaleX(0); - opacity: 0.5; - } - 100% { - transform: translateX(0) scaleX(1); - opacity: 0; - } -} - -.ant-rate { - box-sizing: border-box; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - font-feature-settings: 'tnum'; - display: inline-block; - margin: 0; - padding: 0; - color: #fadb14; - font-size: 20px; - line-height: unset; - list-style: none; - outline: none; -} - -.ant-rate-disabled .ant-rate-star { - cursor: default; -} - -.ant-rate-disabled .ant-rate-star:hover { - transform: scale(1); -} - -.ant-rate-star { - position: relative; - display: inline-block; - color: inherit; - cursor: pointer; -} - -.ant-rate-star:not(:last-child) { - margin-right: 8px; -} - -.ant-rate-star > div { - transition: all 0.3s, outline 0s; -} - -.ant-rate-star > div:hover, -.ant-rate-star > div:focus-visible { - transform: scale(1.1); -} - -.ant-rate-star > div:focus { - outline: 0; -} - -.ant-rate-star > div:focus-visible { - outline: 1px dashed #fadb14; -} - -.ant-rate-star-first, -.ant-rate-star-second { - color: #f0f0f0; - transition: all 0.3s; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-rate-star-first .anticon, -.ant-rate-star-second .anticon { - vertical-align: middle; -} - -.ant-rate-star-first { - position: absolute; - top: 0; - left: 0; - width: 50%; - height: 100%; - overflow: hidden; - opacity: 0; -} - -.ant-rate-star-half .ant-rate-star-first, -.ant-rate-star-half .ant-rate-star-second { - opacity: 1; -} - -.ant-rate-star-half .ant-rate-star-first, -.ant-rate-star-full .ant-rate-star-second { - color: inherit; -} - -.ant-rate-text { - display: inline-block; - margin: 0 8px; - font-size: 14px; -} - -.ant-result { - padding: 48px 32px; -} - -.ant-result-success .ant-result-icon > .anticon { - color: #52c41a; -} - -.ant-result-error .ant-result-icon > .anticon { - color: #ff4d4f; -} - -.ant-result-info .ant-result-icon > .anticon { - color: #1890ff; -} - -.ant-result-warning .ant-result-icon > .anticon { - color: #faad14; -} - -.ant-result-image { - width: 250px; - height: 295px; - margin: auto; -} - -.ant-result-icon { - margin-bottom: 24px; - text-align: center; -} - -.ant-result-icon > .anticon { - font-size: 72px; -} - -.ant-result-title { - color: rgba(0, 0, 0, 0.85); - font-size: 24px; - line-height: 1.8; - text-align: center; -} - -.ant-result-subtitle { - color: rgba(0, 0, 0, 0.45); - font-size: 14px; - line-height: 1.6; - text-align: center; -} - -.ant-result-extra { - margin: 24px 0 0 0; - text-align: center; -} - -.ant-result-extra > * { - margin-right: 8px; -} - -.ant-result-extra > *:last-child { - margin-right: 0; -} - -.ant-result-content { - margin-top: 24px; - padding: 24px 40px; - background-color: #fafafa; -} - -.ant-skeleton { - display: table; - width: 100%; -} - -.ant-skeleton-header { - display: table-cell; - padding-right: 16px; - vertical-align: top; -} - -.ant-skeleton-header .ant-skeleton-avatar { - display: inline-block; - vertical-align: top; - background: rgba(190, 190, 190, 0.2); - width: 32px; - height: 32px; - line-height: 32px; -} - -.ant-skeleton-header .ant-skeleton-avatar.ant-skeleton-avatar-circle { - border-radius: 50%; -} - -.ant-skeleton-header .ant-skeleton-avatar-lg { - width: 40px; - height: 40px; - line-height: 40px; -} - -.ant-skeleton-header .ant-skeleton-avatar-lg.ant-skeleton-avatar-circle { - border-radius: 50%; -} - -.ant-skeleton-header .ant-skeleton-avatar-sm { - width: 24px; - height: 24px; - line-height: 24px; -} - -.ant-skeleton-header .ant-skeleton-avatar-sm.ant-skeleton-avatar-circle { - border-radius: 50%; -} - -.ant-skeleton-content { - display: table-cell; - width: 100%; - vertical-align: top; -} - -.ant-skeleton-content .ant-skeleton-title { - width: 100%; - height: 16px; - margin-top: 16px; - background: rgba(190, 190, 190, 0.2); - border-radius: 4px; -} - -.ant-skeleton-content .ant-skeleton-title + .ant-skeleton-paragraph { - margin-top: 24px; -} - -.ant-skeleton-content .ant-skeleton-paragraph { - padding: 0; -} - -.ant-skeleton-content .ant-skeleton-paragraph > li { - width: 100%; - height: 16px; - list-style: none; - background: rgba(190, 190, 190, 0.2); - border-radius: 4px; -} - -.ant-skeleton-content .ant-skeleton-paragraph > li:last-child:not(:first-child):not(:nth-child(2)) { - width: 61%; -} - -.ant-skeleton-content .ant-skeleton-paragraph > li + li { - margin-top: 16px; -} - -.ant-skeleton-with-avatar .ant-skeleton-content .ant-skeleton-title { - margin-top: 12px; -} - -.ant-skeleton-with-avatar .ant-skeleton-content .ant-skeleton-title + .ant-skeleton-paragraph { - margin-top: 28px; -} - -.ant-skeleton-round .ant-skeleton-content .ant-skeleton-title, -.ant-skeleton-round .ant-skeleton-content .ant-skeleton-paragraph > li { - border-radius: 100px; -} - -.ant-skeleton.ant-skeleton-active .ant-skeleton-content .ant-skeleton-title, -.ant-skeleton.ant-skeleton-active .ant-skeleton-content .ant-skeleton-paragraph > li { - background: linear-gradient(90deg, rgba(190, 190, 190, 0.2) 25%, rgba(129, 129, 129, 0.24) 37%, rgba(190, 190, 190, 0.2) 63%); - background-size: 400% 100%; - -webkit-animation: ant-skeleton-loading 1.4s ease infinite; - animation: ant-skeleton-loading 1.4s ease infinite; -} - -.ant-skeleton.ant-skeleton-active .ant-skeleton-avatar { - background: linear-gradient(90deg, rgba(190, 190, 190, 0.2) 25%, rgba(129, 129, 129, 0.24) 37%, rgba(190, 190, 190, 0.2) 63%); - background-size: 400% 100%; - -webkit-animation: ant-skeleton-loading 1.4s ease infinite; - animation: ant-skeleton-loading 1.4s ease infinite; -} - -.ant-skeleton.ant-skeleton-active .ant-skeleton-button { - background: linear-gradient(90deg, rgba(190, 190, 190, 0.2) 25%, rgba(129, 129, 129, 0.24) 37%, rgba(190, 190, 190, 0.2) 63%); - background-size: 400% 100%; - -webkit-animation: ant-skeleton-loading 1.4s ease infinite; - animation: ant-skeleton-loading 1.4s ease infinite; -} - -.ant-skeleton.ant-skeleton-active .ant-skeleton-input { - background: linear-gradient(90deg, rgba(190, 190, 190, 0.2) 25%, rgba(129, 129, 129, 0.24) 37%, rgba(190, 190, 190, 0.2) 63%); - background-size: 400% 100%; - -webkit-animation: ant-skeleton-loading 1.4s ease infinite; - animation: ant-skeleton-loading 1.4s ease infinite; -} - -.ant-skeleton.ant-skeleton-active .ant-skeleton-image { - background: linear-gradient(90deg, rgba(190, 190, 190, 0.2) 25%, rgba(129, 129, 129, 0.24) 37%, rgba(190, 190, 190, 0.2) 63%); - background-size: 400% 100%; - -webkit-animation: ant-skeleton-loading 1.4s ease infinite; - animation: ant-skeleton-loading 1.4s ease infinite; -} - -.ant-skeleton.ant-skeleton-block { - width: 100%; -} - -.ant-skeleton.ant-skeleton-block .ant-skeleton-button { - width: 100%; -} - -.ant-skeleton-element { - display: inline-block; - width: auto; -} - -.ant-skeleton-element .ant-skeleton-button { - display: inline-block; - vertical-align: top; - background: rgba(190, 190, 190, 0.2); - border-radius: 2px; - width: 64px; - min-width: 64px; - height: 32px; - line-height: 32px; -} - -.ant-skeleton-element .ant-skeleton-button.ant-skeleton-button-circle { - width: 32px; - min-width: 32px; - border-radius: 50%; -} - -.ant-skeleton-element .ant-skeleton-button.ant-skeleton-button-round { - border-radius: 32px; -} - -.ant-skeleton-element .ant-skeleton-button-lg { - width: 80px; - min-width: 80px; - height: 40px; - line-height: 40px; -} - -.ant-skeleton-element .ant-skeleton-button-lg.ant-skeleton-button-circle { - width: 40px; - min-width: 40px; - border-radius: 50%; -} - -.ant-skeleton-element .ant-skeleton-button-lg.ant-skeleton-button-round { - border-radius: 40px; -} - -.ant-skeleton-element .ant-skeleton-button-sm { - width: 48px; - min-width: 48px; - height: 24px; - line-height: 24px; -} - -.ant-skeleton-element .ant-skeleton-button-sm.ant-skeleton-button-circle { - width: 24px; - min-width: 24px; - border-radius: 50%; -} - -.ant-skeleton-element .ant-skeleton-button-sm.ant-skeleton-button-round { - border-radius: 24px; -} - -.ant-skeleton-element .ant-skeleton-avatar { - display: inline-block; - vertical-align: top; - background: rgba(190, 190, 190, 0.2); - width: 32px; - height: 32px; - line-height: 32px; -} - -.ant-skeleton-element .ant-skeleton-avatar.ant-skeleton-avatar-circle { - border-radius: 50%; -} - -.ant-skeleton-element .ant-skeleton-avatar-lg { - width: 40px; - height: 40px; - line-height: 40px; -} - -.ant-skeleton-element .ant-skeleton-avatar-lg.ant-skeleton-avatar-circle { - border-radius: 50%; -} - -.ant-skeleton-element .ant-skeleton-avatar-sm { - width: 24px; - height: 24px; - line-height: 24px; -} - -.ant-skeleton-element .ant-skeleton-avatar-sm.ant-skeleton-avatar-circle { - border-radius: 50%; -} - -.ant-skeleton-element .ant-skeleton-input { - display: inline-block; - vertical-align: top; - background: rgba(190, 190, 190, 0.2); - width: 100%; - height: 32px; - line-height: 32px; -} - -.ant-skeleton-element .ant-skeleton-input-lg { - width: 100%; - height: 40px; - line-height: 40px; -} - -.ant-skeleton-element .ant-skeleton-input-sm { - width: 100%; - height: 24px; - line-height: 24px; -} - -.ant-skeleton-element .ant-skeleton-image { - display: flex; - align-items: center; - justify-content: center; - vertical-align: top; - background: rgba(190, 190, 190, 0.2); - width: 96px; - height: 96px; - line-height: 96px; -} - -.ant-skeleton-element .ant-skeleton-image.ant-skeleton-image-circle { - border-radius: 50%; -} - -.ant-skeleton-element .ant-skeleton-image-path { - fill: #bfbfbf; -} - -.ant-skeleton-element .ant-skeleton-image-svg { - width: 48px; - height: 48px; - line-height: 48px; - max-width: 192px; - max-height: 192px; -} - -.ant-skeleton-element .ant-skeleton-image-svg.ant-skeleton-image-circle { - border-radius: 50%; -} - -@-webkit-keyframes ant-skeleton-loading { - 0% { - background-position: 100% 50%; - } - 100% { - background-position: 0 50%; - } -} - -@keyframes ant-skeleton-loading { - 0% { - background-position: 100% 50%; - } - 100% { - background-position: 0 50%; - } -} - -.ant-slider { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - height: 12px; - margin: 10px 6px 10px; - padding: 4px 0; - cursor: pointer; - touch-action: none; -} - -.ant-slider-vertical { - width: 12px; - height: 100%; - margin: 6px 10px; - padding: 0 4px; -} - -.ant-slider-vertical .ant-slider-rail { - width: 4px; - height: 100%; -} - -.ant-slider-vertical .ant-slider-track { - width: 4px; -} - -.ant-slider-vertical .ant-slider-handle { - margin-top: -6px; - margin-left: -5px; -} - -.ant-slider-vertical .ant-slider-mark { - top: 0; - left: 12px; - width: 18px; - height: 100%; -} - -.ant-slider-vertical .ant-slider-mark-text { - left: 4px; - white-space: nowrap; -} - -.ant-slider-vertical .ant-slider-step { - width: 4px; - height: 100%; -} - -.ant-slider-vertical .ant-slider-dot { - top: auto; - left: 2px; - margin-bottom: -4px; -} - -.ant-slider-tooltip .ant-tooltip-inner { - min-width: unset; -} - -.ant-slider-with-marks { - margin-bottom: 28px; -} - -.ant-slider-rail { - position: absolute; - width: 100%; - height: 4px; - background-color: #f5f5f5; - border-radius: 2px; - transition: background-color 0.3s; -} - -.ant-slider-track { - position: absolute; - height: 4px; - background-color: #91d5ff; - border-radius: 2px; - transition: background-color 0.3s; -} - -.ant-slider-handle { - position: absolute; - width: 14px; - height: 14px; - margin-top: -5px; - background-color: #fff; - border: solid 2px #91d5ff; - border-radius: 50%; - box-shadow: 0; - cursor: pointer; - transition: border-color 0.3s, box-shadow 0.6s, transform 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.28); -} - -.ant-slider-handle-dragging.ant-slider-handle-dragging.ant-slider-handle-dragging { - border-color: #46a6ff; - box-shadow: 0 0 0 5px rgba(24, 144, 255, 0.12); -} - -.ant-slider-handle:focus { - border-color: #46a6ff; - outline: none; - box-shadow: 0 0 0 5px rgba(24, 144, 255, 0.12); -} - -.ant-slider-handle.ant-tooltip-open { - border-color: #1890ff; -} - -.ant-slider:hover .ant-slider-rail { - background-color: #e1e1e1; -} - -.ant-slider:hover .ant-slider-track { - background-color: #69c0ff; -} - -.ant-slider:hover .ant-slider-handle:not(.ant-tooltip-open) { - border-color: #69c0ff; -} - -.ant-slider-mark { - position: absolute; - top: 14px; - left: 0; - width: 100%; - font-size: 14px; -} - -.ant-slider-mark-text { - position: absolute; - display: inline-block; - color: rgba(0, 0, 0, 0.45); - text-align: center; - word-break: keep-all; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-slider-mark-text-active { - color: rgba(0, 0, 0, 0.85); -} - -.ant-slider-step { - position: absolute; - width: 100%; - height: 4px; - background: transparent; -} - -.ant-slider-dot { - position: absolute; - top: -2px; - width: 8px; - height: 8px; - margin-left: -4px; - background-color: #fff; - border: 2px solid #f0f0f0; - border-radius: 50%; - cursor: pointer; -} - -.ant-slider-dot:first-child { - margin-left: -4px; -} - -.ant-slider-dot:last-child { - margin-left: -4px; -} - -.ant-slider-dot-active { - border-color: #8cc8ff; -} - -.ant-slider-disabled { - cursor: not-allowed; -} - -.ant-slider-disabled .ant-slider-track { - background-color: rgba(0, 0, 0, 0.25) !important; -} - -.ant-slider-disabled .ant-slider-handle, -.ant-slider-disabled .ant-slider-dot { - background-color: #fff; - border-color: rgba(0, 0, 0, 0.25) !important; - box-shadow: none; - cursor: not-allowed; -} - -.ant-slider-disabled .ant-slider-mark-text, -.ant-slider-disabled .ant-slider-dot { - cursor: not-allowed !important; -} - -.ant-space { - display: inline-flex; -} - -.ant-space-vertical { - flex-direction: column; -} - -.ant-space-align-center { - align-items: center; -} - -.ant-space-align-start { - align-items: flex-start; -} - -.ant-space-align-end { - align-items: flex-end; -} - -.ant-space-align-baseline { - align-items: baseline; -} - -.ant-space-item:empty { - display: none; -} - -.ant-statistic { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; -} - -.ant-statistic-title { - margin-bottom: 4px; - color: rgba(0, 0, 0, 0.45); - font-size: 14px; -} - -.ant-statistic-content { - color: rgba(0, 0, 0, 0.85); - font-size: 24px; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; -} - -.ant-statistic-content-value { - display: inline-block; - direction: ltr; -} - -.ant-statistic-content-prefix, -.ant-statistic-content-suffix { - display: inline-block; -} - -.ant-statistic-content-prefix { - margin-right: 4px; -} - -.ant-statistic-content-suffix { - margin-left: 4px; -} - -.ant-steps { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - display: flex; - width: 100%; - font-size: 0; - text-align: initial; -} - -.ant-steps-item { - position: relative; - display: inline-block; - flex: 1; - overflow: hidden; - vertical-align: top; -} - -.ant-steps-item-container { - outline: none; -} - -.ant-steps-item:last-child { - flex: none; -} - -.ant-steps-item:last-child > .ant-steps-item-container > .ant-steps-item-tail, -.ant-steps-item:last-child > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title::after { - display: none; -} - -.ant-steps-item-icon, -.ant-steps-item-content { - display: inline-block; - vertical-align: top; -} - -.ant-steps-item-icon { - width: 32px; - height: 32px; - margin: 0 8px 0 0; - font-size: 16px; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; - line-height: 32px; - text-align: center; - border: 1px solid rgba(0, 0, 0, 0.25); - border-radius: 32px; - transition: background-color 0.3s, border-color 0.3s; -} - -.ant-steps-item-icon .ant-steps-icon { - position: relative; - top: -0.5px; - color: #1890ff; - line-height: 1; -} - -.ant-steps-item-tail { - position: absolute; - top: 12px; - left: 0; - width: 100%; - padding: 0 10px; -} - -.ant-steps-item-tail::after { - display: inline-block; - width: 100%; - height: 1px; - background: #f0f0f0; - border-radius: 1px; - transition: background 0.3s; - content: ''; -} - -.ant-steps-item-title { - position: relative; - display: inline-block; - padding-right: 16px; - color: rgba(0, 0, 0, 0.85); - font-size: 16px; - line-height: 32px; -} - -.ant-steps-item-title::after { - position: absolute; - top: 16px; - left: 100%; - display: block; - width: 9999px; - height: 1px; - background: #f0f0f0; - content: ''; -} - -.ant-steps-item-subtitle { - display: inline; - margin-left: 8px; - color: rgba(0, 0, 0, 0.45); - font-weight: normal; - font-size: 14px; -} - -.ant-steps-item-description { - color: rgba(0, 0, 0, 0.45); - font-size: 14px; -} - -.ant-steps-item-wait .ant-steps-item-icon { - background-color: #fff; - border-color: rgba(0, 0, 0, 0.25); -} - -.ant-steps-item-wait .ant-steps-item-icon > .ant-steps-icon { - color: rgba(0, 0, 0, 0.25); -} - -.ant-steps-item-wait .ant-steps-item-icon > .ant-steps-icon .ant-steps-icon-dot { - background: rgba(0, 0, 0, 0.25); -} - -.ant-steps-item-wait > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title { - color: rgba(0, 0, 0, 0.45); -} - -.ant-steps-item-wait > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title::after { - background-color: #f0f0f0; -} - -.ant-steps-item-wait > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-description { - color: rgba(0, 0, 0, 0.45); -} - -.ant-steps-item-wait > .ant-steps-item-container > .ant-steps-item-tail::after { - background-color: #f0f0f0; -} - -.ant-steps-item-process .ant-steps-item-icon { - background-color: #fff; - border-color: #1890ff; -} - -.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon { - color: #1890ff; -} - -.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon .ant-steps-icon-dot { - background: #1890ff; -} - -.ant-steps-item-process > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title { - color: rgba(0, 0, 0, 0.85); -} - -.ant-steps-item-process > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title::after { - background-color: #f0f0f0; -} - -.ant-steps-item-process > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-description { - color: rgba(0, 0, 0, 0.85); -} - -.ant-steps-item-process > .ant-steps-item-container > .ant-steps-item-tail::after { - background-color: #f0f0f0; -} - -.ant-steps-item-process > .ant-steps-item-container > .ant-steps-item-icon { - background: #1890ff; -} - -.ant-steps-item-process > .ant-steps-item-container > .ant-steps-item-icon .ant-steps-icon { - color: #fff; -} - -.ant-steps-item-process > .ant-steps-item-container > .ant-steps-item-title { - font-weight: 500; -} - -.ant-steps-item-finish .ant-steps-item-icon { - background-color: #fff; - border-color: #1890ff; -} - -.ant-steps-item-finish .ant-steps-item-icon > .ant-steps-icon { - color: #1890ff; -} - -.ant-steps-item-finish .ant-steps-item-icon > .ant-steps-icon .ant-steps-icon-dot { - background: #1890ff; -} - -.ant-steps-item-finish > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title { - color: rgba(0, 0, 0, 0.85); -} - -.ant-steps-item-finish > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title::after { - background-color: #1890ff; -} - -.ant-steps-item-finish > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-description { - color: rgba(0, 0, 0, 0.45); -} - -.ant-steps-item-finish > .ant-steps-item-container > .ant-steps-item-tail::after { - background-color: #1890ff; -} - -.ant-steps-item-error .ant-steps-item-icon { - background-color: #fff; - border-color: #ff4d4f; -} - -.ant-steps-item-error .ant-steps-item-icon > .ant-steps-icon { - color: #ff4d4f; -} - -.ant-steps-item-error .ant-steps-item-icon > .ant-steps-icon .ant-steps-icon-dot { - background: #ff4d4f; -} - -.ant-steps-item-error > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title { - color: #ff4d4f; -} - -.ant-steps-item-error > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title::after { - background-color: #f0f0f0; -} - -.ant-steps-item-error > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-description { - color: #ff4d4f; -} - -.ant-steps-item-error > .ant-steps-item-container > .ant-steps-item-tail::after { - background-color: #f0f0f0; -} - -.ant-steps-item.ant-steps-next-error .ant-steps-item-title::after { - background: #ff4d4f; -} - -.ant-steps-item-disabled { - cursor: not-allowed; -} - -.ant-steps .ant-steps-item:not(.ant-steps-item-active) > .ant-steps-item-container[role='button'] { - cursor: pointer; -} - -.ant-steps .ant-steps-item:not(.ant-steps-item-active) > .ant-steps-item-container[role='button'] .ant-steps-item-title, -.ant-steps .ant-steps-item:not(.ant-steps-item-active) > .ant-steps-item-container[role='button'] .ant-steps-item-subtitle, -.ant-steps .ant-steps-item:not(.ant-steps-item-active) > .ant-steps-item-container[role='button'] .ant-steps-item-description, -.ant-steps .ant-steps-item:not(.ant-steps-item-active) > .ant-steps-item-container[role='button'] .ant-steps-item-icon .ant-steps-icon { - transition: color 0.3s; -} - -.ant-steps .ant-steps-item:not(.ant-steps-item-active) > .ant-steps-item-container[role='button']:hover .ant-steps-item-title, -.ant-steps .ant-steps-item:not(.ant-steps-item-active) > .ant-steps-item-container[role='button']:hover .ant-steps-item-subtitle, -.ant-steps .ant-steps-item:not(.ant-steps-item-active) > .ant-steps-item-container[role='button']:hover .ant-steps-item-description { - color: #1890ff; -} - -.ant-steps .ant-steps-item:not(.ant-steps-item-active):not(.ant-steps-item-process) > .ant-steps-item-container[role='button']:hover .ant-steps-item-icon { - border-color: #1890ff; -} - -.ant-steps .ant-steps-item:not(.ant-steps-item-active):not(.ant-steps-item-process) > .ant-steps-item-container[role='button']:hover .ant-steps-item-icon .ant-steps-icon { - color: #1890ff; -} - -.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item { - padding-left: 16px; - white-space: nowrap; -} - -.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:first-child { - padding-left: 0; -} - -.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:last-child .ant-steps-item-title { - padding-right: 0; -} - -.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item-tail { - display: none; -} - -.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item-description { - max-width: 140px; - white-space: normal; -} - -.ant-steps-item-custom > .ant-steps-item-container > .ant-steps-item-icon { - height: auto; - background: none; - border: 0; -} - -.ant-steps-item-custom > .ant-steps-item-container > .ant-steps-item-icon > .ant-steps-icon { - top: 0px; - left: 0.5px; - width: 32px; - height: 32px; - font-size: 24px; - line-height: 32px; -} - -.ant-steps-item-custom.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon { - color: #1890ff; -} - -.ant-steps:not(.ant-steps-vertical) .ant-steps-item-custom .ant-steps-item-icon { - width: auto; - background: none; -} - -.ant-steps-small.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item { - padding-left: 12px; -} - -.ant-steps-small.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:first-child { - padding-left: 0; -} - -.ant-steps-small .ant-steps-item-icon { - width: 24px; - height: 24px; - margin: 0 8px 0 0; - font-size: 12px; - line-height: 24px; - text-align: center; - border-radius: 24px; -} - -.ant-steps-small .ant-steps-item-title { - padding-right: 12px; - font-size: 14px; - line-height: 24px; -} - -.ant-steps-small .ant-steps-item-title::after { - top: 12px; -} - -.ant-steps-small .ant-steps-item-description { - color: rgba(0, 0, 0, 0.45); - font-size: 14px; -} - -.ant-steps-small .ant-steps-item-tail { - top: 8px; -} - -.ant-steps-small .ant-steps-item-custom .ant-steps-item-icon { - width: inherit; - height: inherit; - line-height: inherit; - background: none; - border: 0; - border-radius: 0; -} - -.ant-steps-small .ant-steps-item-custom .ant-steps-item-icon > .ant-steps-icon { - font-size: 24px; - line-height: 24px; - transform: none; -} - -.ant-steps-vertical { - display: flex; - flex-direction: column; -} - -.ant-steps-vertical > .ant-steps-item { - display: block; - flex: 1 0 auto; - padding-left: 0; - overflow: visible; -} - -.ant-steps-vertical > .ant-steps-item .ant-steps-item-icon { - float: left; - margin-right: 16px; -} - -.ant-steps-vertical > .ant-steps-item .ant-steps-item-content { - display: block; - min-height: 48px; - overflow: hidden; -} - -.ant-steps-vertical > .ant-steps-item .ant-steps-item-title { - line-height: 32px; -} - -.ant-steps-vertical > .ant-steps-item .ant-steps-item-description { - padding-bottom: 12px; -} - -.ant-steps-vertical > .ant-steps-item > .ant-steps-item-container > .ant-steps-item-tail { - position: absolute; - top: 0; - left: 16px; - width: 1px; - height: 100%; - padding: 38px 0 6px; -} - -.ant-steps-vertical > .ant-steps-item > .ant-steps-item-container > .ant-steps-item-tail::after { - width: 1px; - height: 100%; -} - -.ant-steps-vertical > .ant-steps-item:not(:last-child) > .ant-steps-item-container > .ant-steps-item-tail { - display: block; -} - -.ant-steps-vertical > .ant-steps-item > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title::after { - display: none; -} - -.ant-steps-vertical.ant-steps-small .ant-steps-item-container .ant-steps-item-tail { - position: absolute; - top: 0; - left: 12px; - padding: 30px 0 6px; -} - -.ant-steps-vertical.ant-steps-small .ant-steps-item-container .ant-steps-item-title { - line-height: 24px; -} - -.ant-steps-label-vertical .ant-steps-item { - overflow: visible; -} - -.ant-steps-label-vertical .ant-steps-item-tail { - margin-left: 58px; - padding: 3.5px 24px; -} - -.ant-steps-label-vertical .ant-steps-item-content { - display: block; - width: 116px; - margin-top: 8px; - text-align: center; -} - -.ant-steps-label-vertical .ant-steps-item-icon { - display: inline-block; - margin-left: 42px; -} - -.ant-steps-label-vertical .ant-steps-item-title { - padding-right: 0; - padding-left: 0; -} - -.ant-steps-label-vertical .ant-steps-item-title::after { - display: none; -} - -.ant-steps-label-vertical .ant-steps-item-subtitle { - display: block; - margin-bottom: 4px; - margin-left: 0; - line-height: 1.5715; -} - -.ant-steps-label-vertical.ant-steps-small:not(.ant-steps-dot) .ant-steps-item-icon { - margin-left: 46px; -} - -.ant-steps-dot .ant-steps-item-title, -.ant-steps-dot.ant-steps-small .ant-steps-item-title { - line-height: 1.5715; -} - -.ant-steps-dot .ant-steps-item-tail, -.ant-steps-dot.ant-steps-small .ant-steps-item-tail { - top: 2px; - width: 100%; - margin: 0 0 0 70px; - padding: 0; -} - -.ant-steps-dot .ant-steps-item-tail::after, -.ant-steps-dot.ant-steps-small .ant-steps-item-tail::after { - width: calc(100% - 20px); - height: 3px; - margin-left: 12px; -} - -.ant-steps-dot .ant-steps-item:first-child .ant-steps-icon-dot, -.ant-steps-dot.ant-steps-small .ant-steps-item:first-child .ant-steps-icon-dot { - left: 2px; -} - -.ant-steps-dot .ant-steps-item-icon, -.ant-steps-dot.ant-steps-small .ant-steps-item-icon { - width: 8px; - height: 8px; - margin-left: 67px; - padding-right: 0; - line-height: 8px; - background: transparent; - border: 0; -} - -.ant-steps-dot .ant-steps-item-icon .ant-steps-icon-dot, -.ant-steps-dot.ant-steps-small .ant-steps-item-icon .ant-steps-icon-dot { - position: relative; - float: left; - width: 100%; - height: 100%; - border-radius: 100px; - transition: all 0.3s; - /* expand hover area */ -} - -.ant-steps-dot .ant-steps-item-icon .ant-steps-icon-dot::after, -.ant-steps-dot.ant-steps-small .ant-steps-item-icon .ant-steps-icon-dot::after { - position: absolute; - top: -12px; - left: -26px; - width: 60px; - height: 32px; - background: rgba(0, 0, 0, 0.001); - content: ''; -} - -.ant-steps-dot .ant-steps-item-content, -.ant-steps-dot.ant-steps-small .ant-steps-item-content { - width: 140px; -} - -.ant-steps-dot .ant-steps-item-process .ant-steps-item-icon, -.ant-steps-dot.ant-steps-small .ant-steps-item-process .ant-steps-item-icon { - position: relative; - top: -1px; - width: 10px; - height: 10px; - line-height: 10px; - background: none; -} - -.ant-steps-dot .ant-steps-item-process .ant-steps-icon:first-child .ant-steps-icon-dot, -.ant-steps-dot.ant-steps-small .ant-steps-item-process .ant-steps-icon:first-child .ant-steps-icon-dot { - left: 0; -} - -.ant-steps-vertical.ant-steps-dot .ant-steps-item-icon { - margin-top: 13px; - margin-left: 0; - background: none; -} - -.ant-steps-vertical.ant-steps-dot .ant-steps-item > .ant-steps-item-container > .ant-steps-item-tail { - top: 6.5px; - left: -9px; - margin: 0; - padding: 22px 0 4px; -} - -.ant-steps-vertical.ant-steps-dot .ant-steps-item:first-child .ant-steps-icon-dot { - left: 0; -} - -.ant-steps-vertical.ant-steps-dot .ant-steps-item-content { - width: inherit; -} - -.ant-steps-vertical.ant-steps-dot .ant-steps-item-process .ant-steps-item-container .ant-steps-item-icon .ant-steps-icon-dot { - top: -1px; - left: -1px; -} - -.ant-steps-navigation { - padding-top: 12px; -} - -.ant-steps-navigation.ant-steps-small .ant-steps-item-container { - margin-left: -12px; -} - -.ant-steps-navigation .ant-steps-item { - overflow: visible; - text-align: center; -} - -.ant-steps-navigation .ant-steps-item-container { - display: inline-block; - height: 100%; - margin-left: -16px; - padding-bottom: 12px; - text-align: left; - transition: opacity 0.3s; -} - -.ant-steps-navigation .ant-steps-item-container .ant-steps-item-content { - max-width: auto; -} - -.ant-steps-navigation .ant-steps-item-container .ant-steps-item-title { - max-width: 100%; - padding-right: 0; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - -.ant-steps-navigation .ant-steps-item-container .ant-steps-item-title::after { - display: none; -} - -.ant-steps-navigation .ant-steps-item:not(.ant-steps-item-active) .ant-steps-item-container[role='button'] { - cursor: pointer; -} - -.ant-steps-navigation .ant-steps-item:not(.ant-steps-item-active) .ant-steps-item-container[role='button']:hover { - opacity: 0.85; -} - -.ant-steps-navigation .ant-steps-item:last-child { - flex: 1; -} - -.ant-steps-navigation .ant-steps-item:last-child::after { - display: none; -} - -.ant-steps-navigation .ant-steps-item::after { - position: absolute; - top: 50%; - left: 100%; - display: inline-block; - width: 12px; - height: 12px; - margin-top: -14px; - margin-left: -2px; - border: 1px solid rgba(0, 0, 0, 0.25); - border-bottom: none; - border-left: none; - transform: rotate(45deg); - content: ''; -} - -.ant-steps-navigation .ant-steps-item::before { - position: absolute; - bottom: 0; - left: 50%; - display: inline-block; - width: 0; - height: 2px; - background-color: #1890ff; - transition: width 0.3s, left 0.3s; - transition-timing-function: ease-out; - content: ''; -} - -.ant-steps-navigation .ant-steps-item.ant-steps-item-active::before { - left: 0; - width: 100%; -} - -.ant-steps-navigation.ant-steps-vertical > .ant-steps-item { - margin-right: 0 !important; -} - -.ant-steps-navigation.ant-steps-vertical > .ant-steps-item::before { - display: none; -} - -.ant-steps-navigation.ant-steps-vertical > .ant-steps-item.ant-steps-item-active::before { - top: 0; - right: 0; - left: unset; - display: block; - width: 3px; - height: calc(100% - 24px); -} - -.ant-steps-navigation.ant-steps-vertical > .ant-steps-item::after { - position: relative; - top: -2px; - left: 50%; - display: block; - width: 8px; - height: 8px; - margin-bottom: 8px; - text-align: center; - transform: rotate(135deg); -} - -.ant-steps-navigation.ant-steps-vertical > .ant-steps-item > .ant-steps-item-container > .ant-steps-item-tail { - visibility: hidden; -} - -.ant-steps-with-progress .ant-steps-item { - padding-top: 4px; -} - -.ant-steps-with-progress .ant-steps-item .ant-steps-item-tail { - top: 4px !important; -} - -.ant-steps-with-progress.ant-steps-horizontal .ant-steps-item:first-child { - padding-bottom: 4px; - padding-left: 4px; -} - -.ant-steps-with-progress .ant-steps-item-icon { - position: relative; -} - -.ant-steps-with-progress .ant-steps-item-icon .ant-progress { - position: absolute; - top: -5px; - right: -5px; - bottom: -5px; - left: -5px; -} - -.ant-switch { - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - display: inline-block; - box-sizing: border-box; - min-width: 44px; - height: 22px; - line-height: 22px; - vertical-align: middle; - background-color: rgba(0, 0, 0, 0.25); - border: 0; - border-radius: 100px; - cursor: pointer; - transition: all 0.2s; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-switch:focus { - outline: 0; - box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1); -} - -.ant-switch-checked:focus { - box-shadow: 0 0 0 2px #e6f7ff; -} - -.ant-switch:focus:hover { - box-shadow: none; -} - -.ant-switch-checked { - background-color: #1890ff; -} - -.ant-switch-loading, -.ant-switch-disabled { - cursor: not-allowed; - opacity: 0.4; -} - -.ant-switch-loading *, -.ant-switch-disabled * { - box-shadow: none; - cursor: not-allowed; -} - -.ant-switch-inner { - display: block; - margin: 0 7px 0 25px; - color: #fff; - font-size: 12px; - transition: margin 0.2s; -} - -.ant-switch-checked .ant-switch-inner { - margin: 0 25px 0 7px; -} - -.ant-switch-handle { - position: absolute; - top: 2px; - left: 2px; - width: 18px; - height: 18px; - transition: all 0.2s ease-in-out; -} - -.ant-switch-handle::before { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-color: #fff; - border-radius: 9px; - box-shadow: 0 2px 4px 0 rgba(0, 35, 11, 0.2); - transition: all 0.2s ease-in-out; - content: ''; -} - -.ant-switch-checked .ant-switch-handle { - left: calc(100% - 18px - 2px); -} - -.ant-switch:not(.ant-switch-disabled):active .ant-switch-handle::before { - right: -30%; - left: 0; -} - -.ant-switch:not(.ant-switch-disabled):active.ant-switch-checked .ant-switch-handle::before { - right: 0; - left: -30%; -} - -.ant-switch-loading-icon.anticon { - position: relative; - top: 2px; - color: rgba(0, 0, 0, 0.65); - vertical-align: top; -} - -.ant-switch-checked .ant-switch-loading-icon { - color: #1890ff; -} - -.ant-switch-small { - min-width: 28px; - height: 16px; - line-height: 16px; -} - -.ant-switch-small .ant-switch-inner { - margin: 0 5px 0 18px; - font-size: 12px; -} - -.ant-switch-small .ant-switch-handle { - width: 12px; - height: 12px; -} - -.ant-switch-small .ant-switch-loading-icon { - top: 1.5px; - font-size: 9px; -} - -.ant-switch-small.ant-switch-checked .ant-switch-inner { - margin: 0 18px 0 5px; -} - -.ant-switch-small.ant-switch-checked .ant-switch-handle { - left: calc(100% - 12px - 2px); -} - -.ant-table.ant-table-middle { - font-size: 14px; -} - -.ant-table.ant-table-middle .ant-table-title, -.ant-table.ant-table-middle .ant-table-footer, -.ant-table.ant-table-middle .ant-table-thead > tr > th, -.ant-table.ant-table-middle .ant-table-tbody > tr > td, -.ant-table.ant-table-middle tfoot > tr > th, -.ant-table.ant-table-middle tfoot > tr > td { - padding: 12px 8px; -} - -.ant-table.ant-table-middle .ant-table-filter-trigger { - margin-right: -4px; -} - -.ant-table.ant-table-middle .ant-table-expanded-row-fixed { - margin: -12px -8px; -} - -.ant-table.ant-table-middle .ant-table-tbody .ant-table-wrapper:only-child .ant-table { - margin: -12px -8px -12px 25px; -} - -.ant-table.ant-table-small { - font-size: 14px; -} - -.ant-table.ant-table-small .ant-table-title, -.ant-table.ant-table-small .ant-table-footer, -.ant-table.ant-table-small .ant-table-thead > tr > th, -.ant-table.ant-table-small .ant-table-tbody > tr > td, -.ant-table.ant-table-small tfoot > tr > th, -.ant-table.ant-table-small tfoot > tr > td { - padding: 8px 8px; -} - -.ant-table.ant-table-small .ant-table-filter-trigger { - margin-right: -4px; -} - -.ant-table.ant-table-small .ant-table-expanded-row-fixed { - margin: -8px -8px; -} - -.ant-table.ant-table-small .ant-table-tbody .ant-table-wrapper:only-child .ant-table { - margin: -8px -8px -8px 25px; -} - -.ant-table-small .ant-table-thead > tr > th { - background-color: #fafafa; -} - -.ant-table-small .ant-table-selection-column { - width: 46px; - min-width: 46px; -} - -.ant-table.ant-table-bordered > .ant-table-title { - border: 1px solid #f0f0f0; - border-bottom: 0; -} - -.ant-table.ant-table-bordered > .ant-table-container { - border-left: 1px solid #f0f0f0; -} - -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > thead > tr > th, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > thead > tr > th, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > thead > tr > th, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > thead > tr > th, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > tbody > tr > td, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > tbody > tr > td, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > tbody > tr > td, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > tbody > tr > td, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > tfoot > tr > th, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > tfoot > tr > th, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > tfoot > tr > th, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > tfoot > tr > th, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > tfoot > tr > td, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > tfoot > tr > td, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > tfoot > tr > td, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > tfoot > tr > td { - border-right: 1px solid #f0f0f0; -} - -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > thead > tr:not(:last-child) > th, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > thead > tr:not(:last-child) > th, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > thead > tr:not(:last-child) > th, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > thead > tr:not(:last-child) > th { - border-bottom: 1px solid #f0f0f0; -} - -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > thead > tr > th::before, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > thead > tr > th::before, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > thead > tr > th::before, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > thead > tr > th::before { - background-color: transparent !important; -} - -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > thead > tr > .ant-table-cell-fix-right-first::after, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > thead > tr > .ant-table-cell-fix-right-first::after, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > thead > tr > .ant-table-cell-fix-right-first::after, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > thead > tr > .ant-table-cell-fix-right-first::after, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > tbody > tr > .ant-table-cell-fix-right-first::after, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > tbody > tr > .ant-table-cell-fix-right-first::after, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > tbody > tr > .ant-table-cell-fix-right-first::after, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > tbody > tr > .ant-table-cell-fix-right-first::after, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > tfoot > tr > .ant-table-cell-fix-right-first::after, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > tfoot > tr > .ant-table-cell-fix-right-first::after, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > tfoot > tr > .ant-table-cell-fix-right-first::after, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > tfoot > tr > .ant-table-cell-fix-right-first::after { - border-right: 1px solid #f0f0f0; -} - -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > tbody > tr > td > .ant-table-expanded-row-fixed, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > tbody > tr > td > .ant-table-expanded-row-fixed, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > tbody > tr > td > .ant-table-expanded-row-fixed, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > tbody > tr > td > .ant-table-expanded-row-fixed { - margin: -16px -17px; -} - -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > tbody > tr > td > .ant-table-expanded-row-fixed::after, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > tbody > tr > td > .ant-table-expanded-row-fixed::after, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > tbody > tr > td > .ant-table-expanded-row-fixed::after, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > tbody > tr > td > .ant-table-expanded-row-fixed::after { - position: absolute; - top: 0; - right: 1px; - bottom: 0; - border-right: 1px solid #f0f0f0; - content: ''; -} - -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table, -.ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table { - border-top: 1px solid #f0f0f0; -} - -.ant-table.ant-table-bordered.ant-table-scroll-horizontal > .ant-table-container > .ant-table-body > table > tbody > tr.ant-table-expanded-row > td, -.ant-table.ant-table-bordered.ant-table-scroll-horizontal > .ant-table-container > .ant-table-body > table > tbody > tr.ant-table-placeholder > td { - border-right: 0; -} - -.ant-table.ant-table-bordered.ant-table-middle > .ant-table-container > .ant-table-content > table > tbody > tr > td > .ant-table-expanded-row-fixed, -.ant-table.ant-table-bordered.ant-table-middle > .ant-table-container > .ant-table-body > table > tbody > tr > td > .ant-table-expanded-row-fixed { - margin: -12px -9px; -} - -.ant-table.ant-table-bordered.ant-table-small > .ant-table-container > .ant-table-content > table > tbody > tr > td > .ant-table-expanded-row-fixed, -.ant-table.ant-table-bordered.ant-table-small > .ant-table-container > .ant-table-body > table > tbody > tr > td > .ant-table-expanded-row-fixed { - margin: -8px -9px; -} - -.ant-table.ant-table-bordered > .ant-table-footer { - border: 1px solid #f0f0f0; - border-top: 0; -} - -.ant-table-cell .ant-table-container:first-child { - border-top: 0; -} - -.ant-table-cell-scrollbar { - box-shadow: 0 1px 0 1px #fafafa; -} - -.ant-table-wrapper { - clear: both; - max-width: 100%; -} - -.ant-table-wrapper::before { - display: table; - content: ''; -} - -.ant-table-wrapper::after { - display: table; - clear: both; - content: ''; -} - -.ant-table { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - font-size: 14px; - background: #fff; - border-radius: 2px; -} - -.ant-table table { - width: 100%; - text-align: left; - border-radius: 2px 2px 0 0; - border-collapse: separate; - border-spacing: 0; -} - -.ant-table-thead > tr > th, -.ant-table-tbody > tr > td, -.ant-table tfoot > tr > th, -.ant-table tfoot > tr > td { - position: relative; - padding: 16px 16px; - overflow-wrap: break-word; -} - -.ant-table-cell-ellipsis { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - word-break: keep-all; -} - -.ant-table-cell-ellipsis.ant-table-cell-fix-left-last, -.ant-table-cell-ellipsis.ant-table-cell-fix-right-first { - overflow: visible; -} - -.ant-table-cell-ellipsis.ant-table-cell-fix-left-last .ant-table-cell-content, -.ant-table-cell-ellipsis.ant-table-cell-fix-right-first .ant-table-cell-content { - display: block; - overflow: hidden; - text-overflow: ellipsis; -} - -.ant-table-cell-ellipsis .ant-table-column-title { - overflow: hidden; - text-overflow: ellipsis; - word-break: keep-all; -} - -.ant-table-title { - padding: 16px 16px; -} - -.ant-table-footer { - padding: 16px 16px; - color: rgba(0, 0, 0, 0.85); - background: #fafafa; -} - -.ant-table-thead > tr > th { - position: relative; - color: rgba(0, 0, 0, 0.85); - font-weight: 500; - text-align: left; - background: #fafafa; - border-bottom: 1px solid #f0f0f0; - transition: background 0.3s ease; -} - -.ant-table-thead > tr > th[colspan]:not([colspan='1']) { - text-align: center; -} - -.ant-table-thead > tr > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { - position: absolute; - top: 50%; - right: 0; - width: 1px; - height: 1.6em; - background-color: rgba(0, 0, 0, 0.06); - transform: translateY(-50%); - transition: background-color 0.3s; - content: ''; -} - -.ant-table-thead > tr:not(:last-child) > th[colspan] { - border-bottom: 0; -} - -.ant-table-tbody > tr > td { - border-bottom: 1px solid #f0f0f0; - transition: background 0.3s; -} - -.ant-table-tbody > tr > td > .ant-table-wrapper:only-child .ant-table, -.ant-table-tbody > tr > td > .ant-table-expanded-row-fixed > .ant-table-wrapper:only-child .ant-table { - margin: -16px -16px -16px 33px; -} - -.ant-table-tbody > tr > td > .ant-table-wrapper:only-child .ant-table-tbody > tr:last-child > td, -.ant-table-tbody > tr > td > .ant-table-expanded-row-fixed > .ant-table-wrapper:only-child .ant-table-tbody > tr:last-child > td { - border-bottom: 0; -} - -.ant-table-tbody > tr > td > .ant-table-wrapper:only-child .ant-table-tbody > tr:last-child > td:first-child, -.ant-table-tbody > tr > td > .ant-table-expanded-row-fixed > .ant-table-wrapper:only-child .ant-table-tbody > tr:last-child > td:first-child, -.ant-table-tbody > tr > td > .ant-table-wrapper:only-child .ant-table-tbody > tr:last-child > td:last-child, -.ant-table-tbody > tr > td > .ant-table-expanded-row-fixed > .ant-table-wrapper:only-child .ant-table-tbody > tr:last-child > td:last-child { - border-radius: 0; -} - -.ant-table-tbody > tr.ant-table-row:hover > td, -.ant-table-tbody > tr > td.ant-table-cell-row-hover { - background: #fafafa; -} - -.ant-table-tbody > tr.ant-table-row-selected > td { - background: #e6f7ff; - border-color: rgba(0, 0, 0, 0.03); -} - -.ant-table-tbody > tr.ant-table-row-selected:hover > td { - background: #dcf4ff; -} - -.ant-table-summary { - position: relative; - z-index: 2; - background: #fff; -} - -div.ant-table-summary { - box-shadow: 0 -1px 0 #f0f0f0; -} - -.ant-table-summary > tr > th, -.ant-table-summary > tr > td { - border-bottom: 1px solid #f0f0f0; -} - -.ant-table-pagination.ant-pagination { - margin: 16px 0; -} - -.ant-table-pagination { - display: flex; - flex-wrap: wrap; - row-gap: 8px; -} - -.ant-table-pagination > * { - flex: none; -} - -.ant-table-pagination-left { - justify-content: flex-start; -} - -.ant-table-pagination-center { - justify-content: center; -} - -.ant-table-pagination-right { - justify-content: flex-end; -} - -.ant-table-thead th.ant-table-column-has-sorters { - cursor: pointer; - transition: all 0.3s; -} - -.ant-table-thead th.ant-table-column-has-sorters:hover { - background: rgba(0, 0, 0, 0.04); -} - -.ant-table-thead th.ant-table-column-has-sorters:hover::before { - background-color: transparent !important; -} - -.ant-table-thead th.ant-table-column-has-sorters.ant-table-cell-fix-left:hover, -.ant-table-thead th.ant-table-column-has-sorters.ant-table-cell-fix-right:hover { - background: #f5f5f5; -} - -.ant-table-thead th.ant-table-column-sort { - background: #f5f5f5; -} - -.ant-table-thead th.ant-table-column-sort::before { - background-color: transparent !important; -} - -td.ant-table-column-sort { - background: #fafafa; -} - -.ant-table-column-title { - position: relative; - z-index: 1; - flex: 1; -} - -.ant-table-column-sorters { - display: flex; - flex: auto; - align-items: center; - justify-content: space-between; -} - -.ant-table-column-sorters::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - content: ''; -} - -.ant-table-column-sorter { - margin-left: 4px; - color: #bfbfbf; - font-size: 0; - transition: color 0.3s; -} - -.ant-table-column-sorter-inner { - display: inline-flex; - flex-direction: column; - align-items: center; -} - -.ant-table-column-sorter-up, -.ant-table-column-sorter-down { - font-size: 11px; -} - -.ant-table-column-sorter-up.active, -.ant-table-column-sorter-down.active { - color: #1890ff; -} - -.ant-table-column-sorter-up + .ant-table-column-sorter-down { - margin-top: -0.3em; -} - -.ant-table-column-sorters:hover .ant-table-column-sorter { - color: #a6a6a6; -} - -.ant-table-filter-column { - display: flex; - justify-content: space-between; -} - -.ant-table-filter-trigger { - position: relative; - display: flex; - align-items: center; - margin: -4px -8px -4px 4px; - padding: 0 4px; - color: #bfbfbf; - font-size: 12px; - border-radius: 2px; - cursor: pointer; - transition: all 0.3s; -} - -.ant-table-filter-trigger:hover { - color: rgba(0, 0, 0, 0.45); - background: rgba(0, 0, 0, 0.04); -} - -.ant-table-filter-trigger.active { - color: #1890ff; -} - -.ant-table-filter-dropdown { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - min-width: 120px; - background-color: #fff; - border-radius: 2px; - box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); -} - -.ant-table-filter-dropdown .ant-dropdown-menu { - max-height: 264px; - overflow-x: hidden; - border: 0; - box-shadow: none; -} - -.ant-table-filter-dropdown .ant-dropdown-menu:empty::after { - display: block; - padding: 8px 0; - color: rgba(0, 0, 0, 0.25); - font-size: 12px; - text-align: center; - content: 'Not Found'; -} - -.ant-table-filter-dropdown-tree { - padding: 8px 8px 0; -} - -.ant-table-filter-dropdown-tree .ant-tree-treenode .ant-tree-node-content-wrapper:hover { - background-color: #f5f5f5; -} - -.ant-table-filter-dropdown-tree .ant-tree-treenode-checkbox-checked .ant-tree-node-content-wrapper, -.ant-table-filter-dropdown-tree .ant-tree-treenode-checkbox-checked .ant-tree-node-content-wrapper:hover { - background-color: #bae7ff; -} - -.ant-table-filter-dropdown-search { - padding: 8px; - border-bottom: 1px #f0f0f0 solid; -} - -.ant-table-filter-dropdown-search-input input { - min-width: 140px; -} - -.ant-table-filter-dropdown-search-input .anticon { - color: rgba(0, 0, 0, 0.25); -} - -.ant-table-filter-dropdown-checkall { - width: 100%; - margin-bottom: 4px; - margin-left: 4px; -} - -.ant-table-filter-dropdown-submenu > ul { - max-height: calc(100vh - 130px); - overflow-x: hidden; - overflow-y: auto; -} - -.ant-table-filter-dropdown .ant-checkbox-wrapper + span, -.ant-table-filter-dropdown-submenu .ant-checkbox-wrapper + span { - padding-left: 8px; -} - -.ant-table-filter-dropdown-btns { - display: flex; - justify-content: space-between; - padding: 7px 8px; - overflow: hidden; - background-color: inherit; - border-top: 1px solid #f0f0f0; -} - -.ant-table-selection-col { - width: 32px; -} - -.ant-table-bordered .ant-table-selection-col { - width: 50px; -} - -table tr th.ant-table-selection-column, -table tr td.ant-table-selection-column { - padding-right: 8px; - padding-left: 8px; - text-align: center; -} - -table tr th.ant-table-selection-column .ant-radio-wrapper, -table tr td.ant-table-selection-column .ant-radio-wrapper { - margin-right: 0; -} - -table tr th.ant-table-selection-column.ant-table-cell-fix-left { - z-index: 3; -} - -table tr th.ant-table-selection-column::after { - background-color: transparent !important; -} - -.ant-table-selection { - position: relative; - display: inline-flex; - flex-direction: column; -} - -.ant-table-selection-extra { - position: absolute; - top: 0; - z-index: 1; - cursor: pointer; - transition: all 0.3s; - -webkit-margin-start: 100%; - margin-inline-start: 100%; - -webkit-padding-start: 4px; - padding-inline-start: 4px; -} - -.ant-table-selection-extra .anticon { - color: #bfbfbf; - font-size: 10px; -} - -.ant-table-selection-extra .anticon:hover { - color: #a6a6a6; -} - -.ant-table-expand-icon-col { - width: 48px; -} - -.ant-table-row-expand-icon-cell { - text-align: center; -} - -.ant-table-row-indent { - float: left; - height: 1px; -} - -.ant-table-row-expand-icon { - color: #1890ff; - text-decoration: none; - cursor: pointer; - transition: color 0.3s; - position: relative; - display: inline-flex; - float: left; - box-sizing: border-box; - width: 17px; - height: 17px; - padding: 0; - color: inherit; - line-height: 17px; - background: #fff; - border: 1px solid #f0f0f0; - border-radius: 2px; - outline: none; - transform: scale(0.94117647); - transition: all 0.3s; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-table-row-expand-icon:focus, -.ant-table-row-expand-icon:hover { - color: #40a9ff; -} - -.ant-table-row-expand-icon:active { - color: #096dd9; -} - -.ant-table-row-expand-icon:focus, -.ant-table-row-expand-icon:hover, -.ant-table-row-expand-icon:active { - border-color: currentColor; -} - -.ant-table-row-expand-icon::before, -.ant-table-row-expand-icon::after { - position: absolute; - background: currentColor; - transition: transform 0.3s ease-out; - content: ''; -} - -.ant-table-row-expand-icon::before { - top: 7px; - right: 3px; - left: 3px; - height: 1px; -} - -.ant-table-row-expand-icon::after { - top: 3px; - bottom: 3px; - left: 7px; - width: 1px; - transform: rotate(90deg); -} - -.ant-table-row-expand-icon-collapsed::before { - transform: rotate(-180deg); -} - -.ant-table-row-expand-icon-collapsed::after { - transform: rotate(0deg); -} - -.ant-table-row-expand-icon-spaced { - background: transparent; - border: 0; - visibility: hidden; -} - -.ant-table-row-expand-icon-spaced::before, -.ant-table-row-expand-icon-spaced::after { - display: none; - content: none; -} - -.ant-table-row-indent + .ant-table-row-expand-icon { - margin-top: 2.5005px; - margin-right: 8px; -} - -tr.ant-table-expanded-row > td, -tr.ant-table-expanded-row:hover > td { - background: #fbfbfb; -} - -tr.ant-table-expanded-row .ant-descriptions-view { - display: flex; -} - -tr.ant-table-expanded-row .ant-descriptions-view table { - flex: auto; - width: auto; -} - -.ant-table .ant-table-expanded-row-fixed { - position: relative; - margin: -16px -16px; - padding: 16px 16px; -} - -.ant-table-tbody > tr.ant-table-placeholder { - text-align: center; -} - -.ant-table-empty .ant-table-tbody > tr.ant-table-placeholder { - color: rgba(0, 0, 0, 0.25); -} - -.ant-table-tbody > tr.ant-table-placeholder:hover > td { - background: #fff; -} - -.ant-table-cell-fix-left, -.ant-table-cell-fix-right { - position: -webkit-sticky !important; - position: sticky !important; - z-index: 2; - background: #fff; -} - -.ant-table-cell-fix-left-first::after, -.ant-table-cell-fix-left-last::after { - position: absolute; - top: 0; - right: 0; - bottom: -1px; - width: 30px; - transform: translateX(100%); - transition: box-shadow 0.3s; - content: ''; - pointer-events: none; -} - -.ant-table-cell-fix-right-first::after, -.ant-table-cell-fix-right-last::after { - position: absolute; - top: 0; - bottom: -1px; - left: 0; - width: 30px; - transform: translateX(-100%); - transition: box-shadow 0.3s; - content: ''; - pointer-events: none; -} - -.ant-table .ant-table-container::before, -.ant-table .ant-table-container::after { - position: absolute; - top: 0; - bottom: 0; - z-index: 1; - width: 30px; - transition: box-shadow 0.3s; - content: ''; - pointer-events: none; -} - -.ant-table .ant-table-container::before { - left: 0; -} - -.ant-table .ant-table-container::after { - right: 0; -} - -.ant-table-ping-left:not(.ant-table-has-fix-left) .ant-table-container { - position: relative; -} - -.ant-table-ping-left:not(.ant-table-has-fix-left) .ant-table-container::before { - box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, 0.15); -} - -.ant-table-ping-left .ant-table-cell-fix-left-first::after, -.ant-table-ping-left .ant-table-cell-fix-left-last::after { - box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, 0.15); -} - -.ant-table-ping-left .ant-table-cell-fix-left-last::before { - background-color: transparent !important; -} - -.ant-table-ping-right:not(.ant-table-has-fix-right) .ant-table-container { - position: relative; -} - -.ant-table-ping-right:not(.ant-table-has-fix-right) .ant-table-container::after { - box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, 0.15); -} - -.ant-table-ping-right .ant-table-cell-fix-right-first::after, -.ant-table-ping-right .ant-table-cell-fix-right-last::after { - box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, 0.15); -} - -.ant-table-sticky-holder { - position: -webkit-sticky; - position: sticky; - z-index: calc(2 + 1); - background: #fff; -} - -.ant-table-sticky-scroll { - position: -webkit-sticky; - position: sticky; - bottom: 0; - z-index: calc(2 + 1); - display: flex; - align-items: center; - background: #ffffff; - border-top: 1px solid #f0f0f0; - opacity: 0.6; -} - -.ant-table-sticky-scroll:hover { - transform-origin: center bottom; -} - -.ant-table-sticky-scroll-bar { - height: 8px; - background-color: rgba(0, 0, 0, 0.35); - border-radius: 4px; -} - -.ant-table-sticky-scroll-bar:hover { - background-color: rgba(0, 0, 0, 0.8); -} - -.ant-table-sticky-scroll-bar-active { - background-color: rgba(0, 0, 0, 0.8); -} - -@media all and (-ms-high-contrast: none) { - .ant-table-ping-left .ant-table-cell-fix-left-last::after { - box-shadow: none !important; - } - .ant-table-ping-right .ant-table-cell-fix-right-first::after { - box-shadow: none !important; - } -} - -.ant-table { - /* title + table */ - /* table */ - /* table + footer */ -} - -.ant-table-title { - border-radius: 2px 2px 0 0; -} - -.ant-table-title + .ant-table-container { - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.ant-table-title + .ant-table-container table > thead > tr:first-child th:first-child { - border-radius: 0; -} - -.ant-table-title + .ant-table-container table > thead > tr:first-child th:last-child { - border-radius: 0; -} - -.ant-table-container { - border-top-left-radius: 2px; - border-top-right-radius: 2px; -} - -.ant-table-container table > thead > tr:first-child th:first-child { - border-top-left-radius: 2px; -} - -.ant-table-container table > thead > tr:first-child th:last-child { - border-top-right-radius: 2px; -} - -.ant-table-footer { - border-radius: 0 0 2px 2px; -} - -@-webkit-keyframes antCheckboxEffect { - 0% { - transform: scale(1); - opacity: 0.5; - } - 100% { - transform: scale(1.6); - opacity: 0; - } -} - -@keyframes antCheckboxEffect { - 0% { - transform: scale(1); - opacity: 0.5; - } - 100% { - transform: scale(1.6); - opacity: 0; - } -} - -@-webkit-keyframes ant-tree-node-fx-do-not-use { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - -@keyframes ant-tree-node-fx-do-not-use { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - -.ant-tree.ant-tree-directory .ant-tree-treenode { - position: relative; -} - -.ant-tree.ant-tree-directory .ant-tree-treenode::before { - position: absolute; - top: 0; - right: 0; - bottom: 4px; - left: 0; - transition: background-color 0.3s; - content: ''; - pointer-events: none; -} - -.ant-tree.ant-tree-directory .ant-tree-treenode:hover::before { - background: #f5f5f5; -} - -.ant-tree.ant-tree-directory .ant-tree-treenode > * { - z-index: 1; -} - -.ant-tree.ant-tree-directory .ant-tree-treenode .ant-tree-switcher { - transition: color 0.3s; -} - -.ant-tree.ant-tree-directory .ant-tree-treenode .ant-tree-node-content-wrapper { - border-radius: 0; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-tree.ant-tree-directory .ant-tree-treenode .ant-tree-node-content-wrapper:hover { - background: transparent; -} - -.ant-tree.ant-tree-directory .ant-tree-treenode .ant-tree-node-content-wrapper.ant-tree-node-selected { - color: #fff; - background: transparent; -} - -.ant-tree.ant-tree-directory .ant-tree-treenode-selected:hover::before, -.ant-tree.ant-tree-directory .ant-tree-treenode-selected::before { - background: #1890ff; -} - -.ant-tree.ant-tree-directory .ant-tree-treenode-selected .ant-tree-switcher { - color: #fff; -} - -.ant-tree.ant-tree-directory .ant-tree-treenode-selected .ant-tree-node-content-wrapper { - color: #fff; - background: transparent; -} - -.ant-tree-checkbox { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - top: 0.2em; - line-height: 1; - white-space: nowrap; - outline: none; - cursor: pointer; -} - -.ant-tree-checkbox-wrapper:hover .ant-tree-checkbox-inner, -.ant-tree-checkbox:hover .ant-tree-checkbox-inner, -.ant-tree-checkbox-input:focus + .ant-tree-checkbox-inner { - border-color: #1890ff; -} - -.ant-tree-checkbox-checked::after { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border: 1px solid #1890ff; - border-radius: 2px; - visibility: hidden; - -webkit-animation: antCheckboxEffect 0.36s ease-in-out; - animation: antCheckboxEffect 0.36s ease-in-out; - -webkit-animation-fill-mode: backwards; - animation-fill-mode: backwards; - content: ''; -} - -.ant-tree-checkbox:hover::after, -.ant-tree-checkbox-wrapper:hover .ant-tree-checkbox::after { - visibility: visible; -} - -.ant-tree-checkbox-inner { - position: relative; - top: 0; - left: 0; - display: block; - width: 16px; - height: 16px; - direction: ltr; - background-color: #fff; - border: 1px solid #d9d9d9; - border-radius: 2px; - border-collapse: separate; - transition: all 0.3s; -} - -.ant-tree-checkbox-inner::after { - position: absolute; - top: 50%; - left: 21.5%; - display: table; - width: 5.71428571px; - height: 9.14285714px; - border: 2px solid #fff; - border-top: 0; - border-left: 0; - transform: rotate(45deg) scale(0) translate(-50%, -50%); - opacity: 0; - transition: all 0.1s cubic-bezier(0.71, -0.46, 0.88, 0.6), opacity 0.1s; - content: ' '; -} - -.ant-tree-checkbox-input { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - width: 100%; - height: 100%; - cursor: pointer; - opacity: 0; -} - -.ant-tree-checkbox-checked .ant-tree-checkbox-inner::after { - position: absolute; - display: table; - border: 2px solid #fff; - border-top: 0; - border-left: 0; - transform: rotate(45deg) scale(1) translate(-50%, -50%); - opacity: 1; - transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s; - content: ' '; -} - -.ant-tree-checkbox-checked .ant-tree-checkbox-inner { - background-color: #1890ff; - border-color: #1890ff; -} - -.ant-tree-checkbox-disabled { - cursor: not-allowed; -} - -.ant-tree-checkbox-disabled.ant-tree-checkbox-checked .ant-tree-checkbox-inner::after { - border-color: rgba(0, 0, 0, 0.25); - -webkit-animation-name: none; - animation-name: none; -} - -.ant-tree-checkbox-disabled .ant-tree-checkbox-input { - cursor: not-allowed; -} - -.ant-tree-checkbox-disabled .ant-tree-checkbox-inner { - background-color: #f5f5f5; - border-color: #d9d9d9 !important; -} - -.ant-tree-checkbox-disabled .ant-tree-checkbox-inner::after { - border-color: #f5f5f5; - border-collapse: separate; - -webkit-animation-name: none; - animation-name: none; -} - -.ant-tree-checkbox-disabled + span { - color: rgba(0, 0, 0, 0.25); - cursor: not-allowed; -} - -.ant-tree-checkbox-disabled:hover::after, -.ant-tree-checkbox-wrapper:hover .ant-tree-checkbox-disabled::after { - visibility: hidden; -} - -.ant-tree-checkbox-wrapper { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - display: inline-flex; - align-items: baseline; - line-height: unset; - cursor: pointer; -} - -.ant-tree-checkbox-wrapper::after { - display: inline-block; - width: 0; - overflow: hidden; - content: '\a0'; -} - -.ant-tree-checkbox-wrapper.ant-tree-checkbox-wrapper-disabled { - cursor: not-allowed; -} - -.ant-tree-checkbox-wrapper + .ant-tree-checkbox-wrapper { - margin-left: 8px; -} - -.ant-tree-checkbox + span { - padding-right: 8px; - padding-left: 8px; -} - -.ant-tree-checkbox-group { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - display: inline-block; -} - -.ant-tree-checkbox-group-item { - margin-right: 8px; -} - -.ant-tree-checkbox-group-item:last-child { - margin-right: 0; -} - -.ant-tree-checkbox-group-item + .ant-tree-checkbox-group-item { - margin-left: 0; -} - -.ant-tree-checkbox-indeterminate .ant-tree-checkbox-inner { - background-color: #fff; - border-color: #d9d9d9; -} - -.ant-tree-checkbox-indeterminate .ant-tree-checkbox-inner::after { - top: 50%; - left: 50%; - width: 8px; - height: 8px; - background-color: #1890ff; - border: 0; - transform: translate(-50%, -50%) scale(1); - opacity: 1; - content: ' '; -} - -.ant-tree-checkbox-indeterminate.ant-tree-checkbox-disabled .ant-tree-checkbox-inner::after { - background-color: rgba(0, 0, 0, 0.25); - border-color: rgba(0, 0, 0, 0.25); -} - -.ant-tree { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - background: #fff; - border-radius: 2px; - transition: background-color 0.3s; -} - -.ant-tree-focused:not(:hover):not(.ant-tree-active-focused) { - background: #e6f7ff; -} - -.ant-tree-list-holder-inner { - align-items: flex-start; -} - -.ant-tree.ant-tree-block-node .ant-tree-list-holder-inner { - align-items: stretch; -} - -.ant-tree.ant-tree-block-node .ant-tree-list-holder-inner .ant-tree-node-content-wrapper { - flex: auto; -} - -.ant-tree.ant-tree-block-node .ant-tree-list-holder-inner .ant-tree-treenode.dragging { - position: relative; -} - -.ant-tree.ant-tree-block-node .ant-tree-list-holder-inner .ant-tree-treenode.dragging::after { - position: absolute; - top: 0; - right: 0; - bottom: 4px; - left: 0; - border: 1px solid #1890ff; - opacity: 0; - -webkit-animation: ant-tree-node-fx-do-not-use 0.3s; - animation: ant-tree-node-fx-do-not-use 0.3s; - -webkit-animation-play-state: running; - animation-play-state: running; - -webkit-animation-fill-mode: forwards; - animation-fill-mode: forwards; - content: ''; - pointer-events: none; -} - -.ant-tree .ant-tree-treenode { - display: flex; - align-items: flex-start; - padding: 0 0 4px 0; - outline: none; -} - -.ant-tree .ant-tree-treenode-disabled .ant-tree-node-content-wrapper { - color: rgba(0, 0, 0, 0.25); - cursor: not-allowed; -} - -.ant-tree .ant-tree-treenode-disabled .ant-tree-node-content-wrapper:hover { - background: transparent; -} - -.ant-tree .ant-tree-treenode-active .ant-tree-node-content-wrapper { - background: #f5f5f5; -} - -.ant-tree .ant-tree-treenode:not(.ant-tree .ant-tree-treenode-disabled).filter-node .ant-tree-title { - color: inherit; - font-weight: 500; -} - -.ant-tree-indent { - align-self: stretch; - white-space: nowrap; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-tree-indent-unit { - display: inline-block; - width: 24px; -} - -.ant-tree-draggable-icon { - width: 24px; - line-height: 24px; - text-align: center; - opacity: 0.2; - transition: opacity 0.3s; -} - -.ant-tree-treenode:hover .ant-tree-draggable-icon { - opacity: 0.45; -} - -.ant-tree-switcher { - position: relative; - flex: none; - align-self: stretch; - width: 24px; - margin: 0; - line-height: 24px; - text-align: center; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-tree-switcher .ant-tree-switcher-icon, -.ant-tree-switcher .ant-select-tree-switcher-icon { - display: inline-block; - font-size: 10px; - vertical-align: baseline; -} - -.ant-tree-switcher .ant-tree-switcher-icon svg, -.ant-tree-switcher .ant-select-tree-switcher-icon svg { - transition: transform 0.3s; -} - -.ant-tree-switcher-noop { - cursor: default; -} - -.ant-tree-switcher_close .ant-tree-switcher-icon svg { - transform: rotate(-90deg); -} - -.ant-tree-switcher-loading-icon { - color: #1890ff; -} - -.ant-tree-switcher-leaf-line { - position: relative; - z-index: 1; - display: inline-block; - width: 100%; - height: 100%; -} - -.ant-tree-switcher-leaf-line::before { - position: absolute; - top: 0; - right: 12px; - bottom: -4px; - margin-left: -1px; - border-right: 1px solid #d9d9d9; - content: ' '; -} - -.ant-tree-switcher-leaf-line::after { - position: absolute; - width: 10px; - height: 14px; - border-bottom: 1px solid #d9d9d9; - content: ' '; -} - -.ant-tree-checkbox { - top: initial; - margin: 4px 8px 0 0; -} - -.ant-tree .ant-tree-node-content-wrapper { - position: relative; - z-index: auto; - min-height: 24px; - margin: 0; - padding: 0 4px; - color: inherit; - line-height: 24px; - background: transparent; - border-radius: 2px; - cursor: pointer; - transition: all 0.3s, border 0s, line-height 0s, box-shadow 0s; -} - -.ant-tree .ant-tree-node-content-wrapper:hover { - background-color: #f5f5f5; -} - -.ant-tree .ant-tree-node-content-wrapper.ant-tree-node-selected { - background-color: #bae7ff; -} - -.ant-tree .ant-tree-node-content-wrapper .ant-tree-iconEle { - display: inline-block; - width: 24px; - height: 24px; - line-height: 24px; - text-align: center; - vertical-align: top; -} - -.ant-tree .ant-tree-node-content-wrapper .ant-tree-iconEle:empty { - display: none; -} - -.ant-tree-unselectable .ant-tree-node-content-wrapper:hover { - background-color: transparent; -} - -.ant-tree-node-content-wrapper { - line-height: 24px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-tree-node-content-wrapper .ant-tree-drop-indicator { - position: absolute; - z-index: 1; - height: 2px; - background-color: #1890ff; - border-radius: 1px; - pointer-events: none; -} - -.ant-tree-node-content-wrapper .ant-tree-drop-indicator::after { - position: absolute; - top: -3px; - left: -6px; - width: 8px; - height: 8px; - background-color: transparent; - border: 2px solid #1890ff; - border-radius: 50%; - content: ''; -} - -.ant-tree .ant-tree-treenode.drop-container > [draggable] { - box-shadow: 0 0 0 2px #1890ff; -} - -.ant-tree-show-line .ant-tree-indent-unit { - position: relative; - height: 100%; -} - -.ant-tree-show-line .ant-tree-indent-unit::before { - position: absolute; - top: 0; - right: 12px; - bottom: -4px; - border-right: 1px solid #d9d9d9; - content: ''; -} - -.ant-tree-show-line .ant-tree-indent-unit-end::before { - display: none; -} - -.ant-tree-show-line .ant-tree-switcher { - background: #fff; -} - -.ant-tree-show-line .ant-tree-switcher-line-icon { - vertical-align: -0.15em; -} - -.ant-tree .ant-tree-treenode-leaf-last .ant-tree-switcher-leaf-line::before { - top: auto !important; - bottom: auto !important; - height: 14px !important; -} - - -.ant-timeline { - box-sizing: border-box; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - font-feature-settings: 'tnum'; - margin: 0; - padding: 0; - list-style: none; -} - -.ant-timeline-item { - position: relative; - margin: 0; - padding-bottom: 20px; - font-size: 14px; - list-style: none; -} - -.ant-timeline-item-tail { - position: absolute; - top: 10px; - left: 4px; - height: calc(100% - 10px); - border-left: 2px solid #f0f0f0; -} - -.ant-timeline-item-pending .ant-timeline-item-head { - font-size: 12px; - background-color: transparent; -} - -.ant-timeline-item-pending .ant-timeline-item-tail { - display: none; -} - -.ant-timeline-item-head { - position: absolute; - width: 10px; - height: 10px; - background-color: #fff; - border: 2px solid transparent; - border-radius: 100px; -} - -.ant-timeline-item-head-blue { - color: #1890ff; - border-color: #1890ff; -} - -.ant-timeline-item-head-red { - color: #ff4d4f; - border-color: #ff4d4f; -} - -.ant-timeline-item-head-green { - color: #52c41a; - border-color: #52c41a; -} - -.ant-timeline-item-head-gray { - color: rgba(0, 0, 0, 0.25); - border-color: rgba(0, 0, 0, 0.25); -} - -.ant-timeline-item-head-custom { - position: absolute; - top: 5.5px; - left: 5px; - width: auto; - height: auto; - margin-top: 0; - padding: 3px 1px; - line-height: 1; - text-align: center; - border: 0; - border-radius: 0; - transform: translate(-50%, -50%); -} - -.ant-timeline-item-content { - position: relative; - top: -7.001px; - margin: 0 0 0 26px; - word-break: break-word; -} - -.ant-timeline-item-last > .ant-timeline-item-tail { - display: none; -} - -.ant-timeline-item-last > .ant-timeline-item-content { - min-height: 48px; -} - -.ant-timeline.ant-timeline-alternate .ant-timeline-item-tail, -.ant-timeline.ant-timeline-right .ant-timeline-item-tail, -.ant-timeline.ant-timeline-label .ant-timeline-item-tail, -.ant-timeline.ant-timeline-alternate .ant-timeline-item-head, -.ant-timeline.ant-timeline-right .ant-timeline-item-head, -.ant-timeline.ant-timeline-label .ant-timeline-item-head, -.ant-timeline.ant-timeline-alternate .ant-timeline-item-head-custom, -.ant-timeline.ant-timeline-right .ant-timeline-item-head-custom, -.ant-timeline.ant-timeline-label .ant-timeline-item-head-custom { - left: 50%; -} - -.ant-timeline.ant-timeline-alternate .ant-timeline-item-head, -.ant-timeline.ant-timeline-right .ant-timeline-item-head, -.ant-timeline.ant-timeline-label .ant-timeline-item-head { - margin-left: -4px; -} - -.ant-timeline.ant-timeline-alternate .ant-timeline-item-head-custom, -.ant-timeline.ant-timeline-right .ant-timeline-item-head-custom, -.ant-timeline.ant-timeline-label .ant-timeline-item-head-custom { - margin-left: 1px; -} - -.ant-timeline.ant-timeline-alternate .ant-timeline-item-left .ant-timeline-item-content, -.ant-timeline.ant-timeline-right .ant-timeline-item-left .ant-timeline-item-content, -.ant-timeline.ant-timeline-label .ant-timeline-item-left .ant-timeline-item-content { - left: calc(50% - 4px); - width: calc(50% - 14px); - text-align: left; -} - -.ant-timeline.ant-timeline-alternate .ant-timeline-item-right .ant-timeline-item-content, -.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-content, -.ant-timeline.ant-timeline-label .ant-timeline-item-right .ant-timeline-item-content { - width: calc(50% - 12px); - margin: 0; - text-align: right; -} - -.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-tail, -.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-head, -.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-head-custom { - left: calc(100% - 4px - 2px); -} - -.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-content { - width: calc(100% - 18px); -} - -.ant-timeline.ant-timeline-pending .ant-timeline-item-last .ant-timeline-item-tail { - display: block; - height: calc(100% - 14px); - border-left: 2px dotted #f0f0f0; -} - -.ant-timeline.ant-timeline-reverse .ant-timeline-item-last .ant-timeline-item-tail { - display: none; -} - -.ant-timeline.ant-timeline-reverse .ant-timeline-item-pending .ant-timeline-item-tail { - top: 15px; - display: block; - height: calc(100% - 15px); - border-left: 2px dotted #f0f0f0; -} - -.ant-timeline.ant-timeline-reverse .ant-timeline-item-pending .ant-timeline-item-content { - min-height: 48px; -} - -.ant-timeline.ant-timeline-label .ant-timeline-item-label { - position: absolute; - top: -7.001px; - width: calc(50% - 12px); - text-align: right; -} - -.ant-timeline.ant-timeline-label .ant-timeline-item-right .ant-timeline-item-label { - left: calc(50% + 14px); - width: calc(50% - 14px); - text-align: left; -} - -@-webkit-keyframes antCheckboxEffect { - 0% { - transform: scale(1); - opacity: 0.5; - } - 100% { - transform: scale(1.6); - opacity: 0; - } -} - -@keyframes antCheckboxEffect { - 0% { - transform: scale(1); - opacity: 0.5; - } - 100% { - transform: scale(1.6); - opacity: 0; - } -} - -.ant-transfer-customize-list .ant-transfer-list { - flex: 1 1 50%; - width: auto; - height: auto; - min-height: 200px; -} - -.ant-transfer-customize-list .ant-table-wrapper .ant-table-small { - border: 0; - border-radius: 0; -} - -.ant-transfer-customize-list .ant-table-wrapper .ant-table-small .ant-table-selection-column { - width: 40px; - min-width: 40px; -} - -.ant-transfer-customize-list .ant-table-wrapper .ant-table-small > .ant-table-content > .ant-table-body > table > .ant-table-thead > tr > th { - background: #fafafa; -} - -.ant-transfer-customize-list .ant-table-wrapper .ant-table-small > .ant-table-content .ant-table-row:last-child td { - border-bottom: 1px solid #f0f0f0; -} - -.ant-transfer-customize-list .ant-table-wrapper .ant-table-small .ant-table-body { - margin: 0; -} - -.ant-transfer-customize-list .ant-table-wrapper .ant-table-pagination.ant-pagination { - margin: 16px 0 4px; -} - -.ant-transfer-customize-list .ant-input[disabled] { - background-color: transparent; -} - -.ant-transfer { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - display: flex; - align-items: stretch; -} - -.ant-transfer-disabled .ant-transfer-list { - background: #f5f5f5; -} - -.ant-transfer-list { - display: flex; - flex-direction: column; - width: 180px; - height: 200px; - border: 1px solid #d9d9d9; - border-radius: 2px; -} - -.ant-transfer-list-with-pagination { - width: 250px; - height: auto; -} - -.ant-transfer-list-search .anticon-search { - color: rgba(0, 0, 0, 0.25); -} - -.ant-transfer-list-header { - display: flex; - flex: none; - align-items: center; - height: 40px; - padding: 8px 12px 9px; - color: rgba(0, 0, 0, 0.85); - background: #fff; - border-bottom: 1px solid #f0f0f0; - border-radius: 2px 2px 0 0; -} - -.ant-transfer-list-header > *:not(:last-child) { - margin-right: 4px; -} - -.ant-transfer-list-header > * { - flex: none; -} - -.ant-transfer-list-header-title { - flex: auto; - overflow: hidden; - white-space: nowrap; - text-align: right; - text-overflow: ellipsis; -} - -.ant-transfer-list-header-dropdown { - font-size: 10px; - transform: translateY(10%); - cursor: pointer; -} - -.ant-transfer-list-header-dropdown[disabled] { - cursor: not-allowed; -} - -.ant-transfer-list-body { - display: flex; - flex: auto; - flex-direction: column; - overflow: hidden; - font-size: 14px; -} - -.ant-transfer-list-body-search-wrapper { - position: relative; - flex: none; - padding: 12px; -} - -.ant-transfer-list-content { - flex: auto; - margin: 0; - padding: 0; - overflow: auto; - list-style: none; -} - -.ant-transfer-list-content-item { - display: flex; - align-items: center; - min-height: 32px; - padding: 6px 12px; - line-height: 20px; - transition: all 0.3s; -} - -.ant-transfer-list-content-item > *:not(:last-child) { - margin-right: 8px; -} - -.ant-transfer-list-content-item > * { - flex: none; -} - -.ant-transfer-list-content-item-text { - flex: auto; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - -.ant-transfer-list-content-item-remove { - color: #1890ff; - text-decoration: none; - outline: none; - cursor: pointer; - transition: color 0.3s; - position: relative; - color: #d9d9d9; -} - -.ant-transfer-list-content-item-remove:focus, -.ant-transfer-list-content-item-remove:hover { - color: #40a9ff; -} - -.ant-transfer-list-content-item-remove:active { - color: #096dd9; -} - -.ant-transfer-list-content-item-remove::after { - position: absolute; - top: -6px; - right: -50%; - bottom: -6px; - left: -50%; - content: ''; -} - -.ant-transfer-list-content-item-remove:hover { - color: #40a9ff; -} - -.ant-transfer-list-content-item:not(.ant-transfer-list-content-item-disabled):hover { - background-color: #f5f5f5; - cursor: pointer; -} - -.ant-transfer-list-content-item:not(.ant-transfer-list-content-item-disabled).ant-transfer-list-content-item-checked:hover { - background-color: #dcf4ff; -} - -.ant-transfer-list-content-show-remove .ant-transfer-list-content-item:not(.ant-transfer-list-content-item-disabled):hover { - background: transparent; - cursor: default; -} - -.ant-transfer-list-content-item-checked { - background-color: #e6f7ff; -} - -.ant-transfer-list-content-item-disabled { - color: rgba(0, 0, 0, 0.25); - cursor: not-allowed; -} - -.ant-transfer-list-pagination { - padding: 8px 0; - text-align: right; - border-top: 1px solid #f0f0f0; -} - -.ant-transfer-list-body-not-found { - flex: none; - width: 100%; - margin: auto 0; - color: rgba(0, 0, 0, 0.25); - text-align: center; -} - -.ant-transfer-list-footer { - border-top: 1px solid #f0f0f0; -} - -.ant-transfer-operation { - display: flex; - flex: none; - flex-direction: column; - align-self: center; - margin: 0 8px; - vertical-align: middle; -} - -.ant-transfer-operation .ant-btn { - display: block; -} - -.ant-transfer-operation .ant-btn:first-child { - margin-bottom: 4px; -} - -.ant-transfer-operation .ant-btn .anticon { - font-size: 12px; -} - -.ant-transfer .ant-empty-image { - max-height: -2px; -} - -@-webkit-keyframes ant-tree-node-fx-do-not-use { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - -@keyframes ant-tree-node-fx-do-not-use { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - -@-webkit-keyframes antCheckboxEffect { - 0% { - transform: scale(1); - opacity: 0.5; - } - 100% { - transform: scale(1.6); - opacity: 0; - } -} - -@keyframes antCheckboxEffect { - 0% { - transform: scale(1); - opacity: 0.5; - } - 100% { - transform: scale(1.6); - opacity: 0; - } -} - -.ant-select-tree-checkbox { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - position: relative; - top: 0.2em; - line-height: 1; - white-space: nowrap; - outline: none; - cursor: pointer; -} - -.ant-select-tree-checkbox-wrapper:hover .ant-select-tree-checkbox-inner, -.ant-select-tree-checkbox:hover .ant-select-tree-checkbox-inner, -.ant-select-tree-checkbox-input:focus + .ant-select-tree-checkbox-inner { - border-color: #1890ff; -} - -.ant-select-tree-checkbox-checked::after { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border: 1px solid #1890ff; - border-radius: 2px; - visibility: hidden; - -webkit-animation: antCheckboxEffect 0.36s ease-in-out; - animation: antCheckboxEffect 0.36s ease-in-out; - -webkit-animation-fill-mode: backwards; - animation-fill-mode: backwards; - content: ''; -} - -.ant-select-tree-checkbox:hover::after, -.ant-select-tree-checkbox-wrapper:hover .ant-select-tree-checkbox::after { - visibility: visible; -} - -.ant-select-tree-checkbox-inner { - position: relative; - top: 0; - left: 0; - display: block; - width: 16px; - height: 16px; - direction: ltr; - background-color: #fff; - border: 1px solid #d9d9d9; - border-radius: 2px; - border-collapse: separate; - transition: all 0.3s; -} - -.ant-select-tree-checkbox-inner::after { - position: absolute; - top: 50%; - left: 21.5%; - display: table; - width: 5.71428571px; - height: 9.14285714px; - border: 2px solid #fff; - border-top: 0; - border-left: 0; - transform: rotate(45deg) scale(0) translate(-50%, -50%); - opacity: 0; - transition: all 0.1s cubic-bezier(0.71, -0.46, 0.88, 0.6), opacity 0.1s; - content: ' '; -} - -.ant-select-tree-checkbox-input { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - width: 100%; - height: 100%; - cursor: pointer; - opacity: 0; -} - -.ant-select-tree-checkbox-checked .ant-select-tree-checkbox-inner::after { - position: absolute; - display: table; - border: 2px solid #fff; - border-top: 0; - border-left: 0; - transform: rotate(45deg) scale(1) translate(-50%, -50%); - opacity: 1; - transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s; - content: ' '; -} - -.ant-select-tree-checkbox-checked .ant-select-tree-checkbox-inner { - background-color: #1890ff; - border-color: #1890ff; -} - -.ant-select-tree-checkbox-disabled { - cursor: not-allowed; -} - -.ant-select-tree-checkbox-disabled.ant-select-tree-checkbox-checked .ant-select-tree-checkbox-inner::after { - border-color: rgba(0, 0, 0, 0.25); - -webkit-animation-name: none; - animation-name: none; -} - -.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-input { - cursor: not-allowed; -} - -.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-inner { - background-color: #f5f5f5; - border-color: #d9d9d9 !important; -} - -.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-inner::after { - border-color: #f5f5f5; - border-collapse: separate; - -webkit-animation-name: none; - animation-name: none; -} - -.ant-select-tree-checkbox-disabled + span { - color: rgba(0, 0, 0, 0.25); - cursor: not-allowed; -} - -.ant-select-tree-checkbox-disabled:hover::after, -.ant-select-tree-checkbox-wrapper:hover .ant-select-tree-checkbox-disabled::after { - visibility: hidden; -} - -.ant-select-tree-checkbox-wrapper { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - display: inline-flex; - align-items: baseline; - line-height: unset; - cursor: pointer; -} - -.ant-select-tree-checkbox-wrapper::after { - display: inline-block; - width: 0; - overflow: hidden; - content: '\a0'; -} - -.ant-select-tree-checkbox-wrapper.ant-select-tree-checkbox-wrapper-disabled { - cursor: not-allowed; -} - -.ant-select-tree-checkbox-wrapper + .ant-select-tree-checkbox-wrapper { - margin-left: 8px; -} - -.ant-select-tree-checkbox + span { - padding-right: 8px; - padding-left: 8px; -} - -.ant-select-tree-checkbox-group { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - display: inline-block; -} - -.ant-select-tree-checkbox-group-item { - margin-right: 8px; -} - -.ant-select-tree-checkbox-group-item:last-child { - margin-right: 0; -} - -.ant-select-tree-checkbox-group-item + .ant-select-tree-checkbox-group-item { - margin-left: 0; -} - -.ant-select-tree-checkbox-indeterminate .ant-select-tree-checkbox-inner { - background-color: #fff; - border-color: #d9d9d9; -} - -.ant-select-tree-checkbox-indeterminate .ant-select-tree-checkbox-inner::after { - top: 50%; - left: 50%; - width: 8px; - height: 8px; - background-color: #1890ff; - border: 0; - transform: translate(-50%, -50%) scale(1); - opacity: 1; - content: ' '; -} - -.ant-select-tree-checkbox-indeterminate.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-inner::after { - background-color: rgba(0, 0, 0, 0.25); - border-color: rgba(0, 0, 0, 0.25); -} - -.ant-tree-select-dropdown { - padding: 8px 4px; -} - -.ant-tree-select-dropdown .ant-select-tree { - border-radius: 0; -} - -.ant-tree-select-dropdown .ant-select-tree-list-holder-inner { - align-items: stretch; -} - -.ant-tree-select-dropdown .ant-select-tree-list-holder-inner .ant-select-tree-treenode .ant-select-tree-node-content-wrapper { - flex: auto; -} - -.ant-select-tree { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - background: #fff; - border-radius: 2px; - transition: background-color 0.3s; -} - -.ant-select-tree-focused:not(:hover):not(.ant-select-tree-active-focused) { - background: #e6f7ff; -} - -.ant-select-tree-list-holder-inner { - align-items: flex-start; -} - -.ant-select-tree.ant-select-tree-block-node .ant-select-tree-list-holder-inner { - align-items: stretch; -} - -.ant-select-tree.ant-select-tree-block-node .ant-select-tree-list-holder-inner .ant-select-tree-node-content-wrapper { - flex: auto; -} - -.ant-select-tree.ant-select-tree-block-node .ant-select-tree-list-holder-inner .ant-select-tree-treenode.dragging { - position: relative; -} - -.ant-select-tree.ant-select-tree-block-node .ant-select-tree-list-holder-inner .ant-select-tree-treenode.dragging::after { - position: absolute; - top: 0; - right: 0; - bottom: 4px; - left: 0; - border: 1px solid #1890ff; - opacity: 0; - -webkit-animation: ant-tree-node-fx-do-not-use 0.3s; - animation: ant-tree-node-fx-do-not-use 0.3s; - -webkit-animation-play-state: running; - animation-play-state: running; - -webkit-animation-fill-mode: forwards; - animation-fill-mode: forwards; - content: ''; - pointer-events: none; -} - -.ant-select-tree .ant-select-tree-treenode { - display: flex; - align-items: flex-start; - padding: 0 0 4px 0; - outline: none; -} - -.ant-select-tree .ant-select-tree-treenode-disabled .ant-select-tree-node-content-wrapper { - color: rgba(0, 0, 0, 0.25); - cursor: not-allowed; -} - -.ant-select-tree .ant-select-tree-treenode-disabled .ant-select-tree-node-content-wrapper:hover { - background: transparent; -} - -.ant-select-tree .ant-select-tree-treenode-active .ant-select-tree-node-content-wrapper { - background: #f5f5f5; -} - -.ant-select-tree .ant-select-tree-treenode:not(.ant-select-tree .ant-select-tree-treenode-disabled).filter-node .ant-select-tree-title { - color: inherit; - font-weight: 500; -} - -.ant-select-tree-indent { - align-self: stretch; - white-space: nowrap; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-select-tree-indent-unit { - display: inline-block; - width: 24px; -} - -.ant-select-tree-draggable-icon { - width: 24px; - line-height: 24px; - text-align: center; - opacity: 0.2; - transition: opacity 0.3s; -} - -.ant-select-tree-treenode:hover .ant-select-tree-draggable-icon { - opacity: 0.45; -} - -.ant-select-tree-switcher { - position: relative; - flex: none; - align-self: stretch; - width: 24px; - margin: 0; - line-height: 24px; - text-align: center; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-select-tree-switcher .ant-tree-switcher-icon, -.ant-select-tree-switcher .ant-select-tree-switcher-icon { - display: inline-block; - font-size: 10px; - vertical-align: baseline; -} - -.ant-select-tree-switcher .ant-tree-switcher-icon svg, -.ant-select-tree-switcher .ant-select-tree-switcher-icon svg { - transition: transform 0.3s; -} - -.ant-select-tree-switcher-noop { - cursor: default; -} - -.ant-select-tree-switcher_close .ant-select-tree-switcher-icon svg { - transform: rotate(-90deg); -} - -.ant-select-tree-switcher-loading-icon { - color: #1890ff; -} - -.ant-select-tree-switcher-leaf-line { - position: relative; - z-index: 1; - display: inline-block; - width: 100%; - height: 100%; -} - -.ant-select-tree-switcher-leaf-line::before { - position: absolute; - top: 0; - right: 12px; - bottom: -4px; - margin-left: -1px; - border-right: 1px solid #d9d9d9; - content: ' '; -} - -.ant-select-tree-switcher-leaf-line::after { - position: absolute; - width: 10px; - height: 14px; - border-bottom: 1px solid #d9d9d9; - content: ' '; -} - -.ant-select-tree-checkbox { - top: initial; - margin: 4px 8px 0 0; -} - -.ant-select-tree .ant-select-tree-node-content-wrapper { - position: relative; - z-index: auto; - min-height: 24px; - margin: 0; - padding: 0 4px; - color: inherit; - line-height: 24px; - background: transparent; - border-radius: 2px; - cursor: pointer; - transition: all 0.3s, border 0s, line-height 0s, box-shadow 0s; -} - -.ant-select-tree .ant-select-tree-node-content-wrapper:hover { - background-color: #f5f5f5; -} - -.ant-select-tree .ant-select-tree-node-content-wrapper.ant-select-tree-node-selected { - background-color: #bae7ff; -} - -.ant-select-tree .ant-select-tree-node-content-wrapper .ant-select-tree-iconEle { - display: inline-block; - width: 24px; - height: 24px; - line-height: 24px; - text-align: center; - vertical-align: top; -} - -.ant-select-tree .ant-select-tree-node-content-wrapper .ant-select-tree-iconEle:empty { - display: none; -} - -.ant-select-tree-unselectable .ant-select-tree-node-content-wrapper:hover { - background-color: transparent; -} - -.ant-select-tree-node-content-wrapper { - line-height: 24px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ant-select-tree-node-content-wrapper .ant-tree-drop-indicator { - position: absolute; - z-index: 1; - height: 2px; - background-color: #1890ff; - border-radius: 1px; - pointer-events: none; -} - -.ant-select-tree-node-content-wrapper .ant-tree-drop-indicator::after { - position: absolute; - top: -3px; - left: -6px; - width: 8px; - height: 8px; - background-color: transparent; - border: 2px solid #1890ff; - border-radius: 50%; - content: ''; -} - -.ant-select-tree .ant-select-tree-treenode.drop-container > [draggable] { - box-shadow: 0 0 0 2px #1890ff; -} - -.ant-select-tree-show-line .ant-select-tree-indent-unit { - position: relative; - height: 100%; -} - -.ant-select-tree-show-line .ant-select-tree-indent-unit::before { - position: absolute; - top: 0; - right: 12px; - bottom: -4px; - border-right: 1px solid #d9d9d9; - content: ''; -} - -.ant-select-tree-show-line .ant-select-tree-indent-unit-end::before { - display: none; -} - -.ant-select-tree-show-line .ant-select-tree-switcher { - background: #fff; -} - -.ant-select-tree-show-line .ant-select-tree-switcher-line-icon { - vertical-align: -0.15em; -} - -.ant-select-tree .ant-select-tree-treenode-leaf-last .ant-select-tree-switcher-leaf-line::before { - top: auto !important; - bottom: auto !important; - height: 14px !important; -} - -.ant-typography { - color: rgba(0, 0, 0, 0.85); - overflow-wrap: break-word; -} - -.ant-typography.ant-typography-secondary { - color: rgba(0, 0, 0, 0.45); -} - -.ant-typography.ant-typography-success { - color: #52c41a; -} - -.ant-typography.ant-typography-warning { - color: #faad14; -} - -.ant-typography.ant-typography-danger { - color: #ff4d4f; -} - -a.ant-typography.ant-typography-danger:active, -a.ant-typography.ant-typography-danger:focus, -a.ant-typography.ant-typography-danger:hover { - color: #ff7875; -} - -.ant-typography.ant-typography-disabled { - color: rgba(0, 0, 0, 0.25); - cursor: not-allowed; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -div.ant-typography, -.ant-typography p { - margin-bottom: 1em; -} - -h1.ant-typography, -.ant-typography h1 { - margin-bottom: 0.5em; - color: rgba(0, 0, 0, 0.85); - font-weight: 600; - font-size: 38px; - line-height: 1.23; -} - -h2.ant-typography, -.ant-typography h2 { - margin-bottom: 0.5em; - color: rgba(0, 0, 0, 0.85); - font-weight: 600; - font-size: 30px; - line-height: 1.35; -} - -h3.ant-typography, -.ant-typography h3 { - margin-bottom: 0.5em; - color: rgba(0, 0, 0, 0.85); - font-weight: 600; - font-size: 24px; - line-height: 1.35; -} - -h4.ant-typography, -.ant-typography h4 { - margin-bottom: 0.5em; - color: rgba(0, 0, 0, 0.85); - font-weight: 600; - font-size: 20px; - line-height: 1.4; -} - -h5.ant-typography, -.ant-typography h5 { - margin-bottom: 0.5em; - color: rgba(0, 0, 0, 0.85); - font-weight: 600; - font-size: 16px; - line-height: 1.5; -} - -.ant-typography + h1.ant-typography, -.ant-typography + h2.ant-typography, -.ant-typography + h3.ant-typography, -.ant-typography + h4.ant-typography, -.ant-typography + h5.ant-typography { - margin-top: 1.2em; -} - -.ant-typography div + h1, -.ant-typography ul + h1, -.ant-typography li + h1, -.ant-typography p + h1, -.ant-typography h1 + h1, -.ant-typography h2 + h1, -.ant-typography h3 + h1, -.ant-typography h4 + h1, -.ant-typography h5 + h1, -.ant-typography div + h2, -.ant-typography ul + h2, -.ant-typography li + h2, -.ant-typography p + h2, -.ant-typography h1 + h2, -.ant-typography h2 + h2, -.ant-typography h3 + h2, -.ant-typography h4 + h2, -.ant-typography h5 + h2, -.ant-typography div + h3, -.ant-typography ul + h3, -.ant-typography li + h3, -.ant-typography p + h3, -.ant-typography h1 + h3, -.ant-typography h2 + h3, -.ant-typography h3 + h3, -.ant-typography h4 + h3, -.ant-typography h5 + h3, -.ant-typography div + h4, -.ant-typography ul + h4, -.ant-typography li + h4, -.ant-typography p + h4, -.ant-typography h1 + h4, -.ant-typography h2 + h4, -.ant-typography h3 + h4, -.ant-typography h4 + h4, -.ant-typography h5 + h4, -.ant-typography div + h5, -.ant-typography ul + h5, -.ant-typography li + h5, -.ant-typography p + h5, -.ant-typography h1 + h5, -.ant-typography h2 + h5, -.ant-typography h3 + h5, -.ant-typography h4 + h5, -.ant-typography h5 + h5 { - margin-top: 1.2em; -} - -a.ant-typography-ellipsis, -span.ant-typography-ellipsis { - display: inline-block; - max-width: 100%; -} - -a.ant-typography, -.ant-typography a { - color: #1890ff; - outline: none; - cursor: pointer; - transition: color 0.3s; - text-decoration: none; -} - -a.ant-typography:focus, -.ant-typography a:focus, -a.ant-typography:hover, -.ant-typography a:hover { - color: #40a9ff; -} - -a.ant-typography:active, -.ant-typography a:active { - color: #096dd9; -} - -a.ant-typography:active, -.ant-typography a:active, -a.ant-typography:hover, -.ant-typography a:hover { - text-decoration: none; -} - -a.ant-typography[disabled], -.ant-typography a[disabled], -a.ant-typography.ant-typography-disabled, -.ant-typography a.ant-typography-disabled { - color: rgba(0, 0, 0, 0.25); - cursor: not-allowed; -} - -a.ant-typography[disabled]:active, -.ant-typography a[disabled]:active, -a.ant-typography.ant-typography-disabled:active, -.ant-typography a.ant-typography-disabled:active, -a.ant-typography[disabled]:hover, -.ant-typography a[disabled]:hover, -a.ant-typography.ant-typography-disabled:hover, -.ant-typography a.ant-typography-disabled:hover { - color: rgba(0, 0, 0, 0.25); -} - -a.ant-typography[disabled]:active, -.ant-typography a[disabled]:active, -a.ant-typography.ant-typography-disabled:active, -.ant-typography a.ant-typography-disabled:active { - pointer-events: none; -} - -.ant-typography code { - margin: 0 0.2em; - padding: 0.2em 0.4em 0.1em; - font-size: 85%; - background: rgba(150, 150, 150, 0.1); - border: 1px solid rgba(100, 100, 100, 0.2); - border-radius: 3px; -} - -.ant-typography kbd { - margin: 0 0.2em; - padding: 0.15em 0.4em 0.1em; - font-size: 90%; - background: rgba(150, 150, 150, 0.06); - border: 1px solid rgba(100, 100, 100, 0.2); - border-bottom-width: 2px; - border-radius: 3px; -} - -.ant-typography mark { - padding: 0; - background-color: #ffe58f; -} - -.ant-typography u, -.ant-typography ins { - text-decoration: underline; - -webkit-text-decoration-skip: ink; - text-decoration-skip-ink: auto; -} - -.ant-typography s, -.ant-typography del { - text-decoration: line-through; -} - -.ant-typography strong { - font-weight: 600; -} - -.ant-typography-expand, -.ant-typography-edit, -.ant-typography-copy { - color: #1890ff; - text-decoration: none; - outline: none; - cursor: pointer; - transition: color 0.3s; - margin-left: 4px; -} - -.ant-typography-expand:focus, -.ant-typography-edit:focus, -.ant-typography-copy:focus, -.ant-typography-expand:hover, -.ant-typography-edit:hover, -.ant-typography-copy:hover { - color: #40a9ff; -} - -.ant-typography-expand:active, -.ant-typography-edit:active, -.ant-typography-copy:active { - color: #096dd9; -} - -.ant-typography-copy-success, -.ant-typography-copy-success:hover, -.ant-typography-copy-success:focus { - color: #52c41a; -} - -.ant-typography-edit-content { - position: relative; -} - -div.ant-typography-edit-content { - left: -12px; - margin-top: -5px; - margin-bottom: calc(1em - 4px - 1px); -} - -.ant-typography-edit-content-confirm { - position: absolute; - right: 10px; - bottom: 8px; - color: rgba(0, 0, 0, 0.45); - pointer-events: none; -} - -.ant-typography-edit-content textarea { - -moz-transition: none; -} - -.ant-typography ul, -.ant-typography ol { - margin: 0 0 1em; - padding: 0; -} - -.ant-typography ul li, -.ant-typography ol li { - margin: 0 0 0 20px; - padding: 0 0 0 4px; -} - -.ant-typography ul { - list-style-type: circle; -} - -.ant-typography ul ul { - list-style-type: disc; -} - -.ant-typography ol { - list-style-type: decimal; -} - -.ant-typography pre, -.ant-typography blockquote { - margin: 1em 0; -} - -.ant-typography pre { - padding: 0.4em 0.6em; - white-space: pre-wrap; - word-wrap: break-word; - background: rgba(150, 150, 150, 0.1); - border: 1px solid rgba(100, 100, 100, 0.2); - border-radius: 3px; -} - -.ant-typography pre code { - display: inline; - margin: 0; - padding: 0; - font-size: inherit; - font-family: inherit; - background: transparent; - border: 0; -} - -.ant-typography blockquote { - padding: 0 0 0 0.6em; - border-left: 4px solid rgba(100, 100, 100, 0.2); - opacity: 0.85; -} - -.ant-typography-single-line { - white-space: nowrap; -} - -.ant-typography-ellipsis-single-line { - overflow: hidden; - text-overflow: ellipsis; -} - -a.ant-typography-ellipsis-single-line, -span.ant-typography-ellipsis-single-line { - vertical-align: bottom; -} - -.ant-typography-ellipsis-multiple-line { - display: -webkit-box; - overflow: hidden; - -webkit-line-clamp: 3; - /*! autoprefixer: ignore next */ - -webkit-box-orient: vertical; -} - -.ant-upload { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - line-height: 1.5715; - list-style: none; - font-feature-settings: 'tnum'; - outline: 0; -} - -.ant-upload p { - margin: 0; -} - -.ant-upload-btn { - display: block; - width: 100%; - outline: none; -} - -.ant-upload input[type='file'] { - cursor: pointer; -} - -.ant-upload.ant-upload-select { - display: inline-block; -} - -.ant-upload.ant-upload-disabled { - cursor: not-allowed; -} - -.ant-upload.ant-upload-select-picture-card { - width: 104px; - height: 104px; - margin-right: 8px; - margin-bottom: 8px; - text-align: center; - vertical-align: top; - background-color: #fafafa; - border: 1px dashed #d9d9d9; - border-radius: 2px; - cursor: pointer; - transition: border-color 0.3s; -} - -.ant-upload.ant-upload-select-picture-card > .ant-upload { - display: flex; - align-items: center; - justify-content: center; - height: 100%; - text-align: center; -} - -.ant-upload.ant-upload-select-picture-card:hover { - border-color: #1890ff; -} - -.ant-upload-disabled.ant-upload.ant-upload-select-picture-card:hover { - border-color: #d9d9d9; -} - -.ant-upload.ant-upload-drag { - position: relative; - width: 100%; - height: 100%; - text-align: center; - background: #fafafa; - border: 1px dashed #d9d9d9; - border-radius: 2px; - cursor: pointer; - transition: border-color 0.3s; -} - -.ant-upload.ant-upload-drag .ant-upload { - padding: 16px 0; -} - -.ant-upload.ant-upload-drag.ant-upload-drag-hover:not(.ant-upload-disabled) { - border-color: #096dd9; -} - -.ant-upload.ant-upload-drag.ant-upload-disabled { - cursor: not-allowed; -} - -.ant-upload.ant-upload-drag .ant-upload-btn { - display: table; - height: 100%; -} - -.ant-upload.ant-upload-drag .ant-upload-drag-container { - display: table-cell; - vertical-align: middle; -} - -.ant-upload.ant-upload-drag:not(.ant-upload-disabled):hover { - border-color: #40a9ff; -} - -.ant-upload.ant-upload-drag p.ant-upload-drag-icon { - margin-bottom: 20px; -} - -.ant-upload.ant-upload-drag p.ant-upload-drag-icon .anticon { - color: #40a9ff; - font-size: 48px; -} - -.ant-upload.ant-upload-drag p.ant-upload-text { - margin: 0 0 4px; - color: rgba(0, 0, 0, 0.85); - font-size: 16px; -} - -.ant-upload.ant-upload-drag p.ant-upload-hint { - color: rgba(0, 0, 0, 0.45); - font-size: 14px; -} - -.ant-upload.ant-upload-drag .anticon-plus { - color: rgba(0, 0, 0, 0.25); - font-size: 30px; - transition: all 0.3s; -} - -.ant-upload.ant-upload-drag .anticon-plus:hover { - color: rgba(0, 0, 0, 0.45); -} - -.ant-upload.ant-upload-drag:hover .anticon-plus { - color: rgba(0, 0, 0, 0.45); -} - -.ant-upload-picture-card-wrapper { - display: inline-block; - width: 100%; -} - -.ant-upload-picture-card-wrapper::before { - display: table; - content: ''; -} - -.ant-upload-picture-card-wrapper::after { - display: table; - clear: both; - content: ''; -} - -.ant-upload-list { - box-sizing: border-box; - margin: 0; - padding: 0; - color: rgba(0, 0, 0, 0.85); - font-size: 14px; - font-variant: tabular-nums; - list-style: none; - font-feature-settings: 'tnum'; - line-height: 1.5715; -} - -.ant-upload-list::before { - display: table; - content: ''; -} - -.ant-upload-list::after { - display: table; - clear: both; - content: ''; -} - -.ant-upload-list-item { - position: relative; - height: 22.001px; - margin-top: 8px; - font-size: 14px; -} - -.ant-upload-list-item-name { - display: inline-block; - width: 100%; - padding-left: 22px; - overflow: hidden; - line-height: 1.5715; - white-space: nowrap; - text-overflow: ellipsis; -} - -.ant-upload-list-item-card-actions { - position: absolute; - right: 0; -} - -.ant-upload-list-item-card-actions-btn { - opacity: 0; -} - -.ant-upload-list-item-card-actions-btn.ant-btn-sm { - height: 20px; - line-height: 1; -} - -.ant-upload-list-item-card-actions.picture { - top: 22px; - line-height: 0; -} - -.ant-upload-list-item-card-actions-btn:focus, -.ant-upload-list-item-card-actions.picture .ant-upload-list-item-card-actions-btn { - opacity: 1; -} - -.ant-upload-list-item-card-actions .anticon { - color: rgba(0, 0, 0, 0.45); -} - -.ant-upload-list-item-info { - height: 100%; - padding: 0 4px; - transition: background-color 0.3s; -} - -.ant-upload-list-item-info > span { - display: block; - width: 100%; - height: 100%; -} - -.ant-upload-list-item-info .anticon-loading .anticon, -.ant-upload-list-item-info .ant-upload-text-icon .anticon { - position: absolute; - top: 5px; - color: rgba(0, 0, 0, 0.45); - font-size: 14px; -} - -.ant-upload-list-item .anticon-close { - position: absolute; - top: 6px; - right: 4px; - color: rgba(0, 0, 0, 0.45); - font-size: 10px; - line-height: 0; - cursor: pointer; - opacity: 0; - transition: all 0.3s; -} - -.ant-upload-list-item .anticon-close:hover { - color: rgba(0, 0, 0, 0.85); -} - -.ant-upload-list-item:hover .ant-upload-list-item-info { - background-color: #f5f5f5; -} - -.ant-upload-list-item:hover .anticon-close { - opacity: 1; -} - -.ant-upload-list-item:hover .ant-upload-list-item-card-actions-btn { - opacity: 1; -} - -.ant-upload-list-item-error, -.ant-upload-list-item-error .ant-upload-text-icon > .anticon, -.ant-upload-list-item-error .ant-upload-list-item-name { - color: #ff4d4f; -} - -.ant-upload-list-item-error .ant-upload-list-item-card-actions .anticon { - color: #ff4d4f; -} - -.ant-upload-list-item-error .ant-upload-list-item-card-actions-btn { - opacity: 1; -} - -.ant-upload-list-item-progress { - position: absolute; - bottom: -12px; - width: 100%; - padding-left: 26px; - font-size: 14px; - line-height: 0; -} - -.ant-upload-list-picture .ant-upload-list-item, -.ant-upload-list-picture-card .ant-upload-list-item { - position: relative; - height: 66px; - padding: 8px; - border: 1px solid #d9d9d9; - border-radius: 2px; -} - -.ant-upload-list-picture .ant-upload-list-item:hover, -.ant-upload-list-picture-card .ant-upload-list-item:hover { - background: transparent; -} - -.ant-upload-list-picture .ant-upload-list-item-error, -.ant-upload-list-picture-card .ant-upload-list-item-error { - border-color: #ff4d4f; -} - -.ant-upload-list-picture .ant-upload-list-item-info, -.ant-upload-list-picture-card .ant-upload-list-item-info { - padding: 0; -} - -.ant-upload-list-picture .ant-upload-list-item:hover .ant-upload-list-item-info, -.ant-upload-list-picture-card .ant-upload-list-item:hover .ant-upload-list-item-info { - background: transparent; -} - -.ant-upload-list-picture .ant-upload-list-item-uploading, -.ant-upload-list-picture-card .ant-upload-list-item-uploading { - border-style: dashed; -} - -.ant-upload-list-picture .ant-upload-list-item-thumbnail, -.ant-upload-list-picture-card .ant-upload-list-item-thumbnail { - width: 48px; - height: 48px; - line-height: 60px; - text-align: center; - opacity: 0.8; -} - -.ant-upload-list-picture .ant-upload-list-item-thumbnail .anticon, -.ant-upload-list-picture-card .ant-upload-list-item-thumbnail .anticon { - font-size: 26px; -} - -.ant-upload-list-picture .ant-upload-list-item-error .ant-upload-list-item-thumbnail .anticon svg path[fill='#e6f7ff'], -.ant-upload-list-picture-card .ant-upload-list-item-error .ant-upload-list-item-thumbnail .anticon svg path[fill='#e6f7ff'] { - fill: #fff2f0; -} - -.ant-upload-list-picture .ant-upload-list-item-error .ant-upload-list-item-thumbnail .anticon svg path[fill='#1890ff'], -.ant-upload-list-picture-card .ant-upload-list-item-error .ant-upload-list-item-thumbnail .anticon svg path[fill='#1890ff'] { - fill: #ff4d4f; -} - -.ant-upload-list-picture .ant-upload-list-item-icon, -.ant-upload-list-picture-card .ant-upload-list-item-icon { - position: absolute; - top: 50%; - left: 50%; - font-size: 26px; - transform: translate(-50%, -50%); -} - -.ant-upload-list-picture .ant-upload-list-item-icon .anticon, -.ant-upload-list-picture-card .ant-upload-list-item-icon .anticon { - font-size: 26px; -} - -.ant-upload-list-picture .ant-upload-list-item-image, -.ant-upload-list-picture-card .ant-upload-list-item-image { - max-width: 100%; -} - -.ant-upload-list-picture .ant-upload-list-item-thumbnail img, -.ant-upload-list-picture-card .ant-upload-list-item-thumbnail img { - display: block; - width: 48px; - height: 48px; - overflow: hidden; -} - -.ant-upload-list-picture .ant-upload-list-item-name, -.ant-upload-list-picture-card .ant-upload-list-item-name { - display: inline-block; - box-sizing: border-box; - max-width: 100%; - margin: 0 0 0 8px; - padding-right: 8px; - padding-left: 48px; - overflow: hidden; - line-height: 44px; - white-space: nowrap; - text-overflow: ellipsis; - transition: all 0.3s; -} - -.ant-upload-list-picture .ant-upload-list-item-uploading .ant-upload-list-item-name, -.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-name { - margin-bottom: 12px; -} - -.ant-upload-list-picture .ant-upload-list-item-progress, -.ant-upload-list-picture-card .ant-upload-list-item-progress { - bottom: 14px; - width: calc(100% - 24px); - margin-top: 0; - padding-left: 56px; -} - -.ant-upload-list-picture .anticon-close, -.ant-upload-list-picture-card .anticon-close { - position: absolute; - top: 8px; - right: 8px; - line-height: 1; - opacity: 1; -} - -.ant-upload-list-picture-card-container { - display: inline-block; - width: 104px; - height: 104px; - margin: 0 8px 8px 0; - vertical-align: top; -} - -.ant-upload-list-picture-card.ant-upload-list::after { - display: none; -} - -.ant-upload-list-picture-card .ant-upload-list-item { - height: 100%; - margin: 0; -} - -.ant-upload-list-picture-card .ant-upload-list-item-info { - position: relative; - height: 100%; - overflow: hidden; -} - -.ant-upload-list-picture-card .ant-upload-list-item-info::before { - position: absolute; - z-index: 1; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); - opacity: 0; - transition: all 0.3s; - content: ' '; -} - -.ant-upload-list-picture-card .ant-upload-list-item:hover .ant-upload-list-item-info::before { - opacity: 1; -} - -.ant-upload-list-picture-card .ant-upload-list-item-actions { - position: absolute; - top: 50%; - left: 50%; - z-index: 10; - white-space: nowrap; - transform: translate(-50%, -50%); - opacity: 0; - transition: all 0.3s; -} - -.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-eye, -.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-download, -.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-delete { - z-index: 10; - width: 16px; - margin: 0 4px; - color: rgba(255, 255, 255, 0.85); - font-size: 16px; - cursor: pointer; - transition: all 0.3s; -} - -.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-eye:hover, -.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-download:hover, -.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-delete:hover { - color: #fff; -} - -.ant-upload-list-picture-card .ant-upload-list-item-info:hover + .ant-upload-list-item-actions, -.ant-upload-list-picture-card .ant-upload-list-item-actions:hover { - opacity: 1; -} - -.ant-upload-list-picture-card .ant-upload-list-item-thumbnail, -.ant-upload-list-picture-card .ant-upload-list-item-thumbnail img { - position: static; - display: block; - width: 100%; - height: 100%; - -o-object-fit: contain; - object-fit: contain; -} - -.ant-upload-list-picture-card .ant-upload-list-item-name { - display: none; - margin: 8px 0 0; - padding: 0; - line-height: 1.5715; - text-align: center; -} - -.ant-upload-list-picture-card .ant-upload-list-item-file + .ant-upload-list-item-name { - position: absolute; - bottom: 10px; - display: block; -} - -.ant-upload-list-picture-card .ant-upload-list-item-uploading.ant-upload-list-item { - background-color: #fafafa; -} - -.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info { - height: auto; -} - -.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info::before, -.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info .anticon-eye, -.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info .anticon-delete { - display: none; -} - -.ant-upload-list-picture-card .ant-upload-list-item-progress { - bottom: 32px; - width: calc(100% - 14px); - padding-left: 0; -} - -.ant-upload-list-text-container, -.ant-upload-list-picture-container { - transition: opacity 0.3s, height 0.3s; -} - -.ant-upload-list-text-container::before, -.ant-upload-list-picture-container::before { - display: table; - width: 0; - height: 0; - content: ''; -} - -.ant-upload-list-text-container .ant-upload-span, -.ant-upload-list-picture-container .ant-upload-span { - display: block; - flex: auto; -} - -.ant-upload-list-text .ant-upload-span, -.ant-upload-list-picture .ant-upload-span { - display: flex; - align-items: center; -} - -.ant-upload-list-text .ant-upload-span > *, -.ant-upload-list-picture .ant-upload-span > * { - flex: none; -} - -.ant-upload-list-text .ant-upload-list-item-name, -.ant-upload-list-picture .ant-upload-list-item-name { - flex: auto; - margin: 0; - padding: 0 8px; -} - -.ant-upload-list-text .ant-upload-list-item-card-actions, -.ant-upload-list-picture .ant-upload-list-item-card-actions { - position: static; -} - -.ant-upload-list-text .ant-upload-text-icon .anticon { - position: static; -} - -.ant-upload-list .ant-upload-animate-inline-appear, -.ant-upload-list .ant-upload-animate-inline-enter, -.ant-upload-list .ant-upload-animate-inline-leave { - -webkit-animation-duration: 0.3s; - animation-duration: 0.3s; - -webkit-animation-fill-mode: cubic-bezier(0.78, 0.14, 0.15, 0.86); - animation-fill-mode: cubic-bezier(0.78, 0.14, 0.15, 0.86); -} - -.ant-upload-list .ant-upload-animate-inline-appear, -.ant-upload-list .ant-upload-animate-inline-enter { - -webkit-animation-name: uploadAnimateInlineIn; - animation-name: uploadAnimateInlineIn; -} - -.ant-upload-list .ant-upload-animate-inline-leave { - -webkit-animation-name: uploadAnimateInlineOut; - animation-name: uploadAnimateInlineOut; -} - -@-webkit-keyframes uploadAnimateInlineIn { - from { - width: 0; - height: 0; - margin: 0; - padding: 0; - opacity: 0; - } -} - -@keyframes uploadAnimateInlineIn { - from { - width: 0; - height: 0; - margin: 0; - padding: 0; - opacity: 0; - } -} - -@-webkit-keyframes uploadAnimateInlineOut { - to { - width: 0; - height: 0; - margin: 0; - padding: 0; - opacity: 0; - } -} - -@keyframes uploadAnimateInlineOut { - to { - width: 0; - height: 0; - margin: 0; - padding: 0; - opacity: 0; - } -} diff --git a/worklenz-frontend/src/res/scss/gantt.scss b/worklenz-frontend/src/res/scss/gantt.scss deleted file mode 100644 index 26537a9a..00000000 --- a/worklenz-frontend/src/res/scss/gantt.scss +++ /dev/null @@ -1,407 +0,0 @@ -$red: #ff6252; -$column_count: var(--column_count); -$column_width: var(--column_width); - -:root { - --column_count: 20; - --column_width: 70px; -} - - -.header { - color: #202125; - margin-bottom: 40px; - - h2 { - font-weight: 600; - } - - p { - font-weight: 300; - } -} - -.wrapper { - max-width: 100vw; - min-width: 700px; - margin: 0 auto; - padding: 0px; - gap: 10px; - overflow: auto; - max-height: 80vh; -} - -.gantt { - display: grid; - border: 0; - position: relative; - box-sizing: border-box; - margin-bottom: 10px; - - &__row { - display: grid; - grid-template-columns: 200px 1fr; - background-color: #fff; - - &--empty { - background-color: #fafafa !important; - z-index: 1; - - .gantt__row-first { - border-width: 1px 1px 0 0; - border-left: 1px solid #f0f0f0; - } - } - - &--lines { - position: absolute; - height: 100%; - width: 100%; - background-color: transparent; - grid-template-columns: 200px repeat($column_count, $column_width); - - span { - display: block; - border-right: 1px solid #f0f0f0; - - &.weekend { - background: linear-gradient(-45deg, rgb(230, 230, 230) 12.5%, transparent 12.5%, transparent 50%, rgb(230, 230, 230) 50%, rgb(230, 230, 230) 62.5%, transparent 62.5%, transparent) 0% 0% / 5px 5px; - z-index: 0; - } - } - - &:after { - grid-row: 1; - grid-column: 0; - background-color: #1688b345; - z-index: 2; - height: 100%; - } - } - - &--months { - color: #000; - background-color: #fafafa !important; - grid-template-columns: 200px repeat($column_count, $column_width); - - .gantt__row-first { - border-top: 0 !important; - background-color: #fafafa !important; - } - - span { - text-align: center; - font-size: 13px; - align-self: center; - font-weight: bold; - padding: 20px 0; - } - } - - &-first { - background-color: #fff; - border: none; - padding: 15px 10px; - font-size: 13px; - // font-weight: bold; - text-align: left; - } - - &-first-user { - border-width: 1px 1px 0px 0px; - } - - &-bars { - list-style: none; - display: grid; - padding: 0px 0px; - margin: 0px 0px 7px 0px; - grid-template-columns: repeat($column_count, $column_width); - grid-gap: 0px 0; - align-items: center; - - li { - font-weight: 500; - text-align: left; - font-size: 13px; - min-height: 15px; - background-color: #55de84; - padding: 0px 0px; - margin: 7px 1px 0px 1px; - color: #fff; - overflow: hidden; - position: relative; - cursor: pointer; - line-height: 21px; - - .ant-btn { - width: 100%; - text-align: left; - padding: 0px 15px; - color: white; - border: none; - font-weight: 400; - - :hover { - color: white; - } - } - - &.stripes { - background-image: repeating-linear-gradient(45deg, transparent, transparent 5px, rgba(255, 255, 255, .1) 5px, rgba(255, 255, 255, .1) 12px); - } - - &:before, - &:after { - content: ""; - height: 100%; - top: 0; - z-index: 4; - position: absolute; - background-color: rgba(0, 0, 0, 0.3); - } - - &:before { - left: 0; - } - - &:after { - right: 0; - } - } - } - - &-month-range { - list-style: none; - display: grid; - padding: 12px 0px; - margin: 0; - grid-template-columns: repeat($column_count, $column_width); - grid-gap: 8px 0; - border-top: 1px solid #f0f0f0; - align-items: center; - - li { - font-weight: 500; - text-align: left; - font-size: 14px; - min-height: 15px; - background-color: #55de84; - padding: 0px 0px; - margin: 0px 1px; - color: #fff; - overflow: hidden; - position: relative; - animation: 0.5s ease-out 0s 1 scale-up-hor-left; - - .ant-btn { - width: 100%; - text-align: left; - padding: 0px 8px; - color: white; - border: none; - font-weight: 400; - - :hover { - color: white; - } - } - - &:before, - &:after { - content: ""; - height: 100%; - top: 0; - z-index: 4; - position: absolute; - background-color: rgba(0, 0, 0, 0.3); - } - - &:before { - left: 0; - } - - &:after { - right: 0; - } - } - } - } -} - -.project-header { - background-color: #f0f2f5 !important; - font-size: 14px; - border-right: 1px solid #f0f0f0; - border-top: 1px solid #bdbdbd; - margin-top: 1px; -} - -.col { - padding: 16px 0; - text-align: center; - border-radius: 0; - min-height: 30px; - margin-top: 8px; - margin-bottom: 8px; - background: rgba(0, 160, 233, 0.7); - color: #fff; -} - -.col.right { - background: #00a0e9; -} - -.sticky-left { - position: sticky; - left: 0; - z-index: 1; -} - -.grid-header { - position: sticky; - top: 0; - z-index: 1; -} - -.grid-header.sticky-left { - z-index: 2; -} - -.grid-column { - height: 100%; -} - -.gantt__row { - a { - color: rgba(0, 0, 0, 0.85); - font-weight: 600; - } -} - -.gantt__row--months span { - font-weight: 500; - padding: 12px 10px; - color: rgba(0, 0, 0, 0.85); - text-align: left; -} - -.gantt-user { - border-right: 1px solid #f0f0f0; - border-left: 1px solid #f0f0f0; - padding-left: 20px; -} - -.dotted-border { - border-bottom: 1px dashed #707070; - - :last-of-type { - border-bottom: none !important; - } -} - -.p-default { - padding: 24px !important; -} - -.border-l-b { - border-left: 1px solid #f0f0f0; - border-bottom: 1px solid #f0f0f0; - border-right: 1px solid #f0f0f0; -} - -.project-name { - font-size: 14px; - font-weight: 500; - color: #292929; -} - -.project-name:hover { - color: #40a9ff; -} - -.ant-popover-inner-content { - padding: 12px 24px; -} - -/** ---------- animations ---------- */ -@-webkit-keyframes scale-up-hor-left { - 0% { - -webkit-transform: scaleX(0.4); - transform: scaleX(0.4); - -webkit-transform-origin: 0% 0%; - transform-origin: 0% 0%; - } - - 100% { - -webkit-transform: scaleX(1); - transform: scaleX(1); - -webkit-transform-origin: 0% 0%; - transform-origin: 0% 0%; - } -} - -@keyframes scale-up-hor-left { - 0% { - -webkit-transform: scaleX(0.4); - transform: scaleX(0.4); - -webkit-transform-origin: 0% 0%; - transform-origin: 0% 0%; - } - - 100% { - -webkit-transform: scaleX(1); - transform: scaleX(1); - -webkit-transform-origin: 0% 0%; - transform-origin: 0% 0%; - } -} - -@-webkit-keyframes slideDown { - from { - -webkit-transform: translateY(-50%); - transform: translateY(-50%); - opacity: 0; - } - - to { - -webkit-transform: translateY(0); - transform: translateY(0); - opacity: 1; - } -} - -@keyframes slideDown { - from { - -webkit-transform: translateY(-50%); - transform: translateY(-50%); - opacity: 0; - } - - to { - -webkit-transform: translateY(0); - transform: translateY(0); - opacity: 1; - } -} - -.weekend { - background: linear-gradient(-45deg, rgb(230, 230, 230) 12.5%, transparent 12.5%, transparent 50%, rgb(230, 230, 230) 50%, rgb(230, 230, 230) 62.5%, transparent 62.5%, transparent) 0% 0% / 5px 5px; -} - -.sunday { - border-right: solid 1px silver !important; -} - -.member { - font-size: 13px; - font-weight: 400; -} - -.header-bg { - background-color: #fafafa !important; -} - -.month { - font-weight: 500; -} diff --git a/worklenz-frontend/src/scss/editable-table.scss b/worklenz-frontend/src/scss/editable-table.scss deleted file mode 100644 index 5e72f124..00000000 --- a/worklenz-frontend/src/scss/editable-table.scss +++ /dev/null @@ -1,73 +0,0 @@ -.editable-cell { - position: relative; - padding: 5px 12px; - cursor: pointer; -} - -.editable-row { - display: flex; - justify-content: space-between; - - .plus-icon { - display: none; - } - - &:hover { - .plus-icon { - display: block; - } - } - - textarea { - width: 100%; - } -} - -.editable-row:hover .editable-cell { - border: 1px solid #d9d9d9; - border-radius: 4px; - padding: 4px 11px; - cursor: text; - width: 100%; -} - -.editable { - cursor: pointer; - - .plus-icon { - display: none; - position: absolute; - right: 10px; - top: 0; - //height: 100%; - margin: auto; - bottom: 0; - align-items: center; - //font-size: 18px; - //color: #52c41a; - } - - &:hover { - background: #f0f2f5 !important; - - .plus-icon { - display: flex; - } - } - - nz-select { - //position: absolute; - //left: 0; - //right: 0; - //top: 0; - //bottom: 0; - - nz-select-top-control { - display: flex !important; - align-items: center !important; - height: 100% !important; - border: 0 !important; - background-color: transparent !important; - } - } -} diff --git a/worklenz-frontend/src/scss/scrollbar.scss b/worklenz-frontend/src/scss/scrollbar.scss deleted file mode 100644 index 55bcc64e..00000000 --- a/worklenz-frontend/src/scss/scrollbar.scss +++ /dev/null @@ -1,29 +0,0 @@ -/* Customize website's scrollbar like Mac OS -Not supports in Firefox and IE */ - -/* total width */ -*::-webkit-scrollbar { - background-color: #fff; - width: 16px; -} - -/* background of the scrollbar except button or resizer */ -*::-webkit-scrollbar-track { - background-color: #fff; -} - -/* scrollbar itself */ -*::-webkit-scrollbar-thumb { - background-color: #cccccc; - border-radius: 16px; - border: 4px solid #fff; - - &:hover { - background-color: #ababab; - } -} - -/* set button(top and bottom of the scrollbar) */ -*::-webkit-scrollbar-button { - display: none; -} diff --git a/worklenz-frontend/src/scss/task-view.scss b/worklenz-frontend/src/scss/task-view.scss deleted file mode 100644 index 1ab1f065..00000000 --- a/worklenz-frontend/src/scss/task-view.scss +++ /dev/null @@ -1,54 +0,0 @@ - -.task-view { - nz-form-label { - width: 130px; - max-width: 130px; - } - - .control-hover { - cursor: pointer; - } - - .task-description-editor { - cursor: text; - user-select: text; - } - - .task-description-editor:not(.editing), - .control-hover { - border: 1px solid transparent; - border-radius: 4px; - - &:hover { - border-color: #d9d9d9; - } - } -} - -.task-form-drawer-opened { - .ant-collapse-header { - padding: 0 !important; - } - - .ant-drawer-body { - padding-top: 0 !important; - } - - & .ant-drawer-header-title { - margin-bottom: 16px; - align-items: baseline; - } - - .ant-drawer-header { - border-bottom: 1px solid whitesmoke; - padding-bottom: 0; - align-items: baseline; - } - - .task-drawer-tabset { - .ant-tabs-nav-wrap { - margin-left: 27px; - padding-top: 8px; - } - } -} diff --git a/worklenz-frontend/src/services/alerts/alertMiddleware.ts b/worklenz-frontend/src/services/alerts/alertMiddleware.ts new file mode 100644 index 00000000..0a378a86 --- /dev/null +++ b/worklenz-frontend/src/services/alerts/alertMiddleware.ts @@ -0,0 +1,25 @@ +// src/services/notification/notificationMiddleware.ts + +import { Middleware } from '@reduxjs/toolkit'; +import { showAlert, hideAlert } from './alertSlice'; +import alertService from './alertService'; + +export const notificationMiddleware: Middleware = store => next => action => { + if (showAlert.match(action)) { + const notification: any = action.payload; + // Show notification using service + alertService.error(notification.title, notification.message, notification.duration); + + // Auto-remove notification after duration + if (notification.duration !== 0) { + setTimeout( + () => { + store.dispatch(hideAlert(notification.id)); + }, + (notification.duration || store.getState().notification.config.duration) * 1000 + ); + } + } + + return next(action); +}; diff --git a/worklenz-frontend/src/services/alerts/alertService.ts b/worklenz-frontend/src/services/alerts/alertService.ts new file mode 100644 index 00000000..df4d2865 --- /dev/null +++ b/worklenz-frontend/src/services/alerts/alertService.ts @@ -0,0 +1,67 @@ +import { AlertType } from '@/types/alert.types'; +import DOMPurify from 'dompurify'; +import { notification } from 'antd'; +class AlertService { + private static instance: AlertService; + private activeAlerts: Set = new Set(); + + private constructor() {} + + public static getInstance(): AlertService { + if (!AlertService.instance) { + AlertService.instance = new AlertService(); + } + return AlertService.instance; + } + + private sanitizeHtml(content: string): string { + return DOMPurify.sanitize(content, { + ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'], + ALLOWED_ATTR: ['href', 'target'], + }); + } + + private show(type: AlertType, title: string, message: string, duration?: number): void { + if (this.activeAlerts.has(message)) return; + + const safeTitle = this.sanitizeHtml(title); + const safeMessage = this.sanitizeHtml(message); + + this.activeAlerts.add(message); + + notification[type]({ + message: safeTitle, + description: safeMessage, + duration: duration || 5, + placement: 'topRight', + style: { borderRadius: '4px' }, + onClose: () => { + this.activeAlerts.delete(message); + }, + }); + } + + public success(title: string, message: string, duration?: number): void { + this.show('success', title, message, duration); + } + + public error(title: string, message: string, duration?: number): void { + this.show('error', title, message, duration); + } + + public info(title: string, message: string, duration?: number): void { + this.show('info', title, message, duration); + } + + public warning(title: string, message: string, duration?: number): void { + this.show('warning', title, message, duration); + } + + public clearAll(): void { + notification.destroy(); + this.activeAlerts.clear(); + } +} + +export const alertService = AlertService.getInstance(); +export default alertService; diff --git a/worklenz-frontend/src/services/alerts/alertSlice.ts b/worklenz-frontend/src/services/alerts/alertSlice.ts new file mode 100644 index 00000000..f7dd324d --- /dev/null +++ b/worklenz-frontend/src/services/alerts/alertSlice.ts @@ -0,0 +1,43 @@ +import { AlertConfig, AlertState, AlertType } from '@/types/alert.types'; +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { notification } from 'antd'; +import DOMPurify from 'dompurify'; + +const initialState: AlertState = { + activeAlerts: new Set(), + config: { + position: 'topRight', + duration: 4.5, + maxCount: 5, + }, +}; + +const alertSlice = createSlice({ + name: 'alert', + initialState, + reducers: { + showAlert: ( + state, + action: PayloadAction<{ + type: AlertType; + title: string; + message: string; + duration?: number; + }> + ) => { + if (!state.activeAlerts.has(action.payload.message)) { + state.activeAlerts.add(action.payload.message); + } + }, + hideAlert: (state, action: PayloadAction) => { + state.activeAlerts.delete(action.payload); + }, + updateAlertConfig: (state, action: PayloadAction>) => { + state.config = { ...state.config, ...action.payload }; + }, + }, +}); + +export const { showAlert, hideAlert, updateAlertConfig } = alertSlice.actions; + +export default alertSlice.reducer; diff --git a/worklenz-frontend/src/services/auth/auth.service.ts b/worklenz-frontend/src/services/auth/auth.service.ts new file mode 100644 index 00000000..0fc245dd --- /dev/null +++ b/worklenz-frontend/src/services/auth/auth.service.ts @@ -0,0 +1,69 @@ +import { ILocalSession } from '@/types/auth/local-session.types'; +import logger from '@/utils/errorLogger'; +import { deleteSession, getUserSession, hasSession, setSession } from '@/utils/session-helper'; +import { NavigateFunction } from 'react-router-dom'; + +class AuthService { + private readonly navigate: NavigateFunction; + + constructor(navigate: NavigateFunction) { + this.navigate = navigate; + } + + // Computed property for user role + get role(): string { + const user = this.getCurrentSession(); + if (!user) return 'Unknown'; + if (user.owner) return 'Owner'; + if (user.is_admin) return 'Admin'; + return 'Member'; + } + + // Session management methods + public isAuthenticated(): boolean { + return !!this.getCurrentSession(); + } + + public isExpired(): boolean { + return !!this.getCurrentSession()?.is_expired; + } + + public setCurrentSession(user: ILocalSession): void { + setSession(user); + } + + + public getCurrentSession(): ILocalSession | null { + return getUserSession(); + } + + public isOwnerOrAdmin(): boolean { + return !!(this.getCurrentSession()?.owner || this.getCurrentSession()?.is_admin); + } + + // Sign out methods + public async signOut(): Promise { + try { + if (hasSession()) { + deleteSession(); + } + } catch (e) { + logger.error('Error signing out', e); + } + } + + public hasCompletedSetup(): boolean { + const user = this.getCurrentSession(); + return !!user?.setup_completed; + } + + private onSignOutConfirm(): void { + void this.signOut(); + window.location.href = '/secure/logout'; + } +} + +// Hook for using AuthService in components +export const createAuthService = (navigate: NavigateFunction): AuthService => { + return new AuthService(navigate); +}; diff --git a/worklenz-frontend/src/services/socket/socket.service.ts b/worklenz-frontend/src/services/socket/socket.service.ts new file mode 100644 index 00000000..e5e2f0cf --- /dev/null +++ b/worklenz-frontend/src/services/socket/socket.service.ts @@ -0,0 +1,154 @@ +import { Message, User } from '@/types/socket.types'; +import logger from '@/utils/errorLogger'; +import { Socket } from 'socket.io-client'; + +export class SocketService { + private socket: Socket | null = null; + private static instance: SocketService | null = null; + + private constructor() {} + + public static getInstance(): SocketService { + if (!SocketService.instance) { + SocketService.instance = new SocketService(); + } + return SocketService.instance; + } + + public init(socket: Socket): void { + if (!this.socket) { + this.socket = socket; + this.setupDefaultListeners(); + } + } + + private setupDefaultListeners(): void { + if (!this.socket) return; + + this.socket.on('connect', () => { + logger.info('Socket connected in service'); + }); + + this.socket.on('disconnect', () => { + logger.info('Socket disconnected in service'); + }); + + this.socket.on('error', (error: Error) => { + logger.error('Socket error', { error }); + }); + } + + // Message Methods + public sendMessage(message: Omit): Promise { + return new Promise((resolve, reject) => { + if (!this.socket) { + reject(new Error('Socket not initialized')); + return; + } + + this.socket.emit( + 'send_message', + message, + (response: { success: boolean; error?: string }) => { + if (response.success) { + resolve(); + } else { + reject(new Error(response.error || 'Failed to send message')); + } + } + ); + }); + } + + public onMessage(callback: (message: Message) => void): () => void { + if (!this.socket) throw new Error('Socket not initialized'); + + this.socket.on('message', callback); + return () => this.socket?.off('message', callback); + } + + // User Methods + public updateUserStatus(status: User['status']): Promise { + return new Promise((resolve, reject) => { + if (!this.socket) { + reject(new Error('Socket not initialized')); + return; + } + + this.socket.emit('update_status', { status }, (response: { success: boolean }) => { + if (response.success) { + resolve(); + } else { + reject(new Error('Failed to update status')); + } + }); + }); + } + + public onUserStatusChange(callback: (user: User) => void): () => void { + if (!this.socket) throw new Error('Socket not initialized'); + + this.socket.on('user_status_changed', callback); + return () => this.socket?.off('user_status_changed', callback); + } + + // Room Methods + public joinRoom(roomId: string): Promise { + return new Promise((resolve, reject) => { + if (!this.socket) { + reject(new Error('Socket not initialized')); + return; + } + + this.socket.emit('join_room', { roomId }, (response: { success: boolean }) => { + if (response.success) { + resolve(); + } else { + reject(new Error('Failed to join room')); + } + }); + }); + } + + public leaveRoom(roomId: string): Promise { + return new Promise((resolve, reject) => { + if (!this.socket) { + reject(new Error('Socket not initialized')); + return; + } + + this.socket.emit('leave_room', { roomId }, (response: { success: boolean }) => { + if (response.success) { + resolve(); + } else { + reject(new Error('Failed to leave room')); + } + }); + }); + } + + // Custom Event Handler + public on(event: string, callback: (data: T) => void): () => void { + if (!this.socket) throw new Error('Socket not initialized'); + + this.socket.on(event, callback); + return () => this.socket?.off(event, callback); + } + + public emit(event: string, data: T): Promise { + return new Promise((resolve, reject) => { + if (!this.socket) { + reject(new Error('Socket not initialized')); + return; + } + + this.socket.emit(event, data, (response: { success: boolean; error?: string }) => { + if (response.success) { + resolve(); + } else { + reject(new Error(response.error || 'Emission failed')); + } + }); + }); + } +} diff --git a/worklenz-frontend/src/services/task-list/taskList.service.ts b/worklenz-frontend/src/services/task-list/taskList.service.ts new file mode 100644 index 00000000..c8e45195 --- /dev/null +++ b/worklenz-frontend/src/services/task-list/taskList.service.ts @@ -0,0 +1,40 @@ +import { getCurrentGroupBoard } from '@/features/board/board-slice'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; +import { IGroupByOption } from '@/types/tasks/taskList.types'; +import { NavigateFunction } from 'react-router-dom'; + +const GROUP_BY_STATUS_VALUE = 'status'; +const GROUP_BY_PRIORITY_VALUE = 'priority'; +const GROUP_BY_PHASE_VALUE = 'phase'; +const GROUP_BY_OPTIONS: IGroupByOption[] = [ + { label: 'Status', value: GROUP_BY_STATUS_VALUE }, + { label: 'Priority', value: GROUP_BY_PRIORITY_VALUE }, + { label: 'Phase', value: GROUP_BY_PHASE_VALUE }, +]; + +class TaskListService { + private readonly navigate: NavigateFunction; + + constructor(navigate: NavigateFunction) { + this.navigate = navigate; + } +} + +// Hook for using AuthService in components +export const createTaskListService = (navigate: NavigateFunction): TaskListService => { + return new TaskListService(navigate); +}; + +export const getGroupIdByGroupedColumn = (task: IProjectTask) => { + const groupBy = getCurrentGroupBoard().value; + if (groupBy === GROUP_BY_STATUS_VALUE) + return task.status as string; + + if (groupBy === GROUP_BY_PRIORITY_VALUE) + return task.priority as string; + + if (groupBy === GROUP_BY_PHASE_VALUE) + return task.phase_id as string; + + return null; +} diff --git a/worklenz-frontend/src/shared/constants.ts b/worklenz-frontend/src/shared/constants.ts new file mode 100644 index 00000000..30e1568d --- /dev/null +++ b/worklenz-frontend/src/shared/constants.ts @@ -0,0 +1,315 @@ +import { IRPTDuration } from '@/types/reporting/reporting.types'; +import { + CheckCircleOutlined, + ClockCircleOutlined, + CloseCircleOutlined, + StopOutlined, +} from '@ant-design/icons'; +import dayjs from 'dayjs'; + +export const avatarNamesMap: { [x: string]: string } = { + A: '#154c9b', + B: '#3b7ad4', + C: '#70a6f3', + D: '#7781ca', + E: '#9877ca', + F: '#c178c9', + G: '#ee87c5', + H: '#ca7881', + I: '#75c9c0', + J: '#75c997', + K: '#80ca79', + L: '#aacb78', + M: '#cbbc78', + N: '#cb9878', + O: '#bb774c', + P: '#905b39', + Q: '#903737', + R: '#bf4949', + S: '#f37070', + T: '#ff9c3c', + U: '#fbc84c', + V: '#cbc8a1', + W: '#a9a9a9', + X: '#767676', + Y: '#cb9878', + Z: '#903737', + '+': '#9e9e9e', +}; + +export const AvatarNamesMap: { [x: string]: string } = { + A: '#154c9b', + B: '#3b7ad4', + C: '#70a6f3', + D: '#7781ca', + E: '#9877ca', + F: '#c178c9', + G: '#ee87c5', + H: '#ca7881', + I: '#75c9c0', + J: '#75c997', + K: '#80ca79', + L: '#aacb78', + M: '#cbbc78', + N: '#cb9878', + O: '#bb774c', + P: '#905b39', + Q: '#903737', + R: '#bf4949', + S: '#f37070', + T: '#ff9c3c', + U: '#fbc84c', + V: '#cbc8a1', + W: '#a9a9a9', + X: '#767676', + Y: '#cb9878', + Z: '#903737', + '+': '#9e9e9e', +}; + +export const NumbersColorMap: { [x: string]: string } = { + '0': '#154c9b', + '1': '#3b7ad4', + '2': '#70a6f3', + '3': '#7781ca', + '4': '#9877ca', + '5': '#c178c9', + '6': '#ee87c5', + '7': '#ca7881', + '8': '#75c9c0', + '9': '#75c997', +}; + +export const ProjectsDefaultColorCodes = [ + '#154c9b', + '#3b7ad4', + '#70a6f3', + '#7781ca', + '#9877ca', + '#c178c9', + '#ee87c5', + '#ca7881', + '#75c9c0', + '#75c997', + '#80ca79', + '#aacb78', + '#cbbc78', + '#cb9878', + '#bb774c', + '#905b39', + '#903737', + '#bf4949', + '#f37070', + '#ff9c3c', + '#fbc84c', + '#cbc8a1', + '#a9a9a9', + '#767676', +]; + +export const PhaseColorCodes = [ + '#154c9b', + '#3b7ad4', + '#70a6f3', + '#7781ca', + '#9877ca', + '#c178c9', + '#ee87c5', + '#ca7881', + '#75c9c0', + '#75c997', + '#80ca79', + '#aacb78', + '#cbbc78', + '#cb9878', + '#bb774c', + '#905b39', + '#903737', + '#bf4949', + '#f37070', + '#ff9c3c', + '#fbc84c', + '#cbc8a1', + '#a9a9a9', + '#767676', + '#cb9878', + '#903737', + '#9e9e9e', +]; + +export const PriorityColorCodes: { [x: number]: string } = { + 0: '#75c997', + 1: '#fbc84c', + 2: '#f37070', +}; + +export const API_BASE_URL = '/api/v1'; +export const AUTH_API_BASE_URL = '/secure'; + +export const DEFAULT_TASK_NAME = 'Untitled Task'; + +export const YESTERDAY = 'YESTERDAY'; +export const LAST_WEEK = 'LAST_WEEK'; +export const LAST_MONTH = 'LAST_MONTH'; +export const LAST_QUARTER = 'LAST_QUARTER'; +export const PREV_WEEK = 'PREV_WEEK'; +export const PREV_MONTH = 'PREV_MONTH'; +export const ALL_TIME = 'ALL_TIME'; + +export const PASSWORD_POLICY = + 'Minimum of 8 characters, with upper and lowercase and a number and a symbol.'; + +export const HTML_TAG_REGEXP = /<\/?[^>]+>/gi; +export const UNMAPPED = 'Unmapped'; + +export const TASK_STATUS_TODO_COLOR = '#a9a9a9'; +export const TASK_STATUS_DOING_COLOR = '#70a6f3'; +export const TASK_STATUS_DONE_COLOR = '#75c997'; + +export const TASK_PRIORITY_LOW_COLOR = '#75c997'; +export const TASK_PRIORITY_MEDIUM_COLOR = '#fbc84c'; +export const TASK_PRIORITY_HIGH_COLOR = '#f37070'; + +export const TASK_DUE_COMPLETED_COLOR = '#75c997'; +export const TASK_DUE_UPCOMING_COLOR = '#70a6f3'; +export const TASK_DUE_OVERDUE_COLOR = '#f37070'; +export const TASK_DUE_NO_DUE_COLOR = '#a9a9a9'; + +export const DEFAULT_PAGE_SIZE = 20; +export const PAGE_SIZE_OPTIONS = ['5', '10', '15', '20', '50', '100']; +export const ALPHA_CHANNEL = '69'; + +export const FILTER_INDEX_KEY = 'worklenz.projects.filter_index'; +export const DISPLAY_MODE_KEY = 'worklenz.projects.display_as'; +export const WORKLENZ_REDIRECT_PROJ_KEY = 'worklenz.redirect_proj'; +export const PROJECT_SORT_FIELD = 'worklenz.projects.sort_field'; +export const PROJECT_SORT_ORDER = 'worklenz.projects.sort_order'; +export const PROJECT_LIST_COLUMNS = 'worklenz.reporting.projects.column_list'; + +export const PROJECT_STATUS_ICON_MAP = { + 'check-circle': CheckCircleOutlined, + 'clock-circle': ClockCircleOutlined, + 'clock-circle-two-tone': ClockCircleOutlined, + 'close-circle': CloseCircleOutlined, + stop: StopOutlined, +}; +export const DRAWER_ANIMATION_INTERVAL = 200; + +export const DATE_FORMAT_OPTIONS: Intl.DateTimeFormatOptions = { + year: 'numeric', + month: 'short', + day: 'numeric', +}; + +export const NOTIFICATION_OPTION_UNREAD = 'Unread'; +export const NOTIFICATION_OPTION_READ = 'Read'; +export const NOTIFICATION_OPTIONS = [NOTIFICATION_OPTION_UNREAD, NOTIFICATION_OPTION_READ]; + +export const MY_DASHBOARD_ACTIVE_FILTER = 'my-dashboard-active-filter'; +export const MY_DASHBOARD_DEFAULT_VIEW = 'All'; + +export const CELL_WIDTH = 75; + +export const SUBSCRIPTION_STATUS = { + ACTIVE: 'active', + PASTDUE: 'past_due', + PAUSED: 'paused', + DELETED: 'deleted', + TRIALING: 'trialing', + FREE: 'free', +}; + +export enum IPaddlePlans { + FREE = "FREE", + ANNUAL = "ANNUAL", + MONTHLY = "MONTHLY", +} + +export enum ISUBSCRIPTION_TYPE { + LIFE_TIME_DEAL = "LIFE_TIME_DEAL", + PADDLE = "PADDLE", + TRIAL = "TRIAL", + CUSTOM = "CUSTOM", + FREE = "FREE", + CREDIT = "CREDIT", +} + +export const IconsMap: { [x: string]: string; } = { + ai: "ai.png", + avi: "avi.png", + css: "css.png", + csv: "csv.png", + doc: "doc.png", + docx: "doc.png", + exe: "exe.png", + html: "html.png", + js: "js.png", + jpg: "jpg.png", + jpeg: "jpg.png", + json: "json.png", + mp3: "mp3.png", + mp4: "mp4.png", + pdf: "pdf.png", + png: "png.png", + ppt: "ppt.png", + psd: "psd.png", + search: "search.png", + svg: "svg.png", + txt: "txt.png", + xls: "xls.png", + xml: "xml.png", + zip: "zip.png", +} + +export const durations: IRPTDuration[] = [ + { + key: YESTERDAY, + label: 'yesterdayText', + dates: new Date(dayjs().subtract(1, 'day').format()).toString(), + }, + { + key: LAST_WEEK, + label: 'lastSevenDaysText', + dates: + new Date(dayjs().subtract(7, 'day').format()).toString() + + ' - ' + + new Date(dayjs().format()).toString(), + }, + { + key: PREV_WEEK, + label: 'lastWeekText', + dates: + new Date(dayjs().startOf('week').subtract(1, 'week').format()).toString() + + ' - ' + + new Date(dayjs().endOf('week').subtract(1, 'week').format()).toString(), + }, + { + key: LAST_MONTH, + label: 'lastThirtyDaysText', + dates: + new Date(dayjs().subtract(30, 'day').format()).toString() + + ' - ' + + new Date(dayjs().format()).toString(), + }, + { + key: PREV_MONTH, + label: 'lastMonthText', + dates: + new Date(dayjs().startOf('month').subtract(1, 'month').format()).toString() + + ' - ' + + new Date(dayjs().endOf('month').subtract(1, 'month').format()).toString(), + }, + { + key: LAST_QUARTER, + label: 'lastThreeMonthsText', + dates: + new Date(dayjs().subtract(3, 'month').format()).toString() + + ' - ' + + new Date(dayjs().format()).toString(), + }, + { + key: ALL_TIME, + label: 'allTimeText', + dates: '', + }, +]; diff --git a/worklenz-frontend/src/app/shared/socket-events.ts b/worklenz-frontend/src/shared/socket-events.ts similarity index 87% rename from worklenz-frontend/src/app/shared/socket-events.ts rename to worklenz-frontend/src/shared/socket-events.ts index 634ad2fc..8caf509d 100644 --- a/worklenz-frontend/src/app/shared/socket-events.ts +++ b/worklenz-frontend/src/shared/socket-events.ts @@ -51,5 +51,11 @@ export enum SocketEvents { SCHEDULE_MEMBER_ALLOCATION_CREATE, SCHEDULE_MEMBER_START_DATE_CHANGE, SCHEDULE_MEMBER_END_DATE_CHANGE, - PROJECT_DATA_CHANGE + PROJECT_DATA_CHANGE, + TASK_BILLABLE_CHANGE, + TASK_RECURRING_CHANGE, + TASK_ASSIGNEES_CHANGE, + TASK_CUSTOM_COLUMN_UPDATE, + CUSTOM_COLUMN_PINNED_CHANGE, + TEAM_MEMBER_ROLE_CHANGE, } diff --git a/worklenz-frontend/src/shared/worklenz-analytics-events.ts b/worklenz-frontend/src/shared/worklenz-analytics-events.ts new file mode 100644 index 00000000..bfe75f38 --- /dev/null +++ b/worklenz-frontend/src/shared/worklenz-analytics-events.ts @@ -0,0 +1,167 @@ +// Analytics event constants organized by feature area + +// Authentication & Login +export const evt_login_page_visit = 'login_page_visit'; +export const evt_login_with_email_click = 'login_with_email_click'; +export const evt_login_with_google_click = 'login_with_google_click'; +export const evt_login_remember_me_click = 'login_remember_me_click'; + +// Registration & Signup +export const evt_signup_page_visit = 'signup_page_visit'; +export const evt_signup_with_email_click = 'signup_with_email_click'; +export const evt_signup_with_google_click = 'signup_with_google_click'; +export const evt_forgot_password_page_visit = 'forgot_password_page_visit'; +export const evt_verify_reset_email_page_visit = 'verify_reset_email_page_visit'; + +// Account Setup & Onboarding +export const evt_account_setup_visit = 'account_setup_visit'; +export const evt_account_setup_complete = 'account_setup_complete'; +export const evt_account_setup_skip_invite = 'account_setup_skip_invite'; +export const evt_account_setup_template_complete = 'account_setup_template_complete'; + +// Password Management +export const evt_reset_password_click = 'reset_password_click'; + +// Project Management +export const evt_projects_page_visit = 'projects_page_visit'; +export const evt_projects_create_click = 'projects_create_click'; +export const evt_projects_create = 'projects_create'; +export const evt_projects_refresh_click = 'projects_refresh_click'; +export const evt_projects_search = 'projects_search'; +export const evt_projects_archive = 'projects_archive'; +export const evt_projects_unarchive = 'projects_unarchive'; +export const evt_projects_settings_click = 'projects_settings_click'; +export const evt_projects_archive_all = 'projects_archive_all'; +export const evt_projects_unarchive_all = 'projects_unarchive_all'; + +// Project Views & Navigation +export const evt_project_board_visit = 'project_board_visit'; +export const evt_project_workload_visit = 'project_workload_visit'; +export const evt_project_roadmap_visit = 'project_roadmap_visit'; +export const evt_project_insights_overview_visit = 'project_insights_overview_visit'; +export const evt_project_insights_members_visit = 'project_insights_members_visit'; +export const evt_project_insights_tasks_visit = 'project_insights_tasks_visit'; +export const evt_project_files_visit = 'project_files_visit'; +export const evt_project_members_visit = 'project_members_visit'; + +// Project Actions +export const evt_project_task_create = 'project_task_create'; +export const evt_project_invite_members_click = 'project_invite_members_click'; +export const evt_project_invite_members = 'project_invite_members'; +export const evt_project_refresh_click = 'project_refresh_click'; +export const evt_project_settings_click = 'project_settings_click'; +export const evt_project_import_tasks_click = 'project_import_tasks_click'; +export const evt_project_import_tasks = 'project_import_tasks'; +export const evt_project_update = 'project_update'; + +// Board Interactions +export const evt_project_board_open_task = 'project_board_open_task'; +export const evt_project_board_transition_task = 'project_board_transition_task'; +export const evt_project_board_update_task_order = 'project_board_update_task_order'; +export const evt_project_board_column_setting_click = 'project_board_column_setting_click'; +export const evt_project_board_create_status_click = 'project_board_create_status_click'; +export const evt_project_board_create_status = 'project_board_create_status'; +export const evt_project_board_create_task_click = 'project_board_create_task_click'; + +// Task List Management +export const evt_project_task_list_visit = 'project_task_list_visit'; +export const evt_project_task_list_show_archived = 'project_task_list_show_archived'; +export const evt_project_task_list_bulk_change_status = 'project_task_list_bulk_change_status'; +export const evt_project_task_list_bulk_change_priority = 'project_task_list_bulk_change_priority'; +export const evt_project_task_list_bulk_change_phase = 'project_task_list_bulk_change_phase'; +export const evt_project_task_list_bulk_update_labels = 'project_task_list_bulk_update_labels'; +export const evt_project_task_list_bulk_assign_me = 'project_task_list_bulk_assign_me'; +export const evt_project_task_list_bulk_assign_members = 'project_task_list_bulk_assign_members'; +export const evt_project_task_list_bulk_archive = 'project_task_list_bulk_archive'; +export const evt_project_task_list_bulk_delete = 'project_task_list_bulk_delete'; +export const evt_project_task_list_context_menu_assign_me = + 'project_task_list_context_menu_assign_me'; +export const evt_project_task_list_context_menu_archive = 'project_task_list_context_menu_archive'; +export const evt_project_task_list_context_menu_delete = 'project_task_list_context_menu_delete'; +export const evt_project_task_list_create_task = 'project_task_list_create_task'; +export const evt_project_task_list_create_subtask = 'project_task_list_create_subtask'; +export const evt_project_task_list_open_task = 'project_task_list_open_task'; +export const evt_project_task_list_drag_and_move = 'project_task_list_drag_and_move'; +export const evt_project_task_list_show_fields = 'project_task_list_show_fields'; +export const evt_project_task_list_search_task = 'project_task_list_search_task'; + +// Team & People Management +export const evt_people_page_visit = 'people_page_visit'; +export const evt_people_refresh_click = 'people_refresh_click'; +export const evt_people_search = 'people_search'; +export const evt_people_create_click = 'people_create_click'; +export const evt_people_click = 'people_click'; +export const evt_people_create = 'people_create'; +export const evt_people_delete = 'people_delete'; +export const evt_people_activate = 'people_activate'; +export const evt_people_deactivate = 'people_deactivate'; +export const evt_project_import_from_template_click = 'project_import_from_template_click'; + +// Schedule & Planning +export const evt_schedule_page_visit = 'schedule_page_visit'; + +// Workload Management +export const evt_workload_drag_change_date = 'workload_drag_change_date'; +export const evt_workload_drag_move = 'workload_drag_move'; +export const evt_workload_task_open = 'workload_task_open'; + +// Roadmap Features +export const evt_roadmap_task_create = 'roadmap_task_create'; +export const evt_roadmap_sub_task_create = 'roadmap_sub_task_create'; +export const evt_roadmap_drag_change_date = 'roadmap_drag_change_date'; +export const evt_roadmap_drag_move = 'roadmap_drag_move'; +export const evt_roadmap_task_open = 'roadmap_task_open'; +export const evt_roadmap_task_drag_n_sort = 'roadmap_task_drag_n_sort'; + +// Settings & Configuration +export const evt_settings_profile_visit = 'settings_profile_visit'; +export const evt_settings_profile_avatar_upload = 'settings_profile_avatar_upload'; +export const evt_settings_profile_name_change = 'settings_profile_name_change'; +export const evt_settings_notifications_visit = 'settings_notifications_visit'; +export const evt_settings_clients_visit = 'settings_clients_visit'; +export const evt_settings_job_titles_visit = 'settings_job_titles_visit'; +export const evt_settings_labels_visit = 'settings_labels_visit'; +export const evt_settings_categories_visit = 'settings_categories_visit'; +export const evt_settings_task_templates_visit = 'settings_task_templates_visit'; +export const evt_settings_teams_visit = 'settings_teams_visit'; +export const evt_settings_change_password_visit = 'settings_change_password_visit'; +export const evt_settings_language_and_region_visit = 'settings_language_and_region_visit'; +export const evt_settings_language_changed = 'settings_language_changed'; +export const evt_settings_profile_update = 'settings_profile_update'; +export const evt_settings_notifications_update = 'settings_notifications_update'; +export const evt_settings_clients_create = 'settings_clients_create'; +export const evt_settings_job_titles_create = 'settings_job_titles_create'; +export const evt_settings_labels_delete = 'settings_labels_delete'; +export const evt_settings_category_delete = 'settings_category_delete'; +export const evt_settings_task_templates_delete = 'settings_task_templates_delete'; +export const evt_settings_profile_picture_update = 'settings_profile_picture_update'; + +// Common Actions +export const evt_common_switch_team = 'common_switch_team'; +export const evt_common_display_notifications = 'common_display_notifications'; +export const evt_common_logout = 'common_logout'; + +// Analytics & Reporting +export const evt_reporting_overview = 'reporting_overview_visit'; +export const evt_reporting_allocation = 'reporting_allocation_visit'; +export const evt_reporting_projects_overview = 'reporting_projects_overview_visit'; +export const evt_reporting_projects_custom = 'reporting_projects_custom_visit'; + +// Billing & Subscription +export const evt_billing_current_bill = 'billing_current_bill'; +export const evt_billing_configuration = 'billing_configuration'; +export const evt_billing_view_plans_modal = 'billing_view_plans_modal'; +export const evt_billing_pause_plan = 'billing_pause_plan'; +export const evt_billing_resume_plan = 'billing_resume_plan'; +export const evt_billing_add_more_seats = 'billing_add_more_seats'; + +// Admin Center +export const evt_admin_center_teams_visit = 'admin_center_teams_visit'; +export const evt_admin_center_users_visit = 'admin_center_users_visit'; +export const evt_admin_center_overview_visit = 'admin_center_overview_visit'; +export const evt_admin_center_teams_delete = 'admin_center_teams_delete'; +export const evt_admin_center_team_settings = 'admin_center_team_settings'; +export const evt_admin_center_projects_visit = 'admin_center_projects_visit'; + +// Project Preferences +export const evt_project_default_view_pinned = 'pin_default_project_view'; diff --git a/worklenz-frontend/src/socket/config.ts b/worklenz-frontend/src/socket/config.ts new file mode 100644 index 00000000..867330c7 --- /dev/null +++ b/worklenz-frontend/src/socket/config.ts @@ -0,0 +1,8 @@ +export const SOCKET_CONFIG = { + url: import.meta.env.VITE_SOCKET_URL || 'ws://localhost:3000', + options: { + transports: ['websocket'], + path: '/socket', + upgrade: true, + }, +}; diff --git a/worklenz-frontend/src/socket/socketContext.tsx b/worklenz-frontend/src/socket/socketContext.tsx new file mode 100644 index 00000000..8d1480d5 --- /dev/null +++ b/worklenz-frontend/src/socket/socketContext.tsx @@ -0,0 +1,149 @@ +import React, { createContext, useContext, useEffect, useState, useRef } from 'react'; +import io, { Socket } from 'socket.io-client'; +import { useTranslation } from 'react-i18next'; + +import { SOCKET_CONFIG } from './config'; +import logger from '@/utils/errorLogger'; +import { Modal, message } from 'antd'; +import { SocketEvents } from '@/shared/socket-events'; +import { getUserSession } from '@/utils/session-helper'; + +interface SocketContextType { + socket: Socket | null; + connected: boolean; + modalContextHolder: React.ReactElement; +} + +const SocketContext = createContext(null); + +export const SocketProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const { t } = useTranslation('common'); + const socketRef = useRef(null); + const [connected, setConnected] = useState(false); + const [modal, contextHolder] = Modal.useModal(); + const profile = getUserSession(); // Adjust based on your Redux structure + const [messageApi, messageContextHolder] = message.useMessage(); // Add message API + const hasShownConnectedMessage = useRef(false); // Add ref to track if message was shown + + // Initialize socket connection + useEffect(() => { + // Only create a new socket if one doesn't exist + if (!socketRef.current) { + socketRef.current = io(SOCKET_CONFIG.url, { + ...SOCKET_CONFIG.options, + reconnection: true, + reconnectionAttempts: Infinity, + reconnectionDelay: 1000, + reconnectionDelayMax: 5000, + timeout: 20000, + }); + } + + const socket = socketRef.current; + + // Set up event listeners before connecting + socket.on('connect', () => { + logger.info('Socket connected'); + setConnected(true); + + // Only show connected message once + if (!hasShownConnectedMessage.current) { + messageApi.success(t('connection-restored')); + hasShownConnectedMessage.current = true; + } + }); + + // Emit login event + if (profile && profile.id) { + socket.emit(SocketEvents.LOGIN.toString(), profile.id); + socket.once(SocketEvents.LOGIN.toString(), () => { + logger.info('Socket login success'); + }); + } + + socket.on('connect_error', error => { + logger.error('Connection error', { error }); + setConnected(false); + messageApi.error(t('connection-lost')); + // Reset the connected message flag on error + hasShownConnectedMessage.current = false; + }); + + socket.on('disconnect', () => { + logger.info('Socket disconnected'); + setConnected(false); + messageApi.loading(t('reconnecting')); + // Reset the connected message flag on disconnect + hasShownConnectedMessage.current = false; + + // Emit logout event + if (profile && profile.id) { + socket.emit(SocketEvents.LOGOUT.toString(), profile.id); + } + }); + + // Add team-related socket events + socket.on(SocketEvents.INVITATIONS_UPDATE.toString(), (message: string) => { + logger.info(message); + }); + + socket.on( + SocketEvents.TEAM_MEMBER_REMOVED.toString(), + (data: { teamId: string; message: string }) => { + if (!data) return; + + if (profile && profile.team_id === data.teamId) { + modal.confirm({ + title: 'You no longer have permissions to stay on this team!', + content: data.message, + closable: false, + cancelButtonProps: { disabled: true }, + onOk: () => window.location.reload(), + }); + } + } + ); + + // Connect after setting up listeners + socket.connect(); + + // Cleanup function + return () => { + if (socket) { + // Remove all listeners first + socket.off('connect'); + socket.off('connect_error'); + socket.off('disconnect'); + socket.off(SocketEvents.INVITATIONS_UPDATE.toString()); + socket.off(SocketEvents.TEAM_MEMBER_REMOVED.toString()); + socket.removeAllListeners(); + + // Then close the connection + socket.close(); + socketRef.current = null; + hasShownConnectedMessage.current = false; // Reset on unmount + } + }; + }, [messageApi, t]); // Add messageApi and t to dependencies + + const value = { + socket: socketRef.current, + connected, + modalContextHolder: contextHolder, + }; + + return ( + + {messageContextHolder} + {children} + + ); +}; + +export const useSocket = () => { + const context = useContext(SocketContext); + if (!context) { + throw new Error('useSocket must be used within a SocketProvider'); + } + return context; +}; diff --git a/worklenz-frontend/src/styles.scss b/worklenz-frontend/src/styles.scss deleted file mode 100644 index c4537413..00000000 --- a/worklenz-frontend/src/styles.scss +++ /dev/null @@ -1,1094 +0,0 @@ -@import "res/scss/antd-variables"; -// Bootstrap Grid System & Utilities (mt-2, w-100, text-center etc.) -$font-family-sans-serif: -apple-system, BlinkMacSystemFont, 'Inter', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; - -@import "bootstrap/scss/bootstrap-grid"; -@import "bootstrap/scss/bootstrap-utilities"; -@import "bootstrap/scss/functions"; -@import "bootstrap/scss/variables"; -@import "res/scss/gantt"; -@import "scss/scrollbar"; - -// Sidebar -$sidebar-width-lg: 240px; -$sidebar-folded-width: 70px; -$sidebar-dark-bg: #0c1427; - -$navbar-height: 60px; - -html, -body { - background: #ffffff; - min-height: 100vh; -} - -.list-unstyled { - padding-left: 0; - list-style: none; -} - -svg.icon-md { - width: 16px; - height: 16px; -} - -.ant-form-item-label > label.ant-form-item-required:not(.ant-form-item-required-mark-optional):before { - display: inline-block; - margin-right: 4px; - color: #ff4d4f; - font-size: 14px; - font-family: SimSun, sans-serif; - line-height: 1; - content: "*"; -} - -.undecorated-input { - border: none; - outline: none; - background: transparent; - - &:focus { - outline: none; - } -} - -.breadcrumb-container { - padding-top: 0px !important; - padding-bottom: 0px; - margin-bottom: 0px; -} - -.gantt-overflow::-webkit-scrollbar-track, -.sidemenu-list::-webkit-scrollbar-track, -.scrollable-list::-webkit-scrollbar-track, -.invite-box::-webkit-scrollbar-track, -.board-column::-webkit-scrollbar-track { - -webkit-box-shadow: inset 0 0 6px #eceded; - border-radius: 10px; - background-color: #eceded; -} - -.gantt-overflow::-webkit-scrollbar, -.sidemenu-list::-webkit-scrollbar, -.scrollable-list::-webkit-scrollbar, -.invite-box::-webkit-scrollbar, -.board-column::-webkit-scrollbar { - width: 6px; - height: 6px; - background-color: #F5F5F5; -} - -.gantt-overflow::-webkit-scrollbar-thumb, -.sidemenu-list::-webkit-scrollbar-thumb, -.scrollable-list::-webkit-scrollbar-thumb, -.invite-box::-webkit-scrollbar-thumb, -.board-column::-webkit-scrollbar-thumb { - border-radius: 10px; - -webkit-box-shadow: inset 0 0 6px #cccdcd; - background-color: #cccdcd; -} - -.sider { - position: fixed; - left: 0; - height: 100vh; - bottom: 0; - top: 48px; -} - -@media(max-width: 1200px) { - .table-xl-scroll { - table { - width: 87vw; - } - - .ant-table-container { - overflow: auto; - } - } -} - -@media(max-width: 991px) { - .table-xl-scroll table { - width: max-content; - } - - .page-layout-ml-auto { - margin-left: 0px; - } - - .ant-layout-sider-zero-width-trigger { - top: 95px; - } - - .sider { - z-index: 999; - } - - .ant-table-content { - overflow: auto; - } - - .mt-0 { - margin-top: 0px !important; - } -} - -@media(max-width: 767px) { - .pd-mobile-ps-0 { - padding-right: 0 !important; - padding-left: 0 !important; - } - - .pd-mobile-pe-0 { - padding-right: 0 !important; - padding-left: 0 !important; - margin-top: 24px !important; - } -} - -//.ant-layout-sider-light .ant-layout-sider-zero-width-trigger { -// color: #fff; -// background: #001529; -//} - -.page-layout-pt-auto { - padding-top: 48px; -} - -.ant-table-thead > tr > th, -.ant-table-tbody > tr > td, -.ant-table tfoot > tr > th, -.ant-table tfoot > tr > td { - padding: 12px 10px; -} - -.ant-list-bordered .ant-list-item.default-list-item-p-11 { - padding: 11px; -} - -.acc-settings-dropdown-item { - height: 2.5rem; -} - -.profile-name-default { - height: 30px; - margin-top: -15px; -} - -.profile-email-default { - height: 40px; -} - -.cdk-overlay-container { - z-index: 1100; -} - -.default-nz-space { - gap: 16px; -} - -//.ant-page-header { -// padding: 0px 12px !important; -//} - -.p-default, -.overview-table { - .ant-table-container { - padding: 24px; - } - - .ant-table-content { - overflow: auto; - } -} - -.ant-progress-bg { - height: 6px !important; -} - -a:hover { - transition: 0.2s all; - - .ant-typography { - color: #40a9ff; - transition: 0.2s all; - } -} - -.b-none::before { - display: none; -} - -.w-0 { - width: 0px; -} - -a { - .ant-badge-status-text { - margin-left: 5px; - } - - .ant-badge-status-dot { - width: 8px; - height: 8px; - vertical-align: baseline; - top: 0px; - } -} - -.mt-deafult { - margin-top: 24px; -} - -.gap-default { - gap: 6px; -} - -.mt-def { - margin-top: 24px; -} - -//.ant-collapse > .ant-collapse-item > .ant-collapse-header { -// padding: 16px 16px; -// font-size: 15px; -//} - -.custom-tabs { - .ant-tabs-nav { - background: white !important; - padding: 0px 24px; - margin-bottom: 0px; - } - - .ant-tabs-content-holder { - margin: 24px; - } -} - -.bg-and-grey { - background-color: #f0f2f5; -} - -.project-single { - .single-d-none { - display: none !important; - } - - .page-data { - margin: 0px; - - .mt-3 { - margin-top: 0px !important; - } - } - - .custom-avatar .ant-avatar-string { - margin-left: auto; - margin-right: auto; - left: 0; - right: 0; - } -} - -//.ant-page-header-heading-left { -// align-items: baseline !important; -//} - -.ant-table { - overflow-y: auto; -} - -.grid-entry .project-name { - color: #262626; -} - -@import "scss/editable-table"; - -.img-fluid { - max-width: 100%; - height: auto; -} - -nz-tabset { - user-select: none; -} - -.ant-layout-sider { - background: #FFFFFF; -} - -.rounded-4 { - border-radius: 0.5rem !important; -} - -.actions-row { - &.compact { - td { - padding-top: 0 !important; - padding-bottom: 0 !important; - } - } - - .actions-col { - width: 70px; - } - - [nztype="delete"] { - opacity: 0.7; - } - - &:hover { - .actions { - opacity: 1; - } - } - - .actions { - opacity: 0; - } - - &.removing { - animation: row-removing 200ms ease-out; - } -} - -@keyframes row-removing { - 0% { - transform: rotateX(0); - } - - 100% { - transform: rotateX(90deg); - } -} - -.cursor-pointer { - cursor: pointer; -} - -.text-left { - text-align: left !important; -} - -.user-select-none { - pointer-events: none; - user-select: none; -} - -nz-collapse-panel { - .ant-collapse-header { - font-weight: 600; - } -} - -.thead-height-0 { - thead { - height: 0; - display: block; - } -} - -.my-dashboard { - nz-table-inner-scroll { - .ant-table-body { - min-height: 300px; - } - } -} - -.p-default-gaant { - padding: 12px !important; -} - -.ant-breadcrumb-link { - // font-weight: 500; -} - -.btn-primary-custom { - border-color: #40a9ff; - color: #40a9ff; -} - -.pin-button { - font-size: 18px; -} - -nz-page-header { - background-color: transparent !important; -} - -.task-due-label { - .ant-progress-text { - font-weight: bold; - font-size: 0.8em; - } -} - -.teams-dropdown-avatar { - .ant-avatar-string { - display: contents; - left: 0 !important; - right: 0 !important; - } -} - -.ant-upload-list { - overflow: hidden; -} - -.body-padding-0 { - .ant-card-body { - padding: 0; - } -} - -nz-segmented { - font-weight: 500; -} - -.ant-steps-item-process > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title::after { - width: 170px; -} - -.active-half.ant-steps-item-process > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title::after { - background: linear-gradient(90deg, #1890ff 50%, #0000000f 50%); - background-color: transparent; - transition: all 3s; -} - -nz-message-container { - nz-message { - .ant-message-notice-content { - border-radius: 21px; - font-weight: 500; - border: none; - } - } -} - -.custom-select { - font-size: 13px; - - .anticon-down { - color: rgba(0, 0, 0, 0.85); - } - - nz-select-top-control { - display: flex !important; - align-items: center !important; - height: 100% !important; - border: 0 !important; - background-color: transparent !important; - } -} - -.sub-tasks-skeleton { - ul, li { - margin: 0 !important; - } - - li { - width: 100% !important; - height: 28px !important; - border-radius: 0 !important; - border: 1px solid #ececec; - } -} - -.sub-task-actions { - .plus-icon { - display: none; - position: absolute; - right: 10px; - top: 0; - margin: auto; - bottom: 0; - align-items: center; - } -} - -nz-list-item-meta-title { - h4 { - margin-bottom: 0 !important; - } -} - -.task-list-label { - font-size: 11px; - font-weight: 500; -} - -nz-avatar { - span { - left: 0 !important; - right: 0 !important; - top: 0 !important; - bottom: 0 !important; - transform: none !important; - } -} - -.ant-table-column-sorter, -.ant-table-filter-trigger { - color: #8b8b8b; -} - -.members-dropdown { - max-height: 255px; - overflow: hidden; - overflow-y: auto; -} - -.task-list-row { - .custom-select { - nz-select-item { - font-weight: 500; - } - } -} - -//.notifications-drawer { -// box-shadow: none !important; -// -// .ant-drawer-header { -// display: none; -// } -// -// .ant-drawer-content { -// background: transparent; -// } -//} - -.avatar-dashed { - color: rgba(0, 0, 0, 0.85); - background: #fff; - border: 1px dashed #c4c4c4; - opacity: 0.8; - - &:hover { - cursor: pointer; - border: 1px dashed #adadad; - } -} - -.empty-label { - padding: 0 24px; - line-height: 19px; -} - -@import "scss/task-view"; - -.task-description-editor { - .description-editor { - display: none; - //padding: 10px; - - > div { - outline: none; - } - - &.editing { - display: block; - } - } - - //p { - // line-height: inherit; - // margin: 0; - // height: auto; - //} - - .ql-toolbar { - display: none !important; - } - - &.editing { - .ql-toolbar { - display: block !important; - } - } -} - -.task-view-comments { - .ant-comment-inner { - padding-bottom: 0; - } - - .ant-comment-actions { - margin-top: 0; - margin-bottom: 0; - } -} - -.custom-dropdown-range-li .custom-dropdown-range { - width: 270px; -} - -.common-secondary-header { - font-size: 14px !important; -} - -.common-data-header { - font-size: 24px !important; - font-weight: 600 !important; -} - -.archived-toggler { - padding: 7px 15px; - font-size: 14px; - border-radius: 4px; - background: whitesmoke; -} - -.archived-toggler label { - position: relative; -} - -.archived-toggler label::after { - position: absolute; - content: ""; - right: -5px; - top: -1px; - width: 8px; - height: 8px; - border-radius: 10px; - background: #f5222d; -} - -.archived-toggler.archived label::after { - background: #52c41a; -} - -.red { - color: #f5222d; -} - -.selector-highlight { - color: #40a9ff; - border-color: #40a9ff; - background-color: #e6f7ff !important; -} - -.custom-dropdown-range { - width: 290px; -} - -li.highlight { - background-color: #e6f7ff !important; - color: #1890ff; -} - -//.ant-collapse { -// border: 1px solid #f0f0f0; -//} - -//.ant-collapse-content { -// border-top: 1px solid #f0f0f0; -//} - -.ant-collapse > .ant-collapse-item { - border-bottom: none; -} - -.table-row:nth-child(odd) { - background-color: #f8f7f9; -} - -.child-row { - .table-header { - background-color: transparent !important; - } - - .table-row { - background-color: transparent; - } -} - -.member-project-table { - max-width: calc(100vw - 225px); - width: max-content; - transition: all 0.3s; -} - -.full { - .member-project-table { - max-width: calc(100vw - 70px); - - .table-row { - background-color: transparent; - } - } -} - -.bg-title { - .ant-card-head { - background: #fafafa; - } - - &:hover { - box-shadow: none !important; - border: 1px solid #f0f0f0 !important; - } -} - -.selected { - .rows .flex-row { - background: #dceeff !important; - background-color: #dceeff !important; - } -} - -.task-description { - p, h1, h2, h3, h4, h5, h6, ul, ol, li, pre, a { - margin-bottom: 0px !important; - padding-left: 0px !important; - font-size: 14px !important; - max-height: 45px; - list-style: none; - max-width: 210px; - overflow: hidden; - text-overflow: ellipsis; - } -} - -.cdk-drag-preview { - max-width: 1315px !important; - box-sizing: border-box; - overflow: hidden; - - .task-key { - margin-left: 5px; - } -} - -@media(max-width: 1400px) { - .cdk-drag-preview { - max-width: 1135px !important; - } -} - -@media(max-width: 1200px) { - .cdk-drag-preview { - max-width: 955px !important; - } -} - -.highlight-col { - border: 1px solid #188fff !important; -} - -.custom-shadow { - box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); -} - -.no-data-text { - color: rgb(0 0 0 / 65%); -} - -.no-data-img-holder img { - filter: grayscale(1); -} - -.kanban-avatars nz-avatar { - width: 24px !important; - height: 24px !important; - line-height: 24px !important; - font-size: 12px; - - & span { - line-height: 24px !important; - } -} - -.kanban-task-card-due-date { - position: absolute; - border: 1px dashed #d9d9d9; - - & input { - color: transparent; - background-color: transparent; - background: transparent; - position: absolute; - height: 24px; - font-size: 12px; - } -} - -.task-list-date-picker { - position: absolute; - - & input { - color: transparent; - background-color: transparent; - background: transparent; - position: absolute; - box-shadow: none; - } -} - -.kanban-task-create-dp { - max-width: 110px; - padding: 1px 2px; - border: 1px dashed #d9d9d9; - - & input { - font-size: 12px; - } -} - -.task:hover { - & .show-hover { - opacity: 1; - } -} - -.task-modal-dp { - background: transparent; - - & input { - color: transparent; - } -} - -.task-view-timer { - & .ant-btn { - width: 24px !important; - height: 24px !important; - } - - & .anticon-caret-right { - font-size: 16px; - margin-left: 2px; - } - - & .icon-stop { - font-size: 10px !important; - margin-top: 4px; - } -} - -.storage-text .ant-progress-circle .ant-progress-text { - font-size: 15px !important; -} - -//.worklenz-notification { -// &:hover { -// background: #f6f6f6; -// } -//} -.kanban-avatars nz-avatar { - width: 24px !important; - height: 24px !important; - line-height: 24px !important; - font-size: 12px; - - & span { - line-height: 24px !important; - } -} - -.kanban-task-card-due-date { - position: absolute; - border: 1px dashed #d9d9d9; - - & input { - color: transparent; - background-color: transparent; - background: transparent; - position: absolute; - height: 24px; - font-size: 12px; - } -} - -.task-list-date-picker { - position: absolute; - - & input { - color: transparent; - background-color: transparent; - background: transparent; - position: absolute; - box-shadow: none; - } -} - -.kanban-task-create-dp { - max-width: 110px; - padding: 1px 2px; - border: 1px dashed #d9d9d9; - - & input { - font-size: 12px; - } -} - -.task:hover { - & .show-hover { - opacity: 1; - } -} - -.task-modal-dp { - background: transparent; - - & input { - color: transparent; - } -} - -.task-view-timer { - & .ant-btn { - width: 24px !important; - height: 24px !important; - } - - & .anticon-caret-right { - font-size: 16px; - margin-left: 2px; - } - - & .icon-stop { - font-size: 10px !important; - margin-top: 4px; - } -} - -.scrolling-panel .task-name { - &::after { - position: absolute; - top: 0; - right: 0; - bottom: -1px; - width: 30px; - transform: translateX(100%); - transition: box-shadow 0.3s; - content: ''; - pointer-events: none; - box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, 0.15); - } -} - -.board-column.cdk-drag-preview { - background: #fafafa !important; -} - -.attachment-preview-modal .ant-modal-body { - padding: 12px 24px !important; -} - -.project-title-header .ant-page-header-heading-left { - width: 100% !important; -} - -.task-input-default .ant-select-selector { - border: 1px solid #d9d9d9 !important; - min-width: 120px; -} - -.homepage-table table tr:nth-of-type(even) { - background: #f8f7f9; -} - -.homepage-table table .ant-table-cell { - background: transparent !important; -} - -.home-calendar .ant-picker-calendar-date-content { - display: none; -} - -.home-calendar .ant-picker-calendar-date-value { - line-height: 44px !important; -} - -.home-calendar .ant-picker-calendar-date { - border-top: 1px solid #f0f0f0 !important; -} - -.home-calendar .ant-picker-calendar-date-today { - border-color: #1890ff !important; -} - -.name-td { - & button { - opacity: 0; - } - - &:hover button { - opacity: 1; - } -} - -.home-date-picker input { - margin-left: -4px; - width: 100px; -} - -.folder-icon { - color: #ff9c3c; -} - -p { - & .mentions { - background: #f7f7f7; - font-weight: 500; - border-radius: 4px; - } - - &.tooltip-comment .mentions { - background: transparent; - font-weight: 500; - } -} - -.project-status-selector-r { - & .anticon-down { - color: rgba(0, 0, 0, 0.65); - } - - &.ant-select-single.ant-select-show-arrow .ant-select-selection-item, .ant-select-single.ant-select-show-arrow .ant-select-selection-placeholder { - padding-right: 24px; - } - - &.ant-select-single.ant-select-open .ant-select-selection-item { - color: transparent; - } -} - -.reporting-submenu { - & .ant-menu-submenu-title { - padding-left: 14px !important; - } -} - -.ant-form-item-label.star-none > label.ant-form-item-required:not(.ant-form-item-required-mark-optional):before { - display: none !important; -} - -.ant-drawer-body:has(.template-drawer-o-none) { - overflow: hidden; -} - - -.small-badge-text { - & .ant-badge-status-text { - font-size: 12px; - color: rgba(0, 0, 0, 0.65); - } -} - -.ant-timeline-item-content { - min-height: 21px !important; -} - -.breakdown-card { - & .ant-card-body { - padding-bottom: 0px !important; - } -} - -.loading-custom-height .ant-skeleton-content { - display: block!important; -} diff --git a/worklenz-frontend/src/styles/colors.ts b/worklenz-frontend/src/styles/colors.ts new file mode 100644 index 00000000..4fefbfbc --- /dev/null +++ b/worklenz-frontend/src/styles/colors.ts @@ -0,0 +1,23 @@ +// colors.ts +export const colors = { + white: '#fff', + darkGray: '#1E1E1E', + lightGray: '#707070', + deepLightGray: '#d1d0d3', + lightBeige: '#fde8b5', + skyBlue: '#1890ff', + midBlue: '#b9cef1', + paleBlue: '#e6f7ff', + vibrantOrange: '#f56a00', + limeGreen: '#52c41a', + lightGreen: '#c2e4d0', + yellow: '#f8d914', + transparent: 'transparent', +}; + +export const applyCssVariables = () => { + const root = document.documentElement; + Object.entries(colors).forEach(([key, value]) => { + root.style.setProperty(`--color-${key}`, value); + }); +}; diff --git a/worklenz-frontend/src/styles/customOverrides.css b/worklenz-frontend/src/styles/customOverrides.css new file mode 100644 index 00000000..f12a3186 --- /dev/null +++ b/worklenz-frontend/src/styles/customOverrides.css @@ -0,0 +1,105 @@ +/* table overrides + used in homepage, settings +*/ + +/* this is for globaly hide tables scrollbar */ +:where(.css-dev-only-do-not-override-17sis5b).ant-table-wrapper .ant-table, +:where(.css-dev-only-do-not-override-bj289r).ant-table-wrapper .ant-table { + scrollbar-color: unset !important; +} + +.custom-two-colors-row-table table tr:nth-child(even) { + background: #4e4e4e10 !important; +} + +.custom-two-colors-row-table table tr { + border-radius: 4px !important; +} + +.custom-two-colors-row-table table tr td, +.custom-two-colors-row-table table th { + border-bottom: none !important; + padding: 6px 12px !important; +} + +.custom-two-colors-row-table table th, +.custom-project-view-task-list-table table th { + padding-bottom: 12px !important; + border: none !important; + font-weight: 400 !important; +} + +.custom-two-colors-row-table table th::before { + display: none; +} + +.ant-table-fixed-left .ant-table-cell td { + background-color: #4e4e4e10 !important; +} + +/* custom table used in */ + +/* custom dropdown + used in navbar, project view filters +*/ +.custom-dropdown .ant-dropdown-menu { + padding: 0 !important; + margin-top: 2px !important; +} + +.custom-dropdown .ant-dropdown-menu-item { + padding: 0 !important; +} + +/* custom list + used in project view filters +*/ +.custom-list-item:hover { + background-color: #f8f7f9 !important; + color: #181818; +} + +.custom-list-item.dark:hover { + background-color: #424242 !important; + color: rgba(255, 255, 255, 0.85); +} + +/* dropdown render card manual shadow + used in project view filters +*/ +.custom-card { + box-shadow: + 0 3px 6px -4px #0000001f, + 0 6px 16px #00000014, + 0 9px 28px 8px #0000000d !important; +} + +/* insights card hover */ +.custom-insights-card:hover { + border: 1px solid #d3d3d3; +} + +/* reporting sidebar */ +.custom-reporting-sider .ant-menu-item-selected { + border-inline-end: 3px solid #1890ff !important; + border-radius: 0; +} + +.custom-reporting-sider .ant-menu-item, +.custom-reporting-sider .ant-menu-submenu-title { + margin-inline: 0 !important; + padding-inline: 16px !important; + width: 100%; +} + +.custom-reporting-sider .ant-menu:nth-child(2) .ant-menu-item { + padding-inline-start: 32px !important; +} + +.ql-snow .ql-picker { + color: #6c757d !important; +} + +.ql-snow .ql-stroke { + stroke: #6c757d !important; +} diff --git a/worklenz-frontend/src/test.ts b/worklenz-frontend/src/test.ts deleted file mode 100644 index 782281b2..00000000 --- a/worklenz-frontend/src/test.ts +++ /dev/null @@ -1,11 +0,0 @@ -// This file is required by karma.conf.js and loads recursively all the .spec and framework files - -import "zone.js/testing"; -import {getTestBed} from "@angular/core/testing"; -import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from "@angular/platform-browser-dynamic/testing"; - -// First, initialize the Angular testing environment. -getTestBed().initTestEnvironment( - BrowserDynamicTestingModule, - platformBrowserDynamicTesting(), -); diff --git a/worklenz-frontend/src/theme.less b/worklenz-frontend/src/theme.less deleted file mode 100644 index 289cd733..00000000 --- a/worklenz-frontend/src/theme.less +++ /dev/null @@ -1,153 +0,0 @@ -// Custom Theming for NG-ZORRO -// For more information: https://ng.ant.design/docs/customize-theme/en - -@import "../node_modules/ng-zorro-antd/ng-zorro-antd.less"; - -@border-radius-base: 4px; -@rate-star-bg: #dedede; -@tooltip-bg: #4a4a4a; - -// Override less variables to here -// View all variables: https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/components/style/themes/default.less - -@font-family: -apple-system, BlinkMacSystemFont, 'Inter', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; - -.gantt__row-bars { - li { - border-radius: @border-radius-base; - } -} - -.reporting, -.custom-table { - .ant-table-tbody > tr > td { - transition: none !important; - } - - .ant-table-tbody > tr.ant-table-row:hover > td, - .ant-table-tbody > tr > td.ant-table-cell-row-hover { - color: @primary-color; - } -} - -.teams-switch { - .team-name { - background: @menu-item-active-bg; - padding: 11px 16px; - color: @menu-highlight-color; - - &:hover { - background: darken(@menu-item-active-bg, 5%); - } - } -} - -.menu-border-0 { - &:after { - border: none !important; - } -} - -.menu-hover { - &:hover { - background-color: #f0f2f5 !important; - } -} - -.ant-table { - &:not(.ant-table-bordered) { - table { - //th { - // background: transparent !important; - //} - - td, th { - border: none !important; - transition: none !important; - } - - nz-divider { - border-color: #b0b0b0 !important; - } - - tr { - &:hover { - - td:not(.nz-disable-td) { - &:first-child { - border-top-left-radius: @border-radius-base; - border-bottom-left-radius: @border-radius-base; - } - - &:last-child { - border-top-right-radius: @border-radius-base; - border-bottom-right-radius: @border-radius-base; - } - - background-color: rgb(237, 235, 240) !important; - } - } - } - - thead tr th { - &:first-child { - border-top-left-radius: @border-radius-base; - border-bottom-left-radius: @border-radius-base; - } - - &:last-child { - border-top-right-radius: @border-radius-base; - border-bottom-right-radius: @border-radius-base; - } - } - - tbody tr:nth-of-type(even) > *:not(.nz-disable-td) { - background: rgb(248, 247, 249); - - &:first-child { - border-top-left-radius: @border-radius-base; - border-bottom-left-radius: @border-radius-base; - } - - &:last-child { - border-top-right-radius: @border-radius-base; - border-bottom-right-radius: @border-radius-base; - } - } - } - } -} - -.ant-tabs-card { - .ant-tabs-tab { - transition: none !important; - - &:hover { - background-color: rgb(237, 235, 240) !important; - } - } -} - -.admin-role { - color: @warning-color; -} - -.owner-role { - color: @primary-color; -} - -.member-role { - color: @text-color-secondary; -} - -.ant-page-header-back-button { - padding: 6px 8px; - border-radius: @border-radius-base; - border: 1px solid transparent; - transition: all 200ms ease-out; - - &:hover { - background: #fafafa; - border-color: #f0f0f0; - } -} diff --git a/worklenz-frontend/src/types/admin-center/admin-center.types.ts b/worklenz-frontend/src/types/admin-center/admin-center.types.ts new file mode 100644 index 00000000..d2e29012 --- /dev/null +++ b/worklenz-frontend/src/types/admin-center/admin-center.types.ts @@ -0,0 +1,231 @@ +import { ISUBSCRIPTION_TYPE } from "@/shared/constants"; + +export interface IOrganization { + name?: string; + owner_name?: string; + email?: string; + contact_number?: string; + contact_number_secondary?: string; +} + +export interface IOrganizationAdmin { + name?: string; + email?: string; + is_owner?: boolean; +} + +export interface IOrganizationUser { + name?: string; + email?: string; + is_owner?: boolean; + is_admin?: boolean; + color_code?: string; + avatar_url?: string; + last_logged?: string; + user_id?: string; +} + +export interface IOrganizationTeamMember { + id?: string; + user_id?: string; + name?: string; + avatar_url?: string; + color_code?: string; + email?: string; + role_id?: string; + role_name?: string; + created_at?: string; +} + +export interface IOrganizationTeam { + id?: string; + name?: string; + created_at?: string; + members_count?: number; + names?: any; + team_members?: IOrganizationTeamMember[]; +} + +export enum ISubscriptionStatus { + DELETED = 'deleted', + PAUSED = 'paused', + ACTIVE = 'active', + PASTDUE = 'past_due', + TRIALING = 'trialing', + LIFE_TIME_DEAL = 'life_time_deal', +} + +export interface IBillingAccountInfo { + billing_type?: string; + cancel_url?: string; + cancellation_effective_date?: string; + contact_no?: string; + contact_number_secondary?: string; + default_currency?: string; + default_storage?: number; + default_trial_storage?: number; + email?: string; + expire_date_string?: string; + name?: string; + paused_from?: string; + plan_name?: string; + plan_id?: string; + remainingStorage?: number; + status?: ISubscriptionStatus; + trial_in_progress?: boolean; + trial_expire_date?: string; + valid_till_date?: string; + unit_price?: number; + unit_price_per_month?: number; + usedPercentage?: number; + usedStorage?: number; + is_custom?: boolean; + is_ltd_user?: boolean; + ltd_users?: number; + total_seats?: number; + total_used?: number; + is_lkr_billing?: boolean; + subscription_type?: ISUBSCRIPTION_TYPE; +} + +export interface IPricingPlans { + monthly_plan_id?: string | null; + monthly_plan_name?: string; + annual_plan_id?: string | null; + annual_plan_name?: string; + team_member_limit?: string; + projects_limit?: string; + free_tier_storage?: string; + current_user_count?: string; + annual_price?: string; + monthly_price?: string; +} + +export interface IPaddleCheckoutParams { + custom_message?: string; + customer_country?: string; + customer_email?: string; + customer_postcode?: string; + discountable?: number; + expires?: string; + image_url?: string; + is_recoverable?: number; + marketing_consent?: number; + passthrough?: string; + prices?: Array; + product_id?: number; + quantity_variable?: number; + quantity?: number; + recurring_prices?: string; + return_url?: string; + title?: string; + trial_days?: number; + vat_company_name?: string; + vat_number?: string; + webhook_url?: string; + coupon_code?: string; + vat_street?: string; +} + +export interface IUpgradeSubscriptionPlanResponse { + params: IPaddleCheckoutParams; + sandbox: boolean; + vendor_id: string; +} + +export interface IBillingConfiguration { + name?: string; + email?: string; + organization_name?: string; + contact_number?: string; + address_line_1?: string; + address_line_2?: string; + city?: string; + state?: string; + postal_code?: string; + country?: string; +} + +export interface IBillingTransaction { + subscription_payment_id?: string; + event_time?: string; + next_bill_date?: string; + currency?: string; + receipt_url?: string; + payment_method?: string; + payment_status?: string; +} + +export interface IStorageInfo { + storage?: string; + default_trial_storage?: string; + plan_name?: string; + trial_expire_date?: string; + trial_in_progress?: boolean; + storage_addon_price?: string; + storage_addon_size?: string; +} + +export interface IBillingCharge { + amount?: number; + end_date?: string; + name?: string; + quantity?: number; + start_date?: string; + status?: string; + unit_price?: number; + currency?: string; +} + +export interface IBillingAccountStorage { + used?: number; + total?: number; + used_percent?: number; + remaining?: number; +} + +export interface IBillingConfigurationCountry { + id?: string; + name: string; + code?: string; +} + +export interface IBillingModifier { + subscription_id?: string; + created_at?: string; +} + +export interface IBillingChargesResponse { + plan_charges?: IBillingCharge[]; + modifiers?: IBillingModifier[]; +} + +export interface IOrganizationUsersGetRequest { + total?: number; + data?: IOrganizationUser[]; +} + +export interface IOrganizationTeamGetRequest { + total?: number; + data?: IOrganizationTeam[]; + current_team_data?: IOrganizationTeam; +} + +export interface IOrganizationProject { + id?: string; + name?: string; + created_at?: string; + member_count?: number; + team_name?: string; +} + +export interface IFreePlanSettings { + projects_limit?: number; + team_member_limit?: number; + free_tier_storage?: number; +} + +export interface IOrganizationProjectsGetResponse { + total?: number; + data?: IOrganizationProject[]; +} diff --git a/worklenz-frontend/src/types/admin-center/country.types.ts b/worklenz-frontend/src/types/admin-center/country.types.ts new file mode 100644 index 00000000..2ab2aa35 --- /dev/null +++ b/worklenz-frontend/src/types/admin-center/country.types.ts @@ -0,0 +1,5 @@ +export interface IBillingConfigurationCountry { + id?: string; + name: string; + code?: string; +} diff --git a/worklenz-frontend/src/types/admin-center/team.types.ts b/worklenz-frontend/src/types/admin-center/team.types.ts new file mode 100644 index 00000000..c364ed4a --- /dev/null +++ b/worklenz-frontend/src/types/admin-center/team.types.ts @@ -0,0 +1,9 @@ +export type TeamsType = { + teamId: string; + teamName: string; + owner: string; + membersCount: Number; + members: string[]; + created: Date; + isActive: boolean; +}; diff --git a/worklenz-frontend/src/types/alert.types.ts b/worklenz-frontend/src/types/alert.types.ts new file mode 100644 index 00000000..86c59d91 --- /dev/null +++ b/worklenz-frontend/src/types/alert.types.ts @@ -0,0 +1,21 @@ +export type AlertType = 'success' | 'error' | 'info' | 'warning'; + +export interface Alert { + id: string; + type: AlertType; + title: string; + message: string; + duration?: number; + timestamp: number; +} + +export interface AlertConfig { + position: 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'; + duration: number; + maxCount: number; +} + +export interface AlertState { + activeAlerts: Set; + config: AlertConfig; +} diff --git a/worklenz-frontend/src/types/apiModels/taskPrioritiesGetResponse.types.ts b/worklenz-frontend/src/types/apiModels/taskPrioritiesGetResponse.types.ts new file mode 100644 index 00000000..ead53593 --- /dev/null +++ b/worklenz-frontend/src/types/apiModels/taskPrioritiesGetResponse.types.ts @@ -0,0 +1,5 @@ +import { ITaskPriority } from '../tasks/taskPriority.types'; + +export interface ITaskPrioritiesGetResponse extends ITaskPriority { + color_code?: string; +} diff --git a/worklenz-frontend/src/types/auth/local-session.types.ts b/worklenz-frontend/src/types/auth/local-session.types.ts new file mode 100644 index 00000000..8bd61a15 --- /dev/null +++ b/worklenz-frontend/src/types/auth/local-session.types.ts @@ -0,0 +1,31 @@ +import { IUserType } from '../user.types'; + +export interface IWorklenzAlert { + description: string; + type: 'success' | 'info' | 'warning' | 'error'; +} + +export interface ILocalSession extends IUserType { + team_id?: string; + team_name?: string; + owner?: boolean; + demo_data?: boolean; + is_admin?: boolean; + is_member?: boolean; + build_v?: string; + is_google?: boolean; + setup_completed?: boolean; + my_setup_completed?: boolean; + timezone?: string; + timezone_name?: string; + avatar_url?: string; + joined_date?: string; + last_updated?: string; + user_no?: number; + team_member_id?: string; + alerts?: Array; + is_expired?: boolean; + subscription_status?: string; + subscription_type?: string; + trial_expire_date?: string; +} diff --git a/worklenz-frontend/src/types/auth/login.types.ts b/worklenz-frontend/src/types/auth/login.types.ts new file mode 100644 index 00000000..f8d8c8b0 --- /dev/null +++ b/worklenz-frontend/src/types/auth/login.types.ts @@ -0,0 +1,33 @@ +import { ILocalSession } from './local-session.types'; + +export interface IUser { + id?: string; + name?: string; + password?: string; + email?: string; +} + +export interface IUserLoginRequest { + email: string; + password: string; + team_id?: string; + project_id?: string; +} + +export interface IUserLoginResponse extends IUser {} + +export interface IAuthorizeResponse { + authenticated: boolean; + user: ILocalSession; + auth_error: string; + message: string; +} + +export interface IAuthState { + user: IUser | null; + isAuthenticated: boolean; + isLoading: boolean; + error: string | null; + teamId?: string; + projectId?: string; +} diff --git a/worklenz-frontend/src/app/interfaces/api-models/user-sign-up-request.ts b/worklenz-frontend/src/types/auth/signup.types.ts similarity index 100% rename from worklenz-frontend/src/app/interfaces/api-models/user-sign-up-request.ts rename to worklenz-frontend/src/types/auth/signup.types.ts diff --git a/worklenz-frontend/src/types/auth/verify-reset-email.types.ts b/worklenz-frontend/src/types/auth/verify-reset-email.types.ts new file mode 100644 index 00000000..a5a93452 --- /dev/null +++ b/worklenz-frontend/src/types/auth/verify-reset-email.types.ts @@ -0,0 +1,6 @@ +export interface IUpdatePasswordRequest { + password?: string; + user?: string; + hash?: string; + confirmPassword?: string; +} diff --git a/worklenz-frontend/src/app/interfaces/avatar-attachment.ts b/worklenz-frontend/src/types/avatarAttachment.types.ts similarity index 100% rename from worklenz-frontend/src/app/interfaces/avatar-attachment.ts rename to worklenz-frontend/src/types/avatarAttachment.types.ts diff --git a/worklenz-frontend/src/types/categories.types.ts b/worklenz-frontend/src/types/categories.types.ts new file mode 100644 index 00000000..1a685bfe --- /dev/null +++ b/worklenz-frontend/src/types/categories.types.ts @@ -0,0 +1,5 @@ +export type CategoryType = { + categoryId: string; + categoryName: string; + categoryColor: string; +}; diff --git a/worklenz-frontend/src/types/client.types.ts b/worklenz-frontend/src/types/client.types.ts new file mode 100644 index 00000000..99934be0 --- /dev/null +++ b/worklenz-frontend/src/types/client.types.ts @@ -0,0 +1,16 @@ +export interface IClient { + id?: string; + name?: string; + team_id?: string; + created_at?: string; + updated_at?: string; +} + +export interface IClientViewModel extends IClient { + projects_count?: number; +} + +export interface IClientsViewModel { + total?: number; + data?: IClientViewModel[]; +} diff --git a/worklenz-frontend/src/app/interfaces/api-models/server-response.ts b/worklenz-frontend/src/types/common.types.ts similarity index 100% rename from worklenz-frontend/src/app/interfaces/api-models/server-response.ts rename to worklenz-frontend/src/types/common.types.ts diff --git a/worklenz-frontend/src/types/dependencies.types.ts b/worklenz-frontend/src/types/dependencies.types.ts new file mode 100644 index 00000000..1989dcf8 --- /dev/null +++ b/worklenz-frontend/src/types/dependencies.types.ts @@ -0,0 +1,6 @@ +export type DependencyType = { + dependencyId: string; + taskId: string; + task: string; + blockedBy: string; +}; diff --git a/worklenz-frontend/src/types/home/home-page.types.ts b/worklenz-frontend/src/types/home/home-page.types.ts new file mode 100644 index 00000000..469fb152 --- /dev/null +++ b/worklenz-frontend/src/types/home/home-page.types.ts @@ -0,0 +1,26 @@ +import { IMyTask } from './my-tasks.types'; +import type { Dayjs } from 'dayjs'; + +export interface IHomeTasksConfig { + current_tab: string | null; // active tab in list view + selected_date: Dayjs | null; // selected date in calendar view + tasks_group_by: number; // tasks assigned to me / assigned by me + current_view: number; // list view or calendar view + is_calendar_view: boolean; + time_zone: string; +} + +export interface IHomeTasksModel { + tasks: IMyTask[]; + total: number; + today: number; + upcoming: number; + overdue: number; + no_due_date: number; +} + +export interface IPersonalTask { + name: string; + color_code: '#000'; + end_date?: string; +} diff --git a/worklenz-frontend/src/types/home/my-tasks.types.ts b/worklenz-frontend/src/types/home/my-tasks.types.ts new file mode 100644 index 00000000..26cf2538 --- /dev/null +++ b/worklenz-frontend/src/types/home/my-tasks.types.ts @@ -0,0 +1,13 @@ +import { IProjectTask } from '../project/projectTasksViewModel.types'; +import { ITaskStatusViewModel } from '../tasks/taskStatusGetResponse.types'; + +export interface IMyTask extends IProjectTask { + is_task: boolean; + done: boolean; + project_color?: string; + project_name?: string; + team_id?: string; + status_color?: string; + project_statuses?: ITaskStatusViewModel[]; + parent_task_name?: string; +} diff --git a/worklenz-frontend/src/types/home/tasks.types.ts b/worklenz-frontend/src/types/home/tasks.types.ts new file mode 100644 index 00000000..113bc0cd --- /dev/null +++ b/worklenz-frontend/src/types/home/tasks.types.ts @@ -0,0 +1,33 @@ +import { InlineMember } from '../teamMembers/inlineMember.types'; + +export interface IMyDashboardProjectTask { + id: string; + name?: string; + project_name?: string; + project_color?: string; + status_color?: string; + project_id?: string; + status?: string; + start_date?: string; + end_date?: string; + done?: boolean; + names?: InlineMember[]; +} + +export interface IMyDashboardAllTasksViewModel { + total?: number; + data?: IMyDashboardProjectTask[]; +} + +export interface IMyDashboardMyTask { + id: string; + name?: string; + project_name?: string; + project_color?: string; + status_color?: string; + project_id?: string; + status?: string; + start_date?: string; + end_date?: string; + names?: InlineMember[]; +} diff --git a/worklenz-frontend/src/types/job.types.ts b/worklenz-frontend/src/types/job.types.ts new file mode 100644 index 00000000..292c0812 --- /dev/null +++ b/worklenz-frontend/src/types/job.types.ts @@ -0,0 +1,10 @@ +export interface IJobTitle { + id?: string; + name?: string; + team_id?: string; +} + +export interface IJobTitlesViewModel { + total?: number; + data?: IJobTitle[]; +} diff --git a/worklenz-frontend/src/app/interfaces/task-label.ts b/worklenz-frontend/src/types/label.type.ts similarity index 100% rename from worklenz-frontend/src/app/interfaces/task-label.ts rename to worklenz-frontend/src/types/label.type.ts diff --git a/worklenz-frontend/src/types/member.types.ts b/worklenz-frontend/src/types/member.types.ts new file mode 100644 index 00000000..4556b328 --- /dev/null +++ b/worklenz-frontend/src/types/member.types.ts @@ -0,0 +1,10 @@ +export type MemberType = { + memberId: string; + memberName: string; + memberEmail: string; + projects?: number; + jobTitle?: string; + memberRole: 'owner' | 'member' | 'admin'; + isActivate: boolean | null; + isInivitationAccept: boolean; +}; diff --git a/worklenz-frontend/src/types/mixpanel.types.ts b/worklenz-frontend/src/types/mixpanel.types.ts new file mode 100644 index 00000000..226b6c7c --- /dev/null +++ b/worklenz-frontend/src/types/mixpanel.types.ts @@ -0,0 +1,15 @@ +export interface MixpanelConfig { + debug?: boolean; + track_pageview?: boolean; + persistence?: 'localStorage' | 'cookie'; +} + +export interface UserProperties { + name?: string; + email?: string; + [key: string]: any; +} + +export interface EventProperties { + [key: string]: any; +} diff --git a/worklenz-frontend/src/types/notification.types.ts b/worklenz-frontend/src/types/notification.types.ts new file mode 100644 index 00000000..5e4fb759 --- /dev/null +++ b/worklenz-frontend/src/types/notification.types.ts @@ -0,0 +1 @@ +export type NotificationType = {}; diff --git a/worklenz-frontend/src/types/notifications/notifications.types.ts b/worklenz-frontend/src/types/notifications/notifications.types.ts new file mode 100644 index 00000000..e218bfa1 --- /dev/null +++ b/worklenz-frontend/src/types/notifications/notifications.types.ts @@ -0,0 +1,25 @@ +import { Params } from 'react-router-dom'; +import { ITeamInvites } from '../teams/team.type'; + +export interface IWorklenzNotification { + id: string; + team: string; + team_id: string; + message: string; + project?: string; + color?: string; + url?: string; + task_id?: string; + params?: Params; + created_at?: string; +} + +export interface ITeamInvitationViewModel extends ITeamInvites { + accepting?: boolean; + joining?: boolean; +} + +export declare type NotificationsDataModel = Array<{ + type: 'invitation' | 'notification'; + data: ITeamInvitationViewModel | IWorklenzNotification; +}>; diff --git a/worklenz-frontend/src/types/phase.types.ts b/worklenz-frontend/src/types/phase.types.ts new file mode 100644 index 00000000..7a00681e --- /dev/null +++ b/worklenz-frontend/src/types/phase.types.ts @@ -0,0 +1,12 @@ +export type PhaseOption = { + optionId: string; + optionName: string; + optionColor: string; +}; + +export type PhaseType = { + phaseId: string; + projectId: string; + phase: string; + phaseOptions: PhaseOption[]; +}; diff --git a/worklenz-frontend/src/types/project-list/project-list.types.ts b/worklenz-frontend/src/types/project-list/project-list.types.ts new file mode 100644 index 00000000..7b3580eb --- /dev/null +++ b/worklenz-frontend/src/types/project-list/project-list.types.ts @@ -0,0 +1,48 @@ +import type { NavigateFunction } from 'react-router-dom'; +import type { ColumnsType, TablePaginationConfig } from 'antd/es/table'; +import type { AppDispatch } from '@/app/store'; +import type { IProjectViewModel } from '@/types/project/projectViewModel.types'; +import type { IProjectStatus } from '@/types/project/projectStatus.types'; +import type { IProjectCategory } from '@/types/project/projectCategory.types'; +import type { InlineMember } from '@/types/teamMembers/inlineMember.types'; +import { FilterValue } from 'antd/es/table/interface'; +import { SorterResult } from 'antd/es/table/interface'; + +export interface ProjectNameCellProps { + record: IProjectViewModel; + navigate: NavigateFunction; +} + +export interface CategoryCellProps { + record: IProjectViewModel; +} + +export interface ActionButtonsProps { + t: (key: string) => string; + record: IProjectViewModel; + setProjectId: (id: string) => void; + dispatch: AppDispatch; + isOwnerOrAdmin: boolean; +} + +export interface TableColumnsProps { + navigate: NavigateFunction; + statuses: IProjectStatus[]; + categories: IProjectCategory[]; + setProjectId: (id: string) => void; +} + +export interface ProjectListTableProps { + loading: boolean; + projects: IProjectViewModel[]; + statuses: IProjectStatus[]; + categories: IProjectCategory[]; + pagination: TablePaginationConfig; + onTableChange: ( + pagination: TablePaginationConfig, + filters: Record, + sorter: SorterResult | SorterResult[] + ) => void; + onProjectSelect: (id: string) => void; + onArchive: (id: string) => void; +} diff --git a/worklenz-frontend/src/types/project-templates/project-templates.types.ts b/worklenz-frontend/src/types/project-templates/project-templates.types.ts new file mode 100644 index 00000000..ccdb9acb --- /dev/null +++ b/worklenz-frontend/src/types/project-templates/project-templates.types.ts @@ -0,0 +1,60 @@ +export interface IWorklenzTemplate { + id?: string; + name?: string; +} + +interface IPhase { + name?: string; + color_code?: string; +} + +interface IStatus { + name?: string; + color_code?: string; +} + +interface IPriority { + name?: string; + color_code?: string; +} + +interface ILabel { + name?: string; + color_code?: string; +} + +interface ITemplateTask { + name?: string; +} + +export interface IProjectTemplate { + id?: string; + name?: string; + image_url?: string; + description?: string; + phases?: IPhase[]; + status?: IStatus[]; + priorities?: IPriority[]; + labels?: ILabel[]; + tasks?: ITemplateTask[]; +} + +export interface ICustomTemplate { + id?: string; + name?: string; + color_code?: string; + selected?: boolean; +} + +export interface IAccountSetupRequest { + team_name?: string; + project_name?: string | null; + tasks: string[]; + team_members: string[]; + template_id?: string | null; +} + +export interface IAccountSetupResponse { + id?: string; + has_invitations?: boolean; +} diff --git a/worklenz-frontend/src/types/project.types.ts b/worklenz-frontend/src/types/project.types.ts new file mode 100644 index 00000000..6180e270 --- /dev/null +++ b/worklenz-frontend/src/types/project.types.ts @@ -0,0 +1,51 @@ +import { CategoryType } from './categories.types'; +import { IClient } from './client.types'; +import { MemberType } from './member.types'; + +export type ProjectStatus = + | 'cancelled' + | 'blocked' + | 'onHold' + | 'proposed' + | 'inPlanning' + | 'inProgress' + | 'completed' + | 'continuous'; + +export type ProjectHealthStatus = 'notSet' | 'needsAttention' | 'atRisk' | 'good'; + +export type ProjectType = { + projectId: string; + projectName: string; + projectColor: string; + projectStatus: ProjectStatus; + projectHealthStatus: ProjectHealthStatus; + projectCategory?: CategoryType | null; + projectNotes?: string | null; + projectClient?: IClient[] | null; + projectManager?: MemberType; + projectStartDate?: Date | null; + projectEndDate?: Date | null; + projectEstimatedWorkingDays?: number; + projectEstimatedManDays?: number; + projectHoursPerDays?: number; + projectCreated: Date; + isFavourite: boolean; + projectTeam: string; + projectMemberCount: number; +}; + +export interface IProject { + id?: string; + name?: string; + color_code?: string; + notes?: string; + team_id?: string; + client_id?: string; + owner_id?: string; + created_at?: string; + updated_at?: string; + status_id?: string; + man_days?: number; + hours_per_day?: number; +} diff --git a/worklenz-frontend/src/types/project/project-insights.types.ts b/worklenz-frontend/src/types/project/project-insights.types.ts new file mode 100644 index 00000000..fadfcb5f --- /dev/null +++ b/worklenz-frontend/src/types/project/project-insights.types.ts @@ -0,0 +1,81 @@ +import { ITeamMember } from '@/features/taskAttributes/taskMemberSlice'; + +export interface IProjectInsightsGetRequest { + total_tasks_count?: number; + archived_tasks_count?: number; + sub_tasks_count?: number; + completed_tasks_count?: number; + pending_tasks_count?: number; + todo_tasks_count?: number; + last_week_count?: number; + overdue_count?: number; + todo_tasks_color_code?: string; + pending_tasks_color_code?: string; + completed_tasks_color_code?: string; + total_estimated_hours_string?: string; + total_logged_hours_string?: string; + total_estimated_hours?: number; + total_logged_hours?: number; + overlogged_hours?: string; +} + +export interface IProjectLogs { + description?: string; + created_at?: string; +} + +export interface IProjectMemberStats { + total_members_count?: number; + unassigned_members?: number; + overdue_members?: number; +} + +export interface IInsightTasks { + id?: string; + name?: string; + start_date?: string; + end_date?: string; + completed_at?: string; + status?: string; + status_id?: string; + status_color?: string; + updated_at?: string; + total_minutes?: string; + overlogged_time?: string; + days_overdue?: number; + is_overdue?: boolean; + parent_task_id?: string; +} + +export interface IDeadlineTaskStats { + deadline_tasks_count?: number; + deadline_logged_hours?: number; + deadline_logged_hours_string?: string; + project_end_date?: string; + tasks?: IInsightTasks[]; +} + +export interface ITaskStatusCounts { + name?: string; + color?: string; + y?: number; +} + +export interface ITaskPriorityCounts { + name?: string; + color?: string; + data?: number[]; +} + +export interface ITeamMemberOverviewGetResponse extends ITeamMember { + task_count?: number; + done_task_count?: number; + pending_task_count?: number; + overdue_task_count?: number; + progress?: number; + contribution?: number; + job_title?: string; + id: string; + name?: string; + tasks?: IInsightTasks[]; +} diff --git a/worklenz-frontend/src/types/project/project.types.ts b/worklenz-frontend/src/types/project/project.types.ts new file mode 100644 index 00000000..e89baf9a --- /dev/null +++ b/worklenz-frontend/src/types/project/project.types.ts @@ -0,0 +1,47 @@ +import { IProjectCategory } from '@/types/project/projectCategory.types'; +import { IProjectStatus } from '@/types/project/projectStatus.types'; + +export interface IProject { + id?: string; + name?: string; + color_code?: string; + notes?: string; + team_id?: string; + client_id?: string; + client_name?: string | null; + owner_id?: string; + created_at?: string; + updated_at?: string; + status_id?: string; + man_days?: number; + hours_per_day?: number; +} + +export interface IProjectUpdate { + name?: string; + category?: IProjectCategory; + status?: IProjectStatus; + notes?: string; +} + +export interface IProjectUpdateComment { + id?: string; + content?: string; + user_id?: string; + project_id?: string; + created_at?: string; + updated_at?: string; +} + +export interface IProjectUpdateCommentViewModel extends IProjectUpdateComment { + created_by?: string; + avatar_url?: string; + color_code?: string; + mentions: [user_name?: string, user_email?: string]; +} + +export enum IProjectFilter { + All = 'All', + Favourites = 'Favorites', + Archived = 'Archived', +} diff --git a/worklenz-frontend/src/app/interfaces/project-access-level.ts b/worklenz-frontend/src/types/project/projectAccessLevel.types.ts similarity index 100% rename from worklenz-frontend/src/app/interfaces/project-access-level.ts rename to worklenz-frontend/src/types/project/projectAccessLevel.types.ts diff --git a/worklenz-frontend/src/app/interfaces/project-category.ts b/worklenz-frontend/src/types/project/projectCategory.types.ts similarity index 100% rename from worklenz-frontend/src/app/interfaces/project-category.ts rename to worklenz-frontend/src/types/project/projectCategory.types.ts diff --git a/worklenz-frontend/src/types/project/projectComments.types.ts b/worklenz-frontend/src/types/project/projectComments.types.ts new file mode 100644 index 00000000..0e8e81e8 --- /dev/null +++ b/worklenz-frontend/src/types/project/projectComments.types.ts @@ -0,0 +1,26 @@ +export interface IMentionMember { + id?: string /*user id*/; + name?: string; + team_member_id?: string; +} + +export interface IMentionMemberViewModel extends IMentionMember { + email?: string; + avatar_url?: string; + color_code?: string; +} + +export interface IMentionMemberSelectOption { + key: string; + value: string; + label: string; +} + +export interface IProjectCommentsCreateRequest { + project_id?: string; + project_name?: string; + team_id?: string; + team_name?: string; + content?: string; + mentions?: IMentionMember[]; +} diff --git a/worklenz-frontend/src/app/interfaces/projects-filter-config.ts b/worklenz-frontend/src/types/project/projectFilterConfig.types.ts similarity index 100% rename from worklenz-frontend/src/app/interfaces/projects-filter-config.ts rename to worklenz-frontend/src/types/project/projectFilterConfig.types.ts diff --git a/worklenz-frontend/src/types/project/projectHealth.types.ts b/worklenz-frontend/src/types/project/projectHealth.types.ts new file mode 100644 index 00000000..c1c9d765 --- /dev/null +++ b/worklenz-frontend/src/types/project/projectHealth.types.ts @@ -0,0 +1,8 @@ +export interface IProjectHealth { + id: string; + name: string; + color_code?: string; + sort_order?: number; + is_default?: boolean; + selected?: boolean; +} diff --git a/worklenz-frontend/src/types/project/projectInsights.types.ts b/worklenz-frontend/src/types/project/projectInsights.types.ts new file mode 100644 index 00000000..739fcd34 --- /dev/null +++ b/worklenz-frontend/src/types/project/projectInsights.types.ts @@ -0,0 +1,55 @@ +export interface IProjectInsightsGetRequest { + total_tasks_count?: number; + archived_tasks_count?: number; + sub_tasks_count?: number; + completed_tasks_count?: number; + pending_tasks_count?: number; + todo_tasks_count?: number; + last_week_count?: number; + overdue_count?: number; + todo_tasks_color_code?: string; + pending_tasks_color_code?: string; + completed_tasks_color_code?: string; + total_estimated_hours_string?: string; + total_logged_hours_string?: string; + total_estimated_hours?: number; + total_logged_hours?: number; + overlogged_hours?: string; +} + +export interface IProjectLogs { + description?: string; + created_at?: string; +} + +export interface IProjectMemberStats { + total_members_count?: number; + unassigned_members?: number; + overdue_members?: number; +} + +export interface IInsightTasks { + id?: string; + name?: string; + start_date?: string; + end_date?: string; + completed_at?: string; + status?: string; + status_id?: string; + status_color?: string; + status_name?: string; + updated_at?: string; + total_minutes?: string; + overlogged_time?: string; + days_overdue?: number; + is_overdue?: boolean; + parent_task_id?: string; +} + +export interface IDeadlineTaskStats { + deadline_tasks_count?: number; + deadline_logged_hours?: number; + deadline_logged_hours_string?: string; + project_end_date?: string; + tasks?: IInsightTasks[]; +} diff --git a/worklenz-frontend/src/types/project/projectManager.types.ts b/worklenz-frontend/src/types/project/projectManager.types.ts new file mode 100644 index 00000000..4a0d2411 --- /dev/null +++ b/worklenz-frontend/src/types/project/projectManager.types.ts @@ -0,0 +1,6 @@ +export interface IProjectManager { + id: string; + name: string; + team_member_id: string; + selected?: boolean; +} diff --git a/worklenz-frontend/src/app/interfaces/project-member.ts b/worklenz-frontend/src/types/project/projectMember.types.ts similarity index 100% rename from worklenz-frontend/src/app/interfaces/project-member.ts rename to worklenz-frontend/src/types/project/projectMember.types.ts diff --git a/worklenz-frontend/src/app/interfaces/project-status.ts b/worklenz-frontend/src/types/project/projectStatus.types.ts similarity index 100% rename from worklenz-frontend/src/app/interfaces/project-status.ts rename to worklenz-frontend/src/types/project/projectStatus.types.ts diff --git a/worklenz-frontend/src/types/project/projectTasksViewModel.types.ts b/worklenz-frontend/src/types/project/projectTasksViewModel.types.ts new file mode 100644 index 00000000..94e93c4c --- /dev/null +++ b/worklenz-frontend/src/types/project/projectTasksViewModel.types.ts @@ -0,0 +1,97 @@ +import { InlineMember } from '../teamMembers/inlineMember.types'; +import { ITaskLabel } from '../tasks/taskLabel.types'; +import { ITaskStatusViewModel } from '../tasks/taskStatusGetResponse.types'; + +export interface ITaskAssignee { + team_member_id: any; + id: string; + project_member_id: string; + name: string; +} + +export interface ITaskStatusCategory { + is_todo?: boolean; + is_doing?: boolean; + is_done?: boolean; +} + +export interface IProjectTask { + due_time?: string; + id?: string; + name?: string; + task_key?: string; + assignees?: ITaskAssignee[]; + names?: InlineMember[]; + reporter?: string; + status?: string; + status_id?: string; + status_color?: string; + status_color_dark?: string; + priority?: string; + start_date?: string; + end_date?: string; + total_hours?: number; + total_minutes?: number; + total_minutes_spent?: number; + progress?: number; + overdue?: boolean; + time_spent_string?: string; + comments_count?: number; + has_subscribers?: boolean; + attachments_count?: number; + has_dependencies?: boolean; + schedule_id?: string; + status_name?: string; + total_time_string?: string; + due_in?: string; + time_spent?: { hours?: number; minutes?: number }; + project_id?: string; + project_name?: string; + updated_at?: string; + name_color?: string; + sub_tasks_count?: number; + total_tasks_count?: number; + is_sub_task?: boolean; + parent_task_name?: string; + parent_task_id?: string; + show_handles?: boolean; + min?: number; + max?: number; + sort_order?: number; + color_code?: string; + priority_color?: string; + priority_color_dark?: string; + show_sub_tasks?: boolean; + sub_tasks?: IProjectTask[]; + sub_tasks_loading?: boolean; + statuses?: ITaskStatusViewModel[]; + project_statuses?: ITaskStatusViewModel[]; + labels?: ITaskLabel[]; + all_labels?: ITaskLabel[]; + archived?: boolean; + complete_ratio?: number; + completed_count?: number; + priority_value?: number; + is_overdue?: boolean; + timer_start_time?: number | null; + description?: string; + completed_at?: string; + created_at?: string; + phase_id?: string; + phase_name?: string; + phase_color?: string; + priority_name?: string; + status_category?: ITaskStatusCategory; + overdue_days?: string | null; + overlogged_time_string?: string; + offset_from?: number; + width?: number; + isVisible?: boolean; + estimated_string?: string; + custom_column_values?: Record; +} + +export interface IProjectTasksViewModel { + total?: number; + data?: IProjectTask[]; +} diff --git a/worklenz-frontend/src/types/project/projectTemplate.types.ts b/worklenz-frontend/src/types/project/projectTemplate.types.ts new file mode 100644 index 00000000..dc15b458 --- /dev/null +++ b/worklenz-frontend/src/types/project/projectTemplate.types.ts @@ -0,0 +1,17 @@ +export interface ICustomProjectTemplateCreateRequest { + project_id: string; + templateName: string; + projectIncludes: { + statuses: boolean; + phases: boolean; + labels: boolean; + }; + taskIncludes: { + status: boolean; + phase: boolean; + labels: boolean; + estimation: boolean; + description: boolean; + subtasks: boolean; + }; +} diff --git a/worklenz-frontend/src/types/project/projectViewModel.types.ts b/worklenz-frontend/src/types/project/projectViewModel.types.ts new file mode 100644 index 00000000..9da91c69 --- /dev/null +++ b/worklenz-frontend/src/types/project/projectViewModel.types.ts @@ -0,0 +1,44 @@ +import { IProject } from '@/types/project/project.types'; +import { ITeamMemberViewModel } from '../teamMembers/teamMembersGetResponse.types'; +import { ITask } from '../tasks/task.types'; +import { InlineMember } from '../teamMembers/inlineMember.types'; + +export interface IProjectViewModel extends IProject { + key?: string; + client_name?: string | null; + project_owner?: string; + updated_at?: string; + updated_at_string?: string; + status?: string; + status_color?: string; + status_icon?: string; + start_date?: string; + end_date?: string; + phase_label?: string; + category_name?: string; + category_color?: string; + category_id?: string; + health_id?: string; + task_count?: number; + members_count?: number; + progress?: number; + all_tasks_count?: number; + completed_tasks_count?: number; + + favorite?: boolean; + archived?: boolean; + loading?: boolean; + collapsed?: boolean; + subscribed?: boolean; + + members?: ITeamMemberViewModel[]; + owner?: ITeamMemberViewModel | null; + tasks?: ITask[]; + names?: Array; + + project_manager?: ITeamMemberViewModel | null; + project_manager_id?: string | null; + + team_member_default_view?: string; + working_days?: number; +} diff --git a/worklenz-frontend/src/types/project/projectsViewModel.types.ts b/worklenz-frontend/src/types/project/projectsViewModel.types.ts new file mode 100644 index 00000000..a8e7ae35 --- /dev/null +++ b/worklenz-frontend/src/types/project/projectsViewModel.types.ts @@ -0,0 +1,11 @@ +import { IProjectViewModel } from './projectViewModel.types'; + +export interface IProjectsViewModel { + total?: number; + data?: IProjectViewModel[]; +} + +export interface IProjectOverviewStats { + done_task_count?: string; + pending_task_count?: string; +} diff --git a/worklenz-frontend/src/types/projectMember.types.ts b/worklenz-frontend/src/types/projectMember.types.ts new file mode 100644 index 00000000..10f1c0c2 --- /dev/null +++ b/worklenz-frontend/src/types/projectMember.types.ts @@ -0,0 +1,20 @@ +import { IProjectMember } from "./project/projectMember.types"; + +export interface IProjectMemberViewModel extends IProjectMember { + id?: string; + name?: string; + email?: string; + access?: string; + pending_invitation?: boolean; + all_tasks_count?: number; + completed_tasks_count?: number; + progress?: number; + job_title?: string; + avatar_url?: string; + team_member_id?: string; +} + +export interface IProjectMembersViewModel { + total?: number; + data?: IProjectMemberViewModel[]; +} diff --git a/worklenz-frontend/src/types/reporting/reporting-allocation.types.ts b/worklenz-frontend/src/types/reporting/reporting-allocation.types.ts new file mode 100644 index 00000000..dec6e46c --- /dev/null +++ b/worklenz-frontend/src/types/reporting/reporting-allocation.types.ts @@ -0,0 +1,19 @@ +import { MemberLoggedTimeType } from "../timeSheet/project.types"; + +export interface IAllocationProject { + name?: string; + color_code?: string; + status_name?: string; + status_color_code?: string; + status_icon?: string; + all_tasks_count?: number; + completed_tasks_count?: number; + progress?: number; + total?: string; + time_logs?: Array<{ time_logged: string }>; +} + +export interface IAllocationViewModel { + projects: Array; + users: Array; +} diff --git a/worklenz-frontend/src/types/reporting/reporting-filters.types.ts b/worklenz-frontend/src/types/reporting/reporting-filters.types.ts new file mode 100644 index 00000000..eadfdbd8 --- /dev/null +++ b/worklenz-frontend/src/types/reporting/reporting-filters.types.ts @@ -0,0 +1,15 @@ +import { IProject } from '@/types/project/project.types'; +import { IProjectCategory } from '@/types/project/projectCategory.types'; +import { ITeam } from '@/types/teams/team.type'; + +export interface ISelectableProject extends IProject { + selected?: boolean; +} + +export interface ISelectableTeam extends ITeam { + selected?: boolean; +} + +export interface ISelectableCategory extends IProjectCategory { + selected?: boolean; +} diff --git a/worklenz-frontend/src/types/reporting/reporting.types.ts b/worklenz-frontend/src/types/reporting/reporting.types.ts new file mode 100644 index 00000000..91ad7392 --- /dev/null +++ b/worklenz-frontend/src/types/reporting/reporting.types.ts @@ -0,0 +1,439 @@ +import { IProjectTask } from '../project/projectTasksViewModel.types'; +import { IActivityLogsLabel } from '../tasks/task-activity-logs-get-request'; +import { InlineMember } from '../teamMembers/inlineMember.types'; +import { ITeamMemberViewModel } from '../teamMembers/teamMembersGetResponse.types'; + +export interface IChartObject { + name: string; + color: string; + y: number; +} + +export interface IRPTDuration { + label: string; + key: string; + dates?: string; +} + +export interface IReportingInfo { + organization_name: string; +} + +export interface IRPTTeamStatistics { + count: number; + projects: number; + members: number; +} + +export interface IRPTProjectStatistics { + count: number; + active: number; + overdue: number; +} + +export interface IRPTMemberStatistics { + count: number; + unassigned: number; + overdue: number; +} + +export interface ITimeLogBreakdownReq { + id: string; + date_range: string[]; + duration: string; + time_zone: string; +} + +export interface IRPTOverviewStatistics { + teams?: IRPTTeamStatistics; + projects?: IRPTProjectStatistics; + members?: IRPTMemberStatistics; +} + +export interface IRPTTeam { + id: string; + name: string; + projects_count: number; + members: InlineMember[]; + selected: boolean; +} + +export interface IRPTOverviewTeamChartData { + chart: IChartObject[]; + total: number; + data: Array<{ + label: string; + color: string; + count: number; + }>; +} + +export interface IRPTOverviewTeamByStatus { + all: number; + in_progress: number; + in_planning: number; + completed: number; + proposed: number; + on_hold: number; + blocked: number; + cancelled: number; + chart: { name: string; color: string; y: number }[]; +} + +export interface IRPTOverviewTeamByHealth { + all: number; + not_set: number; + needs_attention: number; + at_risk: number; + good: number; + chart: IChartObject[]; +} + +export interface IRPTOverviewTeamInfo { + by_status?: IRPTOverviewTeamByStatus; + by_category?: IRPTOverviewTeamChartData; + by_health?: IRPTOverviewTeamByHealth; +} + +export interface IRPTOverviewProject { + id: string; + name: string; + client: string; + status: { + name: string; + color_code: string; + icon: string; + }; +} + +export interface IRPTOverviewProjectExt extends IRPTOverviewProject { + team_member_id: string; +} + +export interface IRPTMemberResponse { + total: number; + members: IRPTMember[]; + team: { + id: string; + name: string; + }; +} + +export interface IRPTMember { + color_code: string; + tasks_stat: any; + + avatar_url?: string; + completed: number; + /** Team member id */ + email: string; + id: string; + name: string; + teams: string; + projects: number; + tasks: number; + overdue: number; + ongoing: number; + todo: number; + member_teams: any; +} + +export interface ISingleMemberLogs { + log_day: string; + logs: ISingleMemberLog[]; +} + +export interface ISingleMemberLog { + time_spent_string: string; + project_name: string; + task_name: string; + task_key: string; + task_id: string; + project_id: string; +} + +export interface ISingleMemberActivityLogs { + log_day: string; + logs: ISingleMemberActivityLog[]; +} + +export interface ISingleMemberActivityLog { + project_id: string; + task_id: string; + project_name: string; + task_name: string; + task_key: string; + attribute_type: string; + previous: string; + current: string; + previous_status?: IActivityLogsLabel; + next_status?: IActivityLogsLabel; + previous_priority?: IActivityLogsLabel; + next_priority?: IActivityLogsLabel; + previous_phase?: IActivityLogsLabel; + next_phase?: IActivityLogsLabel; +} + +export interface IRPTOverviewProjectMember { + /** Project member id */ + id: string; + name: string; + tasks_count: number; + completed: number; + incompleted: number; + overdue: number; + contribution: number; + team_member_id: string; + progress: number; + time_logged: string; +} + +export interface IRPTOverviewProjectTasksStats { + completed: number; + incompleted: number; + overdue: number; + total_allocated: number; + total_logged: number; +} + +export interface IRPTOverviewProjectTasksByStatus { + all: number; + todo: number; + doing: number; + done: number; + chart: IChartObject[]; +} + +export interface IRPTOverviewProjectTasksByPriority { + all: number; + low: number; + medium: number; + high: number; + chart: IChartObject[]; +} + +export interface IRPTOverviewProjectTasksByDue { + all: number; + completed: number; + upcoming: number; + overdue: number; + no_due: number; + chart: IChartObject[]; +} + +export interface IRPTOverviewProjectInfo { + stats?: IRPTOverviewProjectTasksStats; + by_status?: IRPTOverviewProjectTasksByStatus; + by_priority?: IRPTOverviewProjectTasksByPriority; + by_due?: IRPTOverviewProjectTasksByDue; +} + +export interface IRPTOverviewMemberStats { + teams: number; + projects: number; + completed: number; + ongoing: number; + overdue: number; + total_tasks: number; + total_logged: number; + assigned: number; +} + +export interface IRPTOverviewMemberChartData { + chart: IChartObject[]; + total: number; + data: Array<{ + label: string; + color: string; + count: number; + }>; +} + +export interface IRPTOverviewMemberInfo { + stats?: IRPTOverviewMemberStats; + by_status?: IRPTOverviewMemberChartData; + by_priority?: IRPTOverviewMemberChartData; + by_project?: IRPTOverviewMemberChartData; +} + +export interface IRPTReportingMemberTask { + id: string; + name: string; + project_id: string; + project_name: string; + priority_name: string; + priority_color: string; + project_color: string; + parent_task_id?: string; + status_name: string; + status_color: string; + end_date?: string; + completed_date?: string; + days_overdue?: number; + estimated_string?: string; + time_spent_string?: string; + overlogged_time?: string; + is_sub_task: boolean; +} + +export interface IRPTTasksDrawerData { + project: IRPTOverviewProject; + member: IRPTMember; +} + +export interface IRPTTaskDrawerData { + taskId: string; + projectId: string; +} + +export interface IRPTMemberDrawerData { + project: IRPTOverviewProject | null; + member: IRPTMember | null; +} + +export interface IRPTLastActivity { + assigned_user?: null; + attribute_type?: 'name'; + current?: string; + done_by?: { + name?: string; + avatar_url?: string; + color_code?: string; + }; + avatar_url?: string; + color_code?: string; + name?: string; + log_text?: string; + log_type?: string; + previous?: string; + last_activity_string?: string; +} + +export interface IRPTProject extends IRPTOverviewProject { + last_activity: IRPTLastActivity; + color_code: string; + category_id: string | null; + category_name: string; + category_color: string; + team_id: string; + team_name: string; + team_color: string; + status_id: string; + status_name: string; + status_color: string; + status_icon: string; + start_date: string; + end_date: string; + estimated_time: number; + actual_time: number; + days_left: number | null; + is_overdue: boolean; + is_today: boolean; + tasks_stat: { + total: number; + todo: number; + doing: number; + done: number; + }; + comment?: string; + project_health: string; + health_color: string; + health_name: string; + estimated_time_string?: string; + actual_time_string?: string; + project_manager: ITeamMemberViewModel; +} + +export interface IDurationChangedEmitter { + selectedDuration: IRPTDuration | null; + dateRange: string[]; +} + +export interface IRPTSingleMemberDrawerData { + member: IRPTMember | null; +} + +export interface IProjectLogs { + project_name: string; + task_key: string; + task_name: string; + time_spent_string: string; + user_name: string; + avatar_url: string; + created_at: string; +} + +export interface IProjectLogsBreakdown { + log_day: string; + logs: IProjectLogs[]; +} + +export interface IRPTProjectsViewModel { + total?: number; + projects?: IRPTProject[]; +} +export interface IRPTMembersViewModel { + total?: number; + members?: IRPTMember[]; +} + +export interface IRPTMemberProject { + completed?: number; + contribution?: number; + incompleted?: number; + name: string; + overdue?: number; + project_task_count: number; + task_count: number; + team: string; + team_member_id: string; + time_logged: string; + id: string; +} + +export interface IRPTTimeProject { + id: string; + name: string; + color_code: string; + value?: number; + estimated_value?: number; + end_date?: string; + logged_time?: string; +} + +export interface IRPTTimeMember { + name: string; + value?: number; + color_code: string; + logged_time?: string; +} + +export interface IMemberTaskStatGroupResonse { + team_member_name: string; + groups: IMemberTaskStatGroup[]; +} + +export interface IMemberProjectsResonse { + team_member_name: string; + projects: IRPTProject[]; +} + +export interface IMemberTaskStatGroup { + name: string; + color_code: string; + tasks: IProjectTask[]; +} + +export interface IGetProjectsRequestBody { + index: number; + size: number; + field: string; + order: string; + search: string | null; + filter: string; + statuses: string[]; + healths: string[]; + categories: string[]; + project_managers: string[]; + archived: boolean; +} diff --git a/worklenz-frontend/src/types/schedule/schedule-v2.types.ts b/worklenz-frontend/src/types/schedule/schedule-v2.types.ts new file mode 100644 index 00000000..84d41afa --- /dev/null +++ b/worklenz-frontend/src/types/schedule/schedule-v2.types.ts @@ -0,0 +1,65 @@ +import { Dayjs } from 'dayjs'; + +export interface Settings { + workingDays: string[]; + workingHours: number; +} + +export interface OneDate { + day?: number; + name?: string; + isToday?: boolean; + isWeekend?: boolean; +} + +interface Dates { + days?: OneDate[]; + month?: string; + weeks?: any[]; +} + +export interface DateList { + chart_end?: string; + date_data?: Dates[]; + chart_start?: string; +} + +interface DateUnion { + start?: string; + end?: string; +} + +export interface Project { + name?: string; + id?: string; + hours_per_day?: number; + total_hours?: number; + date_union?: DateUnion; + indicator_offset?: number; + indicator_width?: number; + tasks?:any []; +} + +export interface Member { + id?: string; + name?: string; + projects?:Project[]; +} + +export interface Project { + id?: string; + projects?:Project[]; +} + +export interface ScheduleData { + id?: string; + project_id?:string; + team_member_id?:string; + seconds_per_day?:string; + total_seconds?:string; + allocated_from?:Dayjs|null; + allocated_to?:Dayjs|null; + +} + +export type PickerType = 'week' | 'month'; \ No newline at end of file diff --git a/worklenz-frontend/src/types/schedule/schedule.types.ts b/worklenz-frontend/src/types/schedule/schedule.types.ts new file mode 100644 index 00000000..28d40a74 --- /dev/null +++ b/worklenz-frontend/src/types/schedule/schedule.types.ts @@ -0,0 +1,27 @@ +export interface Task { + taskName: string; + taskId: string; + taskStatus: string; +} + +export interface Project { + projectName: string; + projectId: string; + perDayHours: number; + totalHours: number; + startDate: string; + endDate: string; + tasks: Task[]; +} + +export interface timeLogged { + date: string; + hours: number; +} + +export interface Member { + memberName: string; + memberId: string; + projects: Project[]; + timeLogged: timeLogged[]; +} diff --git a/worklenz-frontend/src/app/interfaces/notification-settings.ts b/worklenz-frontend/src/types/settings/notifications.types.ts similarity index 100% rename from worklenz-frontend/src/app/interfaces/notification-settings.ts rename to worklenz-frontend/src/types/settings/notifications.types.ts diff --git a/worklenz-frontend/src/app/interfaces/profile-settings.ts b/worklenz-frontend/src/types/settings/profile.types.ts similarity index 100% rename from worklenz-frontend/src/app/interfaces/profile-settings.ts rename to worklenz-frontend/src/types/settings/profile.types.ts diff --git a/worklenz-frontend/src/types/settings/task-templates.types.ts b/worklenz-frontend/src/types/settings/task-templates.types.ts new file mode 100644 index 00000000..8d911a7e --- /dev/null +++ b/worklenz-frontend/src/types/settings/task-templates.types.ts @@ -0,0 +1,14 @@ +import { IProjectTask } from "../project/projectTasksViewModel.types"; + +export interface ITaskTemplatesGetResponse { + name?: string; + id?: string; + created_at?: string; +} + +export interface ITaskTemplateGetResponse { + id?: string; + name?: string; + tasks?: IProjectTask[] + } + diff --git a/worklenz-frontend/src/app/interfaces/timezone.ts b/worklenz-frontend/src/types/settings/timezone.types.ts similarity index 100% rename from worklenz-frontend/src/app/interfaces/timezone.ts rename to worklenz-frontend/src/types/settings/timezone.types.ts diff --git a/worklenz-frontend/src/types/socket.types.ts b/worklenz-frontend/src/types/socket.types.ts new file mode 100644 index 00000000..3f7692af --- /dev/null +++ b/worklenz-frontend/src/types/socket.types.ts @@ -0,0 +1,12 @@ +export interface Message { + id: string; + text: string; + userId: string; + timestamp: Date; +} + +export interface User { + id: string; + name: string; + status: 'online' | 'offline'; +} diff --git a/worklenz-frontend/src/types/status.types.ts b/worklenz-frontend/src/types/status.types.ts new file mode 100644 index 00000000..25e4669b --- /dev/null +++ b/worklenz-frontend/src/types/status.types.ts @@ -0,0 +1,29 @@ +export interface ITaskStatusCategory { + id?: string; + name?: string; + color_code?: string; + description?: string; +} + +export interface ITaskStatus { + id?: string; + name?: string; + description?: string; + order_index?: number; + color_code?: string; + color_code_dark?: string; + team_id?: string; + default_status?: boolean; + date_created?: string; + date_updated?: string; +} + +export interface IKanbanTaskStatus extends ITaskStatus { + category_id?: string; +} + +export interface ICategorizedStatus { + category_id: string; + category_color: string; + statuses: ITaskStatusCategory[]; +} diff --git a/worklenz-frontend/src/types/task.types.ts b/worklenz-frontend/src/types/task.types.ts new file mode 100644 index 00000000..6d6b8d58 --- /dev/null +++ b/worklenz-frontend/src/types/task.types.ts @@ -0,0 +1,36 @@ +import { LabelType } from './label.type'; +import { MemberType } from './member.types'; +import { ProjectType } from './project.types'; + +export type TaskStatusType = 'doing' | 'todo' | 'done'; +export type TaskPriorityType = 'low' | 'medium' | 'high'; + +export type SubTaskType = { + subTaskId: string; + subTask: string; + subTaskMembers?: string[]; + subTaskStatus: TaskStatusType; + subTaskDueDate?: Date; +}; + +export type TaskType = { + taskId: string; + task: string; + description?: string | null; + progress?: number; + members?: MemberType[]; + labels?: LabelType[]; + status: TaskStatusType | string; + priority: TaskPriorityType | string; + timeTracking?: number; + estimation?: string; + startDate?: Date | null; + dueDate?: Date | null; + completedDate?: Date | null; + createdDate?: Date; + lastUpdated?: Date; + reporter?: string; + phase?: string; + subTasks?: TaskType[]; + project?: ProjectType; +}; diff --git a/worklenz-frontend/src/types/tasks/bulk-action-bar.types.ts b/worklenz-frontend/src/types/tasks/bulk-action-bar.types.ts new file mode 100644 index 00000000..4ad10a4d --- /dev/null +++ b/worklenz-frontend/src/types/tasks/bulk-action-bar.types.ts @@ -0,0 +1,43 @@ +import { ITaskAssignee } from './task.types'; +import { ITaskLabel } from './taskLabel.types'; + +export interface IBulkTasksStatusChangeRequest { + tasks: string[]; + status_id: string; +} + +export interface IBulkTasksPriorityChangeRequest { + tasks: string[]; + priority_id: string; +} + +export interface IBulkTasksPhaseChangeRequest { + tasks: string[]; + phase_id: string; +} + +export interface IBulkTasksDeleteRequest { + tasks: string[]; +} + +export interface IBulkTasksArchiveRequest { + tasks: string[]; + project_id: string; +} + +export interface IBulkTasksLabelsRequest { + tasks: string[]; + text: string | null; + labels: ITaskLabel[]; +} + +export interface IBulkAssignRequest { + tasks: string[]; + project_id: string; +} + +export interface IBulkAssignMembersRequest { + tasks: string[]; + project_id: string; + members: ITaskAssignee[]; +} diff --git a/worklenz-frontend/src/types/tasks/subTask.types.ts b/worklenz-frontend/src/types/tasks/subTask.types.ts new file mode 100644 index 00000000..a331c856 --- /dev/null +++ b/worklenz-frontend/src/types/tasks/subTask.types.ts @@ -0,0 +1,18 @@ +import { InlineMember } from '@/types/teamMembers/inlineMember.types'; +import { IProjectTask } from '../project/projectTasksViewModel.types'; + +export interface ISubTask extends IProjectTask { + id?: string; + name?: string; + status_color?: string; + status?: string; + status_name?: string; + priority?: string; + priority_name?: string; + end_date?: string; + names?: InlineMember[]; + show_handles?: boolean; + min?: number; + max?: number; + color_code?: string; +} diff --git a/worklenz-frontend/src/types/tasks/task-activity-logs-get-request.ts b/worklenz-frontend/src/types/tasks/task-activity-logs-get-request.ts new file mode 100644 index 00000000..f6cb37b3 --- /dev/null +++ b/worklenz-frontend/src/types/tasks/task-activity-logs-get-request.ts @@ -0,0 +1,61 @@ +export interface IActivityLogsUser { + avatar_url?: string; + color_code?: string; + name?: string; + user_id?: string; +} + +export interface IActivityLogsLabel { + color_code?: string; + color_code_dark?: string; + name?: string; +} + +export interface IActivityLogsStatus { + color_code?: string; + color_code_dark?: string; + name?: string; +} + +export enum IActivityLogAttributeTypes { + NAME = 'name', + STATUS = 'status', + ASSIGNEES = 'assignee', + END_DATE = 'end_date', + START_DATE = 'start_date', + PRIORITY = 'priority', + PHASE = 'phase', + ESTIMATION = 'estimation', + LABEL = 'label', + DESCRIPTION = 'description', + ATTACHMENT = 'attachment', + COMMENT = 'comment', + ARCHIVE = 'archive', +} + +export interface IActivityLog { + attribute_type?: string; + created_at?: string; + current?: string; + log_text?: string; + log_type?: string; + previous?: string; + task_id?: string; + done_by?: IActivityLogsUser; + assigned_user?: IActivityLogsUser; + label_data?: IActivityLogsLabel; + previous_status?: IActivityLogsLabel; + next_status?: IActivityLogsLabel; + previous_priority?: IActivityLogsLabel; + next_priority?: IActivityLogsLabel; + previous_phase: IActivityLogsLabel; + next_phase: IActivityLogsLabel; +} + +export interface IActivityLogsResponse { + avatar_url?: string; + color_code?: string; + created_at?: string; + logs?: IActivityLog[]; + name?: string; +} diff --git a/worklenz-frontend/src/types/tasks/task-assignee-update-response.ts b/worklenz-frontend/src/types/tasks/task-assignee-update-response.ts new file mode 100644 index 00000000..95bb21a3 --- /dev/null +++ b/worklenz-frontend/src/types/tasks/task-assignee-update-response.ts @@ -0,0 +1,13 @@ +import { ITaskAssignee } from '../project/projectTasksViewModel.types'; +import { InlineMember } from '../teamMembers/inlineMember.types'; +import { ITeamMemberViewModel } from '../teamMembers/teamMembersGetResponse.types'; + +export interface ITaskAssigneesUpdateResponse { + id: string; + parent_task: string; + assignees: ITaskAssignee[]; + names: InlineMember[]; + members: ITeamMemberViewModel[]; + mode: any; + team_member_id: string; +} diff --git a/worklenz-frontend/src/types/tasks/task-attachment-view-model.ts b/worklenz-frontend/src/types/tasks/task-attachment-view-model.ts new file mode 100644 index 00000000..866fa7c2 --- /dev/null +++ b/worklenz-frontend/src/types/tasks/task-attachment-view-model.ts @@ -0,0 +1,24 @@ +export interface ITaskAttachmentViewModel { + id?: string; + name?: string; + url?: string; + size?: string; + type?: string; + created_at?: string; + task_name?: string; + task_key?: string; + uploader_name?: string; +} + +export interface IProjectAttachmentsViewModel { + total?: number; + data?: ITaskAttachmentViewModel[]; +} + +export interface ITaskAttachment { + file: string; + file_name: string; + project_id: string; + size: number; + task_id?: string | null; +} diff --git a/worklenz-frontend/src/types/tasks/task-comments.types.ts b/worklenz-frontend/src/types/tasks/task-comments.types.ts new file mode 100644 index 00000000..4c7b395d --- /dev/null +++ b/worklenz-frontend/src/types/tasks/task-comments.types.ts @@ -0,0 +1,42 @@ +import { ITaskAttachment, ITaskAttachmentViewModel } from './task-attachment-view-model'; + +export interface ITaskCommentsCreateRequest { + task_id?: string; + content?: string; + mentions?: any[]; + attachments?: ITaskAttachment[]; +} + +export interface ITaskAttachmentCreateRequest { + task_id?: string; + attachments?: ITaskAttachment[]; +} + +export interface ITaskComment { + id?: string; + content?: string; + rawContent?: string; + user_id?: string; + team_member_id?: string; + task_id?: string; + created_at?: string; + updated_at?: string; +} + +export interface ITaskCommentViewModel extends ITaskComment { + edit?: boolean; + is_edited?: boolean; + member_name?: string; + team_member_id?: string; + avatar_url?: string; + reactions?: ITaskCommentReaction; + attachments?: ITaskAttachmentViewModel[]; +} + +interface ITaskCommentReaction { + likes: { + count: number; + liked_members: string[]; + liked_member_ids: string[]; + }; +} diff --git a/worklenz-frontend/src/types/tasks/task-create-request.types.ts b/worklenz-frontend/src/types/tasks/task-create-request.types.ts new file mode 100644 index 00000000..fcd1eef0 --- /dev/null +++ b/worklenz-frontend/src/types/tasks/task-create-request.types.ts @@ -0,0 +1,25 @@ +import { ITask } from './task.types'; + +export interface ITaskCreateRequest extends ITask { + status_id?: string; + project_id?: string; + task_index?: number; + attachments?: string[]; + labels?: string[]; + parent_task_id?: string; + reporter_id?: string; + team_id?: string; + priority_id?: string; + phase_id?: string; + chart_start?: string; + offset?: number; + width?: number; + is_dragged?: boolean; +} + +export interface IHomeTaskCreateRequest extends ITask { + name: string; + project_id?: string; + reporter_id?: string; + team_id?: string; +} diff --git a/worklenz-frontend/src/types/tasks/task-dependency.types.ts b/worklenz-frontend/src/types/tasks/task-dependency.types.ts new file mode 100644 index 00000000..6f1154f6 --- /dev/null +++ b/worklenz-frontend/src/types/tasks/task-dependency.types.ts @@ -0,0 +1,13 @@ +export interface ITaskDependency { + id?: string; + task_id?: string; + task_name?: string; + dependency_type?: IDependencyType; + related_task_id?: string; + related_task_name?: string; + task_key?: string; +} + +export enum IDependencyType { + BLOCKED_BY = 'blocked_by', +} diff --git a/worklenz-frontend/src/types/tasks/task-list-priority.types.ts b/worklenz-frontend/src/types/tasks/task-list-priority.types.ts new file mode 100644 index 00000000..1119cec8 --- /dev/null +++ b/worklenz-frontend/src/types/tasks/task-list-priority.types.ts @@ -0,0 +1,7 @@ +export interface ITaskListPriorityChangeResponse { + priority_id: string | undefined; + id: string; + parent_task?: string; + color_code: string; + color_code_dark: string; +} diff --git a/worklenz-frontend/src/types/tasks/task-list-status.types.ts b/worklenz-frontend/src/types/tasks/task-list-status.types.ts new file mode 100644 index 00000000..bd37a624 --- /dev/null +++ b/worklenz-frontend/src/types/tasks/task-list-status.types.ts @@ -0,0 +1,16 @@ +export interface ITaskListStatusChangeResponse { + status_id: string | undefined; + id: string; + parent_task: string; + color_code: string; + color_code_dark: string; + complete_ratio: number; + completed_at?: string; + timer_start_time?: number; + statusCategory?: { + is_todo: boolean; + is_doing: boolean; + is_done: boolean; + }; + completed_deps?: boolean; +} diff --git a/worklenz-frontend/src/app/interfaces/api-models/task-log-create-request.ts b/worklenz-frontend/src/types/tasks/task-log-view.types.ts similarity index 100% rename from worklenz-frontend/src/app/interfaces/api-models/task-log-create-request.ts rename to worklenz-frontend/src/types/tasks/task-log-view.types.ts diff --git a/worklenz-frontend/src/app/interfaces/task-phase-change-response.ts b/worklenz-frontend/src/types/tasks/task-phase-change-response.ts similarity index 100% rename from worklenz-frontend/src/app/interfaces/task-phase-change-response.ts rename to worklenz-frontend/src/types/tasks/task-phase-change-response.ts diff --git a/worklenz-frontend/src/app/interfaces/api-models/task-status-create-request.ts b/worklenz-frontend/src/types/tasks/task-status-create-request.ts similarity index 77% rename from worklenz-frontend/src/app/interfaces/api-models/task-status-create-request.ts rename to worklenz-frontend/src/types/tasks/task-status-create-request.ts index bd493432..9dbb8c71 100644 --- a/worklenz-frontend/src/app/interfaces/api-models/task-status-create-request.ts +++ b/worklenz-frontend/src/types/tasks/task-status-create-request.ts @@ -1,4 +1,4 @@ -import {ITaskStatus} from "@interfaces/task-status"; +import { ITaskStatus } from './taskStatus.types'; export interface ITaskStatusCreateRequest extends ITaskStatus { status_order?: string[]; diff --git a/worklenz-frontend/src/app/interfaces/api-models/task-status-update-model.ts b/worklenz-frontend/src/types/tasks/task-status-update-model.types.ts similarity index 100% rename from worklenz-frontend/src/app/interfaces/api-models/task-status-update-model.ts rename to worklenz-frontend/src/types/tasks/task-status-update-model.types.ts diff --git a/worklenz-frontend/src/types/tasks/task.types.ts b/worklenz-frontend/src/types/tasks/task.types.ts new file mode 100644 index 00000000..3f804921 --- /dev/null +++ b/worklenz-frontend/src/types/tasks/task.types.ts @@ -0,0 +1,107 @@ +import { IUser } from '../auth/login.types'; +import { ITaskLabel } from '../label.type'; +import { IProject } from '../project/project.types'; +import { IProjectMember } from '../project/projectMember.types'; +import { InlineMember } from '../teamMembers/inlineMember.types'; +import { ITeamMember } from '../teamMembers/teamMember.types'; +import { ISubTask } from './subTask.types'; +import { ITaskPhase } from './taskPhase.types'; +import { ITaskPriority } from './taskPriority.types'; +import { ITaskStatus } from './taskStatus.types'; + +export interface ITaskAssignee { + team_member_id: any; + id: string; + project_member_id: string; + name: string; +} + +export interface ITask { + assignees?: ITaskAssignee[] | string[]; + assignees_ids?: any[]; + description?: string; + done?: boolean; + end?: string | Date; + end_date?: string | Date; + id?: string; + name?: string; + resize_valid?: boolean; + start?: string | Date; + start_date?: string | Date; + _start?: Date; + _end?: Date; + color_code?: string; + priority?: string; + priority_id?: string; + status?: string; + status_id?: string; + project_id?: string; + reporter_id?: string; + created_at?: string; + updated_at?: string; + show_handles?: boolean; + min?: number; + max?: number; + total_hours?: number; + total_minutes?: number; + name_color?: string; + sub_tasks_count?: number; + is_sub_task?: boolean; + parent_task_name?: string; + parent_task_id?: string; + show_sub_tasks?: boolean; + sub_tasks?: ISubTask[]; + archived?: boolean; + subscribers?: IUser[]; +} + +export interface IProjectMemberViewModel extends IProjectMember { + name?: string; + team_member_id?: string; + job_title?: string; + email?: string; + avatar_url?: string; + color_code?: string; +} + +export interface ITaskViewModel extends ITask { + task_key?: string; + created_from_now?: string; + updated_from_now?: string; + reporter?: string; + start_date?: string; + end_date?: string; + sub_tasks_count?: number; + is_sub_task?: boolean; + status_color?: string; + status_color_dark?: string; + attachments_count?: number; + complete_ratio?: number; + names?: InlineMember[]; + labels?: ITaskLabel[]; + timer_start_time?: number; + phase_id?: string; + billable?: boolean; + recurring?: boolean; +} + +export interface ITaskTeamMember extends ITeamMember { + name?: string; + color_code?: string; + avatar_url?: string; + email?: string; +} + +export interface ITaskFormViewModel { + task?: ITaskViewModel; + priorities?: ITaskPriority[]; + projects?: IProject[]; + statuses?: ITaskStatus[]; + phases?: ITaskPhase[]; + team_members?: ITaskTeamMember[]; +} + +export interface IHomeTaskViewModel extends ITask { + task?: ITaskViewModel; + team_member_id?: string; +} diff --git a/worklenz-frontend/src/types/tasks/taskLabel.types.ts b/worklenz-frontend/src/types/tasks/taskLabel.types.ts new file mode 100644 index 00000000..2ed13c5a --- /dev/null +++ b/worklenz-frontend/src/types/tasks/taskLabel.types.ts @@ -0,0 +1,14 @@ +export interface ITaskLabel { + id?: string; + name?: string; + color_code?: string; + team_id?: string; + selected?: boolean; + end?: boolean; + names?: string[]; + usage?: number; +} + +export interface ITaskLabelFilter extends ITaskLabel { + selected?: boolean; +} diff --git a/worklenz-frontend/src/types/tasks/taskList.types.ts b/worklenz-frontend/src/types/tasks/taskList.types.ts new file mode 100644 index 00000000..7a01b821 --- /dev/null +++ b/worklenz-frontend/src/types/tasks/taskList.types.ts @@ -0,0 +1,110 @@ +import { ITaskPrioritiesGetResponse } from '../apiModels/taskPrioritiesGetResponse.types'; +import { IProjectTask } from '../project/projectTasksViewModel.types'; +import { ITeamMemberViewModel } from '../teamMembers/teamMembersGetResponse.types'; +import { ITaskLabel } from './taskLabel.types'; +import { ITaskStatus } from './taskStatus.types'; + +export interface ISelectableTaskStatus extends ITaskStatus { + selected?: boolean; +} + +export interface ITaskListConfigV2 { + id: string; + field: string | null; + order: string | null; + search: string | null; + statuses: string | null; + members: string | null; + projects: string | null; + labels?: string | null; + priorities?: string | null; + archived?: boolean; + count?: boolean; + parent_task?: string; + group?: string; + isSubtasksInclude: boolean; +} + +export interface ITaskListGroup { + id: string; + name: string; + start_date?: string; + end_date?: string; + color_code: string; + color_code_dark: string; + category_id?: string; + old_category_id?: string; + todo_progress?: number; + doing_progress?: number; + done_progress?: number; + tasks: IProjectTask[]; + is_expanded?: boolean; +} + +export interface ITaskListContextMenuEvent { + event: MouseEvent; + task: IProjectTask; +} + +export interface IGroupByOption { + label: string; + value: string; +} + +export interface ITaskListPriorityFilter extends ITaskPrioritiesGetResponse { + selected?: boolean; +} + +export interface ITaskListLabelFilter extends ITaskLabel { + selected?: boolean; +} + +export interface ITaskListMemberFilter extends ITeamMemberViewModel { + selected?: boolean; +} + +export interface ITaskListGroupChangeResponse { + taskId?: string; + isSubTask?: boolean; +} + +export interface ITaskListSortableColumn { + label?: string; + key?: string; + sort_order?: string; + selected?: boolean; +} + +export interface ITaskListEstimationChangeResponse { + id: string; + total_minutes: number; + total_hours: number; + parent_task: string; + total_minutes_spent: number; + total_time_string?: string; +} + +export interface ILabelsChangeResponse { + id: string; + parent_task: string; + is_new: boolean; + new_label: ITaskLabel; + all_labels: ITaskLabel[]; + labels: ITaskLabel[]; +} + +export interface IMembersFilterChange { + selection: string; + is_subtasks_included: boolean; +} + +export interface ITaskListColumn { + id?: string; + name?: string; + key?: string; + index?: number; + pinned?: boolean; + project_id?: string; + custom_column?: boolean; + custom_column_obj?: any; +} diff --git a/worklenz-frontend/src/types/tasks/taskListFilters.types.ts b/worklenz-frontend/src/types/tasks/taskListFilters.types.ts new file mode 100644 index 00000000..45595384 --- /dev/null +++ b/worklenz-frontend/src/types/tasks/taskListFilters.types.ts @@ -0,0 +1,50 @@ +import { ITeamMemberViewModel } from '../teamMembers/teamMembersGetResponse.types'; +import { ITaskLabel } from './taskLabel.types'; +import { ITaskPrioritiesGetResponse } from './taskPriority.types'; + +export interface ITaskListMemberFilter extends ITeamMemberViewModel { + selected?: boolean; +} + +export interface ITaskListLabelFilter extends ITaskLabel { + selected?: boolean; +} + +export interface ITaskListPriorityFilter extends ITaskPrioritiesGetResponse { + selected?: boolean; +} + +export interface ITaskListGroupChangeResponse { + taskId?: string; + isSubTask?: boolean; +} + +export interface ITaskListSortableColumn { + label?: string; + key?: string; + sort_order?: string; + selected?: boolean; +} + +export interface ITaskListEstimationChangeResponse { + id: string; + total_minutes: number; + total_hours: number; + parent_task: string; + total_minutes_spent: number; + total_time_string?: string; +} + +export interface ILabelsChangeResponse { + id: string; + parent_task: string; + is_new: boolean; + new_label: ITaskLabel; + all_labels: ITaskLabel[]; + labels: ITaskLabel[]; +} + +export interface IMembersFilterChange { + selection: string; + is_subtasks_included: boolean; +} diff --git a/worklenz-frontend/src/app/interfaces/api-models/task-phase.ts b/worklenz-frontend/src/types/tasks/taskPhase.types.ts similarity index 100% rename from worklenz-frontend/src/app/interfaces/api-models/task-phase.ts rename to worklenz-frontend/src/types/tasks/taskPhase.types.ts diff --git a/worklenz-frontend/src/types/tasks/taskPriority.types.ts b/worklenz-frontend/src/types/tasks/taskPriority.types.ts new file mode 100644 index 00000000..87640129 --- /dev/null +++ b/worklenz-frontend/src/types/tasks/taskPriority.types.ts @@ -0,0 +1,12 @@ +export interface ITaskPriority { + id: string; + name: string; + value: string; + color_code?: string; + color_code_dark?: string; +} + +export interface ITaskPrioritiesGetResponse extends ITaskPriority { + color_code?: string; + color_code_dark?: string; +} diff --git a/worklenz-frontend/src/types/tasks/taskStatus.types.ts b/worklenz-frontend/src/types/tasks/taskStatus.types.ts new file mode 100644 index 00000000..cc0a52cd --- /dev/null +++ b/worklenz-frontend/src/types/tasks/taskStatus.types.ts @@ -0,0 +1,24 @@ +import { ITaskStatusCategory } from './taskStatusCategory.types'; + +export interface ITaskStatus { + id?: string; + name?: string; + description?: string; + order_index?: number; + color_code?: string; + color_code_dark?: string; + team_id?: string; + default_status?: boolean; + date_created?: string; + date_updated?: string; +} + +export interface IKanbanTaskStatus extends ITaskStatus { + category_id?: string; +} + +export interface ICategorizedStatus { + category_id: string; + category_color: string; + statuses: ITaskStatusCategory[]; +} diff --git a/worklenz-frontend/src/app/interfaces/task-status-category.ts b/worklenz-frontend/src/types/tasks/taskStatusCategory.types.ts similarity index 100% rename from worklenz-frontend/src/app/interfaces/task-status-category.ts rename to worklenz-frontend/src/types/tasks/taskStatusCategory.types.ts diff --git a/worklenz-frontend/src/types/tasks/taskStatusGetResponse.types.ts b/worklenz-frontend/src/types/tasks/taskStatusGetResponse.types.ts new file mode 100644 index 00000000..55ebfda4 --- /dev/null +++ b/worklenz-frontend/src/types/tasks/taskStatusGetResponse.types.ts @@ -0,0 +1,6 @@ +import { ITaskStatus } from './taskStatus.types'; + +export interface ITaskStatusViewModel extends ITaskStatus { + category_id?: string; + category_name?: string; +} diff --git a/worklenz-frontend/src/types/teamMembers/inlineMember.types.ts b/worklenz-frontend/src/types/teamMembers/inlineMember.types.ts new file mode 100644 index 00000000..d4986df7 --- /dev/null +++ b/worklenz-frontend/src/types/teamMembers/inlineMember.types.ts @@ -0,0 +1,8 @@ +export interface InlineMember { + name: string; + end?: boolean; + names?: string[]; + color_code?: string; + avatar_url?: string; + team_member_id?: string; +} diff --git a/worklenz-frontend/src/types/teamMembers/team-member-create-request.ts b/worklenz-frontend/src/types/teamMembers/team-member-create-request.ts new file mode 100644 index 00000000..d1191d57 --- /dev/null +++ b/worklenz-frontend/src/types/teamMembers/team-member-create-request.ts @@ -0,0 +1,7 @@ +import { ITeamMember } from './teamMember.types'; + +export interface ITeamMemberCreateRequest extends ITeamMember { + job_title?: string | null; + emails?: string | string[]; + is_admin?: boolean; +} diff --git a/worklenz-frontend/src/app/interfaces/team-member.ts b/worklenz-frontend/src/types/teamMembers/teamMember.types.ts similarity index 100% rename from worklenz-frontend/src/app/interfaces/team-member.ts rename to worklenz-frontend/src/types/teamMembers/teamMember.types.ts diff --git a/worklenz-frontend/src/types/teamMembers/teamMembersGetResponse.types.ts b/worklenz-frontend/src/types/teamMembers/teamMembersGetResponse.types.ts new file mode 100644 index 00000000..becd43b9 --- /dev/null +++ b/worklenz-frontend/src/types/teamMembers/teamMembersGetResponse.types.ts @@ -0,0 +1,77 @@ +import { IInsightTasks } from '../project/projectInsights.types'; +import { ITask } from '../tasks/task.types'; +import { ITeamMember } from './teamMember.types'; + +export interface ITeamMemberViewModel extends ITeamMember { + id?: string; + active?: boolean; + name?: string; + taskCount?: number; + job_title?: string; + email?: string; + task_count?: number; + projects_count?: number; + role_name?: string; + tasks?: ITask[]; + is_admin?: boolean; + show_handles?: boolean; + is_online?: boolean; + avatar_url?: string; + selected?: boolean; + color_code?: string; + usage?: number; + projects?: any; + total_logged_time?: string; + member_teams?: string[]; + is_pending?: boolean; +} + +export interface ITeamMemberOverviewGetResponse extends ITeamMember { + task_count?: number; + done_task_count?: number; + pending_task_count?: number; + overdue_task_count?: number; + progress?: number; + contribution?: number; + job_title?: string; + id: string; + name?: string; + tasks?: IInsightTasks[]; +} + +export interface ITeamMemberOverviewByProjectGetResponse { + name?: string; + assigned_task_count?: number; + progress?: number; + done_task_count?: number; + pending_task_count?: number; + id?: string; +} + +export interface ITeamMemberOverviewChartGetResponse { + pending_count?: number; + done_count?: number; +} + +export interface ITeamMemberFilterResponse { + text?: string; + value?: string; +} + +export interface ITeamMemberTreeMapResponse { + total: number; + data: ITeamMemberTreeMap[]; +} + +export interface ITeamMemberTreeMap { + value?: number; + name?: string; + parent?: number; + id?: string; + color?: string; +} + +export interface ITasksByTeamMembers { + name?: string; + task_count?: string; +} diff --git a/worklenz-frontend/src/types/teamMembers/teamMembersViewModel.types.ts b/worklenz-frontend/src/types/teamMembers/teamMembersViewModel.types.ts new file mode 100644 index 00000000..7b4fb91c --- /dev/null +++ b/worklenz-frontend/src/types/teamMembers/teamMembersViewModel.types.ts @@ -0,0 +1,6 @@ +import { ITeamMemberViewModel } from './teamMembersGetResponse.types'; + +export interface ITeamMembersViewModel { + data?: ITeamMemberViewModel[]; + total?: number; +} diff --git a/worklenz-frontend/src/types/teams/team.type.ts b/worklenz-frontend/src/types/teams/team.type.ts new file mode 100644 index 00000000..c2e2199f --- /dev/null +++ b/worklenz-frontend/src/types/teams/team.type.ts @@ -0,0 +1,47 @@ +export interface ITeam { + id?: string; + name?: string; + key?: string; + user_id?: string; + created_at?: string; + updated_at?: string; +} + +export interface ITeamInvites { + id?: string; + team_id?: string; + team_member_id?: string; + team_name?: string; + team_owner?: string; +} + +export interface IAcceptTeamInvite { + team_member_id?: string; + show_alert?: boolean; +} + +export interface ITeamGetResponse extends ITeam { + owner?: boolean; + owns_by?: string; + created_at?: string; +} + +// Type for all modal/drawer states +export interface UIState { + drawer: boolean; + settingsDrawer: boolean; + updateTitleNameModal: boolean; +} + +export interface ITeamState { + teamsList: ITeamGetResponse[]; + loading: boolean; + error: string | null; + initialized: boolean; + ui: UIState; + activeTeamId: string | null; +} + +export interface ITeamActivateResponse { + subdomain: string; +} diff --git a/worklenz-frontend/src/types/timeSheet/project.types.ts b/worklenz-frontend/src/types/timeSheet/project.types.ts new file mode 100644 index 00000000..cf224c32 --- /dev/null +++ b/worklenz-frontend/src/types/timeSheet/project.types.ts @@ -0,0 +1,5 @@ +export type MemberLoggedTimeType = { + id: string; + name: string; + total_time?: string; +}; diff --git a/worklenz-frontend/src/types/todo.types.ts b/worklenz-frontend/src/types/todo.types.ts new file mode 100644 index 00000000..2bf98262 --- /dev/null +++ b/worklenz-frontend/src/types/todo.types.ts @@ -0,0 +1,5 @@ +export type TodoType = { + todoId: string; + todoName: string; + isCompleted: boolean; +}; diff --git a/worklenz-frontend/src/types/updates.types.ts b/worklenz-frontend/src/types/updates.types.ts new file mode 100644 index 00000000..53236096 --- /dev/null +++ b/worklenz-frontend/src/types/updates.types.ts @@ -0,0 +1,6 @@ +export type UpdatesType = { + messageId: string; + message: string; + member: string; + createdAt: Date; +}; diff --git a/worklenz-frontend/src/types/user.types.ts b/worklenz-frontend/src/types/user.types.ts new file mode 100644 index 00000000..98bf2160 --- /dev/null +++ b/worklenz-frontend/src/types/user.types.ts @@ -0,0 +1,6 @@ +export type IUserType = { + id?: string; + name?: string; + password?: string; + email?: string; +}; diff --git a/worklenz-frontend/src/typings.d.ts b/worklenz-frontend/src/typings.d.ts deleted file mode 100644 index 4cfd9776..00000000 --- a/worklenz-frontend/src/typings.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare const PerfectScrollbar: any; diff --git a/worklenz-frontend/src/utils/calculate-time-difference.ts b/worklenz-frontend/src/utils/calculate-time-difference.ts new file mode 100644 index 00000000..c3e13f19 --- /dev/null +++ b/worklenz-frontend/src/utils/calculate-time-difference.ts @@ -0,0 +1,41 @@ +import { + differenceInSeconds, + differenceInMinutes, + differenceInHours, + differenceInDays, + differenceInWeeks, + differenceInMonths, + differenceInYears, + formatDistanceToNow, +} from 'date-fns'; +import { enUS, es, pt } from 'date-fns/locale'; +import { getLanguageFromLocalStorage } from './language-utils'; + +export function calculateTimeDifference(timestamp: string | Date): string { + const date = typeof timestamp === 'string' ? new Date(timestamp) : timestamp; + const localeString = getLanguageFromLocalStorage(); + const locale = localeString === 'en' ? enUS : localeString === 'es' ? es : pt; + const now = new Date(); + + const diffInSeconds = differenceInSeconds(now, date); + if (diffInSeconds < 60) { + return 'Just now'; + } + + const distanceFunctions = [ + differenceInYears, + differenceInMonths, + differenceInWeeks, + differenceInDays, + differenceInHours, + differenceInMinutes, + ]; + + for (const distanceFunction of distanceFunctions) { + if (distanceFunction(now, date) > 0) { + return formatDistanceToNow(date, { addSuffix: true, locale }); + } + } + + return 'Just now'; +} diff --git a/worklenz-frontend/src/utils/calculate-time-gap.ts b/worklenz-frontend/src/utils/calculate-time-gap.ts new file mode 100644 index 00000000..0d601136 --- /dev/null +++ b/worklenz-frontend/src/utils/calculate-time-gap.ts @@ -0,0 +1,10 @@ +import { formatDistanceToNow } from "date-fns"; +import { enUS, es, pt } from "date-fns/locale"; +import { getLanguageFromLocalStorage } from "./language-utils"; + +export function calculateTimeGap(timestamp: string | Date): string { + const localeString = getLanguageFromLocalStorage(); + const locale = localeString === 'en' ? enUS : localeString === 'es' ? es : pt; + const date = typeof timestamp === 'string' ? new Date(timestamp) : timestamp; + return formatDistanceToNow(date, { addSuffix: true, locale }); +} diff --git a/worklenz-frontend/src/utils/check-task-dependency-status.ts b/worklenz-frontend/src/utils/check-task-dependency-status.ts new file mode 100644 index 00000000..27d42aee --- /dev/null +++ b/worklenz-frontend/src/utils/check-task-dependency-status.ts @@ -0,0 +1,13 @@ +import { tasksApiService } from "@/api/tasks/tasks.api.service"; +import logger from "./errorLogger"; + +export const checkTaskDependencyStatus = async (taskId: string, statusId: string) => { + if (!taskId || !statusId) return false; + try { + const res = await tasksApiService.getTaskDependencyStatus(taskId, statusId); + return res.done ? res.body.can_continue : false; + } catch (error) { + logger.error('Error checking task dependency status:', error); + return false; + } + }; \ No newline at end of file diff --git a/worklenz-frontend/src/utils/colorUtils.ts b/worklenz-frontend/src/utils/colorUtils.ts new file mode 100644 index 00000000..801492ee --- /dev/null +++ b/worklenz-frontend/src/utils/colorUtils.ts @@ -0,0 +1,3 @@ +export const tagBackground = (color: string): string => { + return `${color}1A`; // 1A is 10% opacity in hex +}; \ No newline at end of file diff --git a/worklenz-frontend/src/utils/current-date-string.ts b/worklenz-frontend/src/utils/current-date-string.ts new file mode 100644 index 00000000..c0ff9782 --- /dev/null +++ b/worklenz-frontend/src/utils/current-date-string.ts @@ -0,0 +1,12 @@ +import dayjs from 'dayjs'; +import { getLanguageFromLocalStorage } from './language-utils'; + +export const currentDateString = (): string => { + const date = dayjs(); + const localeString = getLanguageFromLocalStorage(); + const locale = localeString === 'en' ? 'en' : localeString === 'es' ? 'es' : 'pt'; + + const todayText = + localeString === 'en' ? 'Today is' : localeString === 'es' ? 'Hoy es' : 'Hoje é'; + return `${todayText} ${date.locale(locale).format('dddd, MMMM DD, YYYY')}`; +}; diff --git a/worklenz-frontend/src/utils/dateUtils.ts b/worklenz-frontend/src/utils/dateUtils.ts new file mode 100644 index 00000000..0b976a4c --- /dev/null +++ b/worklenz-frontend/src/utils/dateUtils.ts @@ -0,0 +1,32 @@ +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; + +// Initialize the relativeTime plugin +dayjs.extend(relativeTime); + +/** + * Formats a date to a relative time string (e.g., "2 hours ago", "a day ago") + * This mimics the Angular fromNow pipe functionality + * + * @param date - The date to format (string, Date, or dayjs object) + * @returns A string representing the relative time + */ +export const fromNow = (date: string | Date | dayjs.Dayjs): string => { + if (!date) return ''; + return dayjs(date).fromNow(); +}; + +/** + * Formats a date to a specific format + * + * @param date - The date to format (string, Date, or dayjs object) + * @param format - The format string (default: 'YYYY-MM-DD') + * @returns A formatted date string + */ +export const formatDate = ( + date: string | Date | dayjs.Dayjs, + format: string = 'YYYY-MM-DD' +): string => { + if (!date) return ''; + return dayjs(date).format(format); +}; \ No newline at end of file diff --git a/worklenz-frontend/src/utils/durationDateFormat.ts b/worklenz-frontend/src/utils/durationDateFormat.ts new file mode 100644 index 00000000..a0b082f6 --- /dev/null +++ b/worklenz-frontend/src/utils/durationDateFormat.ts @@ -0,0 +1,25 @@ +export const durationDateFormat = (date: Date | null | string | undefined): string => { + if (!date) return '-'; + + const givenDate = new Date(date); + const currentDate = new Date(); + + const diffInMilliseconds = currentDate.getTime() - givenDate.getTime(); + + const diffInDays = Math.floor(diffInMilliseconds / (1000 * 60 * 60 * 24)); + const diffInMonths = + currentDate.getMonth() - + givenDate.getMonth() + + 12 * (currentDate.getFullYear() - givenDate.getFullYear()); + const diffInYears = currentDate.getFullYear() - givenDate.getFullYear(); + + if (diffInYears > 0) { + return diffInYears === 1 ? '1 year ago' : `${diffInYears} years ago`; + } else if (diffInMonths > 0) { + return diffInMonths === 1 ? '1 month ago' : `${diffInMonths} months ago`; + } else if (diffInDays > 0) { + return diffInDays === 1 ? '1 day ago' : `${diffInDays} days ago`; + } else { + return 'Today'; + } +}; diff --git a/worklenz-frontend/src/utils/errorLogger.ts b/worklenz-frontend/src/utils/errorLogger.ts new file mode 100644 index 00000000..3929138d --- /dev/null +++ b/worklenz-frontend/src/utils/errorLogger.ts @@ -0,0 +1,153 @@ +export type LogLevel = 'info' | 'success' | 'warning' | 'error' | 'debug'; + +interface LogStyles { + title: string; + text: string; + background?: string; +} + +interface LogOptions { + showTimestamp?: boolean; + collapsed?: boolean; + level?: LogLevel; +} + +class ConsoleLogger { + private readonly isProduction = import.meta.env.PROD; + + private readonly styles: Record = { + info: { + title: 'color: #1890ff; font-weight: bold; font-size: 12px;', + text: 'color: #1890ff; font-size: 12px;', + background: 'background: transparent; padding: 2px 5px; border-radius: 2px;', + }, + success: { + title: 'color: #52c41a; font-weight: bold; font-size: 12px;', + text: 'color: #52c41a; font-size: 12px;', + background: 'background: transparent; padding: 2px 5px; border-radius: 2px;', + }, + warning: { + title: 'color: #faad14; font-weight: bold; font-size: 12px;', + text: 'color: #faad14; font-size: 12px;', + background: 'background: transparent; padding: 2px 5px; border-radius: 2px;', + }, + error: { + title: 'color: #ff4d4f; font-weight: bold; font-size: 12px;', + text: 'color: #ff4d4f; font-size: 12px;', + background: 'background: transparent; padding: 2px 5px; border-radius: 2px;', + }, + debug: { + title: 'color: #722ed1; font-weight: bold; font-size: 12px;', + text: 'color: #722ed1; font-size: 12px;', + background: 'background: transparent; padding: 2px 5px; border-radius: 2px;', + }, + }; + + // Private helper methods + private getTimestamp(): string { + return new Date().toISOString(); + } + + private formatValue(value: unknown): unknown { + if (value instanceof Error) { + const { name, message, stack } = value; + return { name, message, stack }; + } + return value; + } + + private formatLogMessage(title: string, showTimestamp: boolean, styles: LogStyles): string { + const timestamp = showTimestamp ? `[${this.getTimestamp()}] ` : ''; + return `%c${timestamp}${title}`; + } + + private logObjectData(data: Record, styles: LogStyles): void { + for (const [key, value] of Object.entries(data)) { + console.log(`%c${key}:`, styles.title, this.formatValue(value)); + } + } + + private log( + title: string, + data: unknown, + { showTimestamp = true, collapsed = false, level = 'info' }: LogOptions = {} + ): void { + if (this.isProduction) return; + + const styles = this.styles[level]; + const logMethod = collapsed ? console.groupCollapsed : console.group; + const formattedMessage = this.formatLogMessage(title, showTimestamp, styles); + + logMethod(formattedMessage, styles.background ?? styles.title); + + if (data !== null) { + if (typeof data === 'object' && data !== null) { + this.logObjectData(data as Record, styles); + } else { + console.log(`%cValue:`, styles.title, this.formatValue(data)); + } + } + + console.groupEnd(); + } + + // Public logging methods + public info(title: string, data: unknown = null, options?: Omit): void { + this.log(title, data, { ...options, level: 'info' }); + } + + public success(title: string, data: unknown = null, options?: Omit): void { + this.log(title, data, { ...options, level: 'success' }); + } + + public warning(title: string, data: unknown = null, options?: Omit): void { + this.log(title, data, { ...options, level: 'warning' }); + } + + public error(title: string, data: unknown = null, options?: Omit): void { + this.log(title, data, { ...options, level: 'error' }); + } + + public debug(title: string, data: unknown = null, options?: Omit): void { + this.log(title, data, { ...options, level: 'debug' }); + } + + // Table logging + public table(title: string, data: unknown[] = []): void { + if (this.isProduction) return; + + console.group(`%c${title}`, this.styles.info.title); + if (data.length > 0) { + console.table(data); + } + console.groupEnd(); + } + + // Performance logging + public time(label: string): void { + if (this.isProduction) return; + console.time(label); + } + + public timeEnd(label: string): void { + if (this.isProduction) return; + console.timeEnd(label); + } + + // Group logging + public group(title: string, collapsed = false): void { + if (this.isProduction) return; + + const method = collapsed ? console.groupCollapsed : console.group; + method(`%c${title}`, this.styles.info.title); + } + + public groupEnd(): void { + if (this.isProduction) return; + console.groupEnd(); + } +} + +// Create default instance +const logger = new ConsoleLogger(); +export default logger; diff --git a/worklenz-frontend/src/utils/fetchData.ts b/worklenz-frontend/src/utils/fetchData.ts new file mode 100644 index 00000000..a1fb558d --- /dev/null +++ b/worklenz-frontend/src/utils/fetchData.ts @@ -0,0 +1,13 @@ +// function to fetch data +export const fetchData = async ( + url: string, + setState: React.Dispatch> +) => { + try { + const response = await fetch(url); + const data = await response.json(); + setState(data); + } catch (error) { + console.error(`Error fetching data from ${url}:`, error); + } +}; diff --git a/worklenz-frontend/src/utils/file-utils.ts b/worklenz-frontend/src/utils/file-utils.ts new file mode 100644 index 00000000..a2e2fd6f --- /dev/null +++ b/worklenz-frontend/src/utils/file-utils.ts @@ -0,0 +1,7 @@ +export const getBase64 = (file: File): Promise => + new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => resolve(reader.result); + reader.onerror = error => reject(error); + }); diff --git a/worklenz-frontend/src/utils/format-date-time-with-locale.ts b/worklenz-frontend/src/utils/format-date-time-with-locale.ts new file mode 100644 index 00000000..23548ac3 --- /dev/null +++ b/worklenz-frontend/src/utils/format-date-time-with-locale.ts @@ -0,0 +1,12 @@ +import { format } from 'date-fns'; +import { enUS, es, pt } from 'date-fns/locale'; +import { getLanguageFromLocalStorage } from './language-utils'; + +export const formatDateTimeWithLocale = (dateString: string): string => { + if (!dateString) return ''; + + const date = new Date(dateString); + const localeString = getLanguageFromLocalStorage(); + const locale = localeString === 'en' ? enUS : localeString === 'es' ? es : pt; + return format(date, 'MMM d, yyyy, h:mm:ss a', { locale }); +}; diff --git a/worklenz-frontend/src/utils/get-initial-theme.ts b/worklenz-frontend/src/utils/get-initial-theme.ts new file mode 100644 index 00000000..49127631 --- /dev/null +++ b/worklenz-frontend/src/utils/get-initial-theme.ts @@ -0,0 +1,10 @@ +export const getInitialTheme = () => { + try { + return ( + localStorage.getItem('theme') || + (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') + ); + } catch { + return 'light'; + } +}; diff --git a/worklenz-frontend/src/utils/getPriorityColors.ts b/worklenz-frontend/src/utils/getPriorityColors.ts new file mode 100644 index 00000000..bd9d624c --- /dev/null +++ b/worklenz-frontend/src/utils/getPriorityColors.ts @@ -0,0 +1,21 @@ +import { TaskPriorityType } from '../types/task.types'; + +type ThemeMode = 'light' | 'dark'; + +const priorityColors = { + light: { + low: '#c2e4d0', + medium: '#f9e3b1', + high: '#f6bfc0', + }, + dark: { + low: '#75c997', + medium: '#fbc84c', + high: '#f37070', + }, +}; + +export const getPriorityColor = (priority: string, themeMode: ThemeMode): string => { + const colors = priorityColors[themeMode]; + return colors[priority as TaskPriorityType]; +}; diff --git a/worklenz-frontend/src/utils/getStatusColor.ts b/worklenz-frontend/src/utils/getStatusColor.ts new file mode 100644 index 00000000..cef37c3f --- /dev/null +++ b/worklenz-frontend/src/utils/getStatusColor.ts @@ -0,0 +1,21 @@ +import { TaskStatusType } from '../types/task.types'; + +type ThemeMode = 'light' | 'dark'; + +const statusColors = { + light: { + todo: '#d8d7d8', + doing: '#c0d5f6', + done: '#c2e4d0', + }, + dark: { + todo: '#a9a9a9', + doing: '#70a6f3', + done: '#75c997', + }, +}; + +export const getStatusColor = (status: string, themeMode: ThemeMode): string => { + const colors = statusColors[themeMode]; + return colors[status as TaskStatusType]; +}; diff --git a/worklenz-frontend/src/utils/greetingString.ts b/worklenz-frontend/src/utils/greetingString.ts new file mode 100644 index 00000000..0cc763b4 --- /dev/null +++ b/worklenz-frontend/src/utils/greetingString.ts @@ -0,0 +1,35 @@ +import dayjs from 'dayjs'; +import { getLanguageFromLocalStorage } from './language-utils'; + +export const greetingString = (name: string): string => { + const date = dayjs(); + const hours = date.hour(); + let greet; + + if (hours < 12) greet = 'morning'; + else if (hours >= 12 && hours < 16) greet = 'afternoon'; + else if (hours >= 16 && hours < 24) greet = 'evening'; + + const language = getLanguageFromLocalStorage(); + let greetingPrefix = 'Hi'; + let greetingSuffix = 'Good'; + let morning = 'morning'; + let afternoon = 'afternoon'; + let evening = 'evening'; + + if (language === 'es') { + greetingPrefix = 'Hola'; + greetingSuffix = 'Buen'; + morning = 'mañana'; + afternoon = 'tarde'; + evening = 'noche'; + } else if (language === 'pt') { + greetingPrefix = 'Olá'; + greetingSuffix = 'Bom'; + morning = 'manhã'; + afternoon = 'tarde'; + evening = 'noite'; + } + + return `${greetingPrefix} ${name}, ${greetingSuffix} ${greet}!`; +}; diff --git a/worklenz-frontend/src/utils/language-utils.ts b/worklenz-frontend/src/utils/language-utils.ts new file mode 100644 index 00000000..6e0643d6 --- /dev/null +++ b/worklenz-frontend/src/utils/language-utils.ts @@ -0,0 +1,37 @@ +import { ILanguageType, Language } from '@/features/i18n/localesSlice'; + +const STORAGE_KEY = 'i18nextLng'; + +/** + * Gets the user's browser language and returns it if supported, otherwise returns English + * @returns The detected supported language or English as fallback + */ +export const getDefaultLanguage = (): ILanguageType => { + const browserLang = navigator.language.split('-')[0]; + if (Object.values(Language).includes(browserLang as Language)) { + return browserLang as ILanguageType; + } + return Language.EN; +}; + +export const DEFAULT_LANGUAGE: ILanguageType = getDefaultLanguage(); + +/** + * Gets the current language from local storage + * @returns The stored language or default language if not found + */ +export const getLanguageFromLocalStorage = (): ILanguageType => { + const savedLng = localStorage.getItem(STORAGE_KEY); + if (Object.values(Language).includes(savedLng as Language)) { + return savedLng as ILanguageType; + } + return DEFAULT_LANGUAGE; +}; + +/** + * Saves the current language to local storage + * @param lng Language to save + */ +export const saveLanguageInLocalStorage = (lng: ILanguageType): void => { + localStorage.setItem(STORAGE_KEY, lng); +}; diff --git a/worklenz-frontend/src/utils/localStorageFunctions.ts b/worklenz-frontend/src/utils/localStorageFunctions.ts new file mode 100644 index 00000000..a4b47660 --- /dev/null +++ b/worklenz-frontend/src/utils/localStorageFunctions.ts @@ -0,0 +1,17 @@ +// these functions are utility functions which are use for save data and get data from local storage +export const getJSONFromLocalStorage = (name: string) => { + const storedItem = localStorage.getItem(name); + return storedItem ? JSON.parse(storedItem) : null; +}; + +export const saveJSONToLocalStorage = (name: string, item: unknown) => { + localStorage.setItem(name, JSON.stringify(item)); +}; + +export const saveToLocalStorage = (name: string, item: string) => { + localStorage.setItem(name, item); +}; + +export const getFromLocalStorage = (name: string) => { + return localStorage.getItem(name); +}; diff --git a/worklenz-frontend/src/utils/mixpanelInit.ts b/worklenz-frontend/src/utils/mixpanelInit.ts new file mode 100644 index 00000000..809a176e --- /dev/null +++ b/worklenz-frontend/src/utils/mixpanelInit.ts @@ -0,0 +1,11 @@ +import { MixpanelConfig } from '@/types/mixpanel.types'; +import mixpanel from 'mixpanel-browser'; + +export const initMixpanel = (token: string, config: MixpanelConfig = {}): void => { + mixpanel.init(token, { + debug: import.meta.env.VITE_APP_ENV !== 'production', + track_pageview: true, + persistence: 'localStorage', + ...config, + }); +}; diff --git a/worklenz-frontend/src/utils/project-list-utils.ts b/worklenz-frontend/src/utils/project-list-utils.ts new file mode 100644 index 00000000..cc81eb2f --- /dev/null +++ b/worklenz-frontend/src/utils/project-list-utils.ts @@ -0,0 +1,24 @@ +import { DATE_FORMAT_OPTIONS } from '@/shared/constants'; +import { IProjectViewModel } from '@/types/project/projectViewModel.types'; + +interface DateRange { + startDate: string | null; + endDate: string | null; +} + +export const formatDateRange = ({ startDate, endDate }: DateRange): string => { + const formattedStart = startDate + ? new Date(startDate).toLocaleDateString('en-US', DATE_FORMAT_OPTIONS) + : 'N/A'; + const formattedEnd = endDate + ? new Date(endDate).toLocaleDateString('en-US', DATE_FORMAT_OPTIONS) + : 'N/A'; + + return `Start date: ${formattedStart}\nEnd date: ${formattedEnd}`; +}; + +export const getTaskProgressTitle = (data: IProjectViewModel): string => { + if (!data.all_tasks_count) return 'No tasks available.'; + if (data.all_tasks_count === data.completed_tasks_count) return 'All tasks completed.'; + return `${data.completed_tasks_count || 0}/${data.all_tasks_count || 0} tasks completed.`; +}; diff --git a/worklenz-frontend/src/utils/projectUtils.ts b/worklenz-frontend/src/utils/projectUtils.ts new file mode 100644 index 00000000..63fd5930 --- /dev/null +++ b/worklenz-frontend/src/utils/projectUtils.ts @@ -0,0 +1,11 @@ +import { PROJECT_STATUS_ICON_MAP } from '@/shared/constants'; +import React from 'react'; + +export function getStatusIcon(statusIcon: string, colorCode: string) { + return React.createElement( + PROJECT_STATUS_ICON_MAP[statusIcon as keyof typeof PROJECT_STATUS_ICON_MAP], + { + style: { fontSize: 16, color: colorCode }, + } + ); +} diff --git a/worklenz-frontend/src/utils/sanitizeInput.ts b/worklenz-frontend/src/utils/sanitizeInput.ts new file mode 100644 index 00000000..8bdcbf8a --- /dev/null +++ b/worklenz-frontend/src/utils/sanitizeInput.ts @@ -0,0 +1,35 @@ +import DOMPurify from 'dompurify'; + +/** + * Sanitizes user input to prevent XSS attacks + * + * @param input - The user input string to sanitize + * @param options - Optional configuration for DOMPurify + * @returns Sanitized string + */ +export const sanitizeInput = (input: string, options?: DOMPurify.Config): string => { + if (!input) return ''; + + // Default options for plain text inputs (strip all HTML) + const defaultOptions: DOMPurify.Config = { + ALLOWED_TAGS: [], + ALLOWED_ATTR: [], + }; + + return DOMPurify.sanitize(input, options || defaultOptions); +}; + +/** + * Sanitizes a string for use in HTML contexts (allows some basic tags) + * + * @param input - The input containing HTML to sanitize + * @returns Sanitized HTML string + */ +export const sanitizeHtml = (input: string): string => { + if (!input) return ''; + + return DOMPurify.sanitize(input, { + ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'], + ALLOWED_ATTR: ['href', 'target', 'rel'], + }); +}; \ No newline at end of file diff --git a/worklenz-frontend/src/utils/schedule.ts b/worklenz-frontend/src/utils/schedule.ts new file mode 100644 index 00000000..5f2ba66a --- /dev/null +++ b/worklenz-frontend/src/utils/schedule.ts @@ -0,0 +1,3 @@ +export const getDayName = (date: Date) => { + return date.toLocaleDateString('en-US', { weekday: 'long' }); // Returns `Monday`, `Tuesday`, etc. + }; \ No newline at end of file diff --git a/worklenz-frontend/src/utils/session-helper.ts b/worklenz-frontend/src/utils/session-helper.ts new file mode 100644 index 00000000..cc88b343 --- /dev/null +++ b/worklenz-frontend/src/utils/session-helper.ts @@ -0,0 +1,33 @@ +import { ILocalSession } from '@/types/auth/local-session.types'; + +export const WORKLENZ_SESSION_ID = import.meta.env.VITE_WORKLENZ_SESSION_ID; +const storage: Storage = localStorage; + +export function setSession(user: ILocalSession): void { + storage.setItem(WORKLENZ_SESSION_ID, btoa(unescape(encodeURIComponent(JSON.stringify(user))))); + // storage.setItem(WORKLENZ_SESSION_ID, btoa(JSON.stringify(user))); +} + +export function getUserSession(): ILocalSession | null { + try { + return JSON.parse(atob(storage.getItem(WORKLENZ_SESSION_ID))); + } catch (e) { + return null; + } +} + +export function hasSession() { + return !!storage.getItem(WORKLENZ_SESSION_ID); +} + +export function deleteSession() { + storage.removeItem(WORKLENZ_SESSION_ID); +} + +export function getRole() { + const session = getUserSession(); + if (!session) return 'Unknown'; + if (session.owner) return 'Owner'; + if (session.is_admin) return 'Admin'; + return 'Member'; +} diff --git a/worklenz-frontend/src/utils/simpleDateFormat.ts b/worklenz-frontend/src/utils/simpleDateFormat.ts new file mode 100644 index 00000000..aa3ba95c --- /dev/null +++ b/worklenz-frontend/src/utils/simpleDateFormat.ts @@ -0,0 +1,24 @@ +export const simpleDateFormat = (date: Date | string | null): string => { + if (!date) return ''; + + // convert ISO string date to Date object if necessary + const dateObj = typeof date === 'string' ? new Date(date) : date; + + // check if the date is valid + if (isNaN(dateObj.getTime())) return ''; + + const options: Intl.DateTimeFormatOptions = { + month: 'short', + day: 'numeric', + }; + + const currentYear = new Date().getFullYear(); + const inputYear = dateObj.getFullYear(); + + // add year to the format if it's not the current year + if (inputYear !== currentYear) { + options.year = 'numeric'; + } + + return new Intl.DateTimeFormat('en-US', options).format(dateObj); +}; diff --git a/worklenz-frontend/src/utils/sort-team-members.ts b/worklenz-frontend/src/utils/sort-team-members.ts new file mode 100644 index 00000000..85b5b4ae --- /dev/null +++ b/worklenz-frontend/src/utils/sort-team-members.ts @@ -0,0 +1,34 @@ +export const sortByBooleanField = >( + data: T[], + field: keyof T, + prioritizeTrue: boolean = true +) => { + return [...data].sort((a, b) => { + const aValue = !!a[field]; + const bValue = !!b[field]; + + if (aValue === bValue) return 0; + if (prioritizeTrue) { + return aValue ? -1 : 1; + } else { + return !aValue ? -1 : 1; + } + }); +}; + +export const sortBySelection = (data: Array<{ selected?: boolean }>) => + sortByBooleanField(data, 'selected'); + +export const sortByPending = (data: Array<{ pending_invitation?: boolean }>) => + sortByBooleanField(data, 'pending_invitation', false); + +export const sortTeamMembers = (data: Array<{ selected?: boolean; pending_invitation?: boolean; is_pending?: boolean }>) => { + return sortByBooleanField( + sortByBooleanField( + sortByBooleanField(data, 'is_pending', false), + 'pending_invitation', + false + ), + 'selected' + ); +}; diff --git a/worklenz-frontend/src/utils/test-utils.ts b/worklenz-frontend/src/utils/test-utils.ts new file mode 100644 index 00000000..dde9ad50 --- /dev/null +++ b/worklenz-frontend/src/utils/test-utils.ts @@ -0,0 +1,12 @@ +import { configureStore } from '@reduxjs/toolkit'; +import authReducer from '@/features/auth/authSlice'; +import userReducer from '@/features/user/userSlice'; +export const mockStore = (preloadedState = {}) => { + return configureStore({ + reducer: { + auth: authReducer, + user: userReducer, + }, + preloadedState, + }); +}; diff --git a/worklenz-frontend/src/utils/themeWiseColor.ts b/worklenz-frontend/src/utils/themeWiseColor.ts new file mode 100644 index 00000000..7ae3d8e9 --- /dev/null +++ b/worklenz-frontend/src/utils/themeWiseColor.ts @@ -0,0 +1,6 @@ +type ThemeMode = 'light' | 'dark'; + +// this utility for toggle any colors with the theme +export const themeWiseColor = (defaultColor: string, darkColor: string, themeMode: ThemeMode) => { + return themeMode === 'dark' ? darkColor : defaultColor; +}; diff --git a/worklenz-frontend/src/utils/timeUtils.ts b/worklenz-frontend/src/utils/timeUtils.ts new file mode 100644 index 00000000..6f2f6dbe --- /dev/null +++ b/worklenz-frontend/src/utils/timeUtils.ts @@ -0,0 +1,13 @@ +import dayjs from 'dayjs'; + +export function formatDate(date: Date): string { + return dayjs(date).format('MMM DD, YYYY'); +} + +export function buildTimeString(hours: number, minutes: number, seconds: number) { + const h = hours > 0 ? `${hours}h` : ''; + const m = `${minutes}m`; + const s = `${seconds}s`; + return `${h} ${m} ${s}`.trim(); +} + diff --git a/worklenz-frontend/src/utils/timeZoneCurrencyMap.ts b/worklenz-frontend/src/utils/timeZoneCurrencyMap.ts new file mode 100644 index 00000000..cc2b63f4 --- /dev/null +++ b/worklenz-frontend/src/utils/timeZoneCurrencyMap.ts @@ -0,0 +1,7 @@ +export const timeZoneCurrencyMap: { [key: string]: string } = { + 'America/New_York': 'USD', // United States Dollar + 'Europe/London': 'GBP', // British Pound + 'Asia/Tokyo': 'JPY', // Japanese Yen + 'Asia/Colombo': 'Rs', // Sri Lankan Rupee + 'Asia/Kolkata': 'INR', // Indian Rupee +}; diff --git a/worklenz-frontend/src/utils/toCamelCase.ts b/worklenz-frontend/src/utils/toCamelCase.ts new file mode 100644 index 00000000..31a4e1c2 --- /dev/null +++ b/worklenz-frontend/src/utils/toCamelCase.ts @@ -0,0 +1,3 @@ +export const toCamelCase = (str: string) => { + return str.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (_, char) => char.toUpperCase()); +}; diff --git a/worklenz-frontend/src/utils/toQueryString.ts b/worklenz-frontend/src/utils/toQueryString.ts new file mode 100644 index 00000000..9a66063f --- /dev/null +++ b/worklenz-frontend/src/utils/toQueryString.ts @@ -0,0 +1,9 @@ +export function toQueryString(obj: any) { + const query = []; + for (const key in obj) { + if (typeof obj[key] !== undefined && obj[key] !== null) { + query.push(`${key}=${obj[key]}`); + } + } + return '?' + query.join('&'); +} diff --git a/worklenz-frontend/src/utils/validateEmail.ts b/worklenz-frontend/src/utils/validateEmail.ts new file mode 100644 index 00000000..cd617661 --- /dev/null +++ b/worklenz-frontend/src/utils/validateEmail.ts @@ -0,0 +1,12 @@ +export const validateEmail = (email: string): boolean => { + if (!email) return false; + + // Check if the email has basic format with @ and domain part + if (!email.includes('@') || email.endsWith('@') || email.split('@').length !== 2) { + return false; + } + + const EMAIL_REGEXP = + /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return EMAIL_REGEXP.test(email); +}; diff --git a/worklenz-frontend/src/vite-env.d.ts b/worklenz-frontend/src/vite-env.d.ts new file mode 100644 index 00000000..0e88f254 --- /dev/null +++ b/worklenz-frontend/src/vite-env.d.ts @@ -0,0 +1,10 @@ +/// +interface ImportMetaEnv { + readonly VITE_API_URL: string; + readonly VITE_APP_TITLE: string; + readonly VITE_APP_ENV: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/worklenz-frontend/tailwind.config.js b/worklenz-frontend/tailwind.config.js new file mode 100644 index 00000000..9b6645eb --- /dev/null +++ b/worklenz-frontend/tailwind.config.js @@ -0,0 +1,8 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./src/**/*.{js,jsx,ts,tsx}'], + theme: { + extend: {}, + }, + plugins: [], +}; diff --git a/worklenz-frontend/tsconfig.app.json b/worklenz-frontend/tsconfig.app.json deleted file mode 100644 index 374cc9d2..00000000 --- a/worklenz-frontend/tsconfig.app.json +++ /dev/null @@ -1,14 +0,0 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/app", - "types": [] - }, - "files": [ - "src/main.ts" - ], - "include": [ - "src/**/*.d.ts" - ] -} diff --git a/worklenz-frontend/tsconfig.json b/worklenz-frontend/tsconfig.json index d3ca5d1c..bf1cbe61 100644 --- a/worklenz-frontend/tsconfig.json +++ b/worklenz-frontend/tsconfig.json @@ -1,58 +1,31 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ { - "compileOnSave": false, "compilerOptions": { - "baseUrl": "src", - "outDir": "./dist/out-tsc", - "forceConsistentCasingInFileNames": true, - "strict": true, - "noImplicitOverride": true, - "noPropertyAccessFromIndexSignature": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "sourceMap": true, - "declaration": false, - "downlevelIteration": true, - "experimentalDecorators": true, - "moduleResolution": "node", - "importHelpers": true, - "allowSyntheticDefaultImports": true, + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": false, "esModuleInterop": true, - "target": "ES2022", - "module": "es2022", + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "baseUrl": ".", "paths": { - "@interfaces/*": [ - "app/interfaces/*" - ], - "@shared/*": [ - "app/shared/*" - ], - "@api/*": [ - "app/services/api/*" - ], - "@services/*": [ - "app/services/*" - ], - "@dtos/*": [ - "app/DTOs/*" - ], - "@pipes/*": [ - "app/pipes/*" - ], - "@admin/components/*": [ - "app/administrator/components/*" - ] + "@/*": ["src/*"], + "@components/*": ["src/components/*"], + "@features/*": ["src/features/*"], + "@assets/*": ["src/assets/*"], + "@utils/*": ["src/utils/*"], + "@api/*": ["src/api/*"], + "@services/*": ["src/services/*"] }, - "lib": [ - "es2020", - "dom" - ], - "useDefineForClassFields": false + "types": ["vitest"] }, - "angularCompilerOptions": { - "enableI18nLegacyMessageIdFormat": false, - "strictInjectionParameters": true, - "strictInputAccessModifiers": true, - "strictTemplates": true - } + "include": ["src"] } diff --git a/worklenz-frontend/tsconfig.spec.json b/worklenz-frontend/tsconfig.spec.json deleted file mode 100644 index 61c1bb35..00000000 --- a/worklenz-frontend/tsconfig.spec.json +++ /dev/null @@ -1,17 +0,0 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/spec", - "types": [ - "jasmine" - ] - }, - "files": [ - "src/test.ts" - ], - "include": [ - "src/**/*.spec.ts", - "src/**/*.d.ts" - ] -} diff --git a/worklenz-frontend/vite.config.ts b/worklenz-frontend/vite.config.ts new file mode 100644 index 00000000..23b51d27 --- /dev/null +++ b/worklenz-frontend/vite.config.ts @@ -0,0 +1,77 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import path from 'path'; +import { UserConfig } from 'vite'; // Import type for better auto-completion + +export default defineConfig(async ({ command }: { command: 'build' | 'serve' }) => { + const tsconfigPaths = (await import('vite-tsconfig-paths')).default; + + return { + // **Plugins** + plugins: [ + react(), + tsconfigPaths({ + // Optionally, you can specify a custom tsconfig file + // loose: true, // If you're using a non-standard tsconfig setup + }), + ], + + // **Resolve** + resolve: { + alias: [ + // Using an array with objects for clarity and easier management + { find: '@', replacement: path.resolve(__dirname, './src') }, + { find: '@components', replacement: path.resolve(__dirname, './src/components') }, + { find: '@features', replacement: path.resolve(__dirname, './src/features') }, + { find: '@assets', replacement: path.resolve(__dirname, './src/assets') }, + { find: '@utils', replacement: path.resolve(__dirname, './src/utils') }, + { find: '@services', replacement: path.resolve(__dirname, './src/services') }, + { find: '@api', replacement: path.resolve(__dirname, './src/api') }, + ], + }, + + // **Build** + build: { + // **Target** + target: ['es2020'], // Updated to a more modern target, adjust according to your needs + + // **Output** + outDir: 'build', + assetsDir: 'assets', // Consider a more specific directory for better organization, e.g., 'build/assets' + cssCodeSplit: true, + + // **Sourcemaps** + sourcemap: command === 'serve' ? 'inline' : true, // Adjust sourcemap strategy based on command + + // **Minification** + minify: 'terser', + terserOptions: { + compress: { + drop_console: command === 'build', + drop_debugger: command === 'build', + }, + // **Additional Optimization** + format: { + comments: command === 'serve', // Preserve comments during development + }, + }, + + // **Rollup Options** + rollupOptions: { + output: { + // **Chunking Strategy** + manualChunks(id) { + if (['react', 'react-dom', 'react-router-dom'].includes(id)) return 'vendor'; + if (id.includes('antd')) return 'antd'; + if (id.includes('i18next')) return 'i18n'; + // Add more conditions as needed + }, + // **File Naming Strategies** + chunkFileNames: 'assets/js/[name]-[hash].js', + entryFileNames: 'assets/js/[name]-[hash].js', + assetFileNames: 'assets/[ext]/[name]-[hash].[ext]', + }, + }, + }, + }; +}); \ No newline at end of file From e42819ef6458ed742733b56b2f846708301f3745 Mon Sep 17 00:00:00 2001 From: chamikaJ Date: Fri, 18 Apr 2025 17:10:56 +0530 Subject: [PATCH 2/5] Update environment configuration, Docker setup, and frontend/backend dependencies - Updated .env.example and .env files for backend and frontend with placeholder values. - Enhanced .gitignore to include additional files and directories. - Modified docker-compose.yml to change image names and improve service health checks. - Updated README.md and SETUP_THE_PROJECT.md for clearer setup instructions. - Added database initialization scripts and SQL files for structured database setup. - Updated frontend Dockerfile to use Node.js 22 and adjusted package.json scripts. - Improved error handling and logging in start scripts for better debugging. - Added reCAPTCHA support in the signup page with conditional loading based on environment variables. --- .env.example | 22 +- .gitignore | 79 ++++++- CONTRIBUTING.md | 2 +- README.md | 203 +++++++++++++++--- SETUP_THE_PROJECT.md | 108 ++++++---- docker-compose.yml | 36 ++-- start.bat | 140 +++++++++++- start.sh | 12 +- worklenz-backend/.env.example | 75 ++++--- worklenz-backend/.env.template | 77 +++---- worklenz-backend/Dockerfile | 4 +- worklenz-backend/README.md | 115 +++++----- worklenz-backend/database/00-init-db.sh | 55 +++++ worklenz-backend/database/README.md | 35 +++ .../database/sql/0_extensions.sql | 3 + .../database/{ => sql}/1_tables.sql | 117 +++++----- worklenz-backend/database/{ => sql}/2_dml.sql | 4 +- .../database/{ => sql}/3_views.sql | 0 .../database/{ => sql}/4_functions.sql | 0 .../database/{ => sql}/5_database_user.sql | 0 .../database/{ => sql}/indexes.sql | 19 ++ .../database/{ => sql}/text_length_checks.sql | 0 .../database/{ => sql}/triggers.sql | 0 .../database/{ => sql}/truncate.sql | 0 worklenz-backend/package-lock.json | 8 + worklenz-backend/package.json | 1 + worklenz-backend/src/app.ts | 11 +- worklenz-backend/src/shared/constants.ts | 14 +- worklenz-frontend/.env.development | 9 +- worklenz-frontend/Dockerfile | 4 +- worklenz-frontend/README.md | 36 +++- worklenz-frontend/package.json | 2 +- .../src/components/EmptyListPlaceholder.tsx | 2 +- .../src/pages/auth/signup-page.tsx | 131 +++++++---- 34 files changed, 948 insertions(+), 376 deletions(-) create mode 100644 worklenz-backend/database/00-init-db.sh create mode 100644 worklenz-backend/database/sql/0_extensions.sql rename worklenz-backend/database/{ => sql}/1_tables.sql (97%) rename worklenz-backend/database/{ => sql}/2_dml.sql (99%) rename worklenz-backend/database/{ => sql}/3_views.sql (100%) rename worklenz-backend/database/{ => sql}/4_functions.sql (100%) rename worklenz-backend/database/{ => sql}/5_database_user.sql (100%) rename worklenz-backend/database/{ => sql}/indexes.sql (85%) rename worklenz-backend/database/{ => sql}/text_length_checks.sql (100%) rename worklenz-backend/database/{ => sql}/triggers.sql (100%) rename worklenz-backend/database/{ => sql}/truncate.sql (100%) diff --git a/.env.example b/.env.example index 97d22a55..9765d3cc 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,8 @@ # Database configuration DB_USER=postgres -DB_PASSWORD=password +DB_PASSWORD=your_db_password DB_NAME=worklenz_db -DB_HOST=db +DB_HOST=localhost DB_PORT=5432 DB_MAX_CLIENTS=50 @@ -10,25 +10,25 @@ DB_MAX_CLIENTS=50 NODE_ENV=development PORT=3000 SESSION_NAME=worklenz.sid -SESSION_SECRET=worklenz-session-secret -COOKIE_SECRET=worklenz-cookie-secret +SESSION_SECRET=your_session_secret +COOKIE_SECRET=your_cookie_secret # CORS SOCKET_IO_CORS=http://localhost:5000 SERVER_CORS=* -# Storage configuration (MinIO) +# Storage configuration STORAGE_PROVIDER=s3 -AWS_REGION=us-east-1 -AWS_BUCKET=worklenz-bucket -S3_ACCESS_KEY_ID=minioadmin -S3_SECRET_ACCESS_KEY=minioadmin -S3_URL=http://minio:9000 +AWS_REGION=your_aws_region +AWS_BUCKET=your_bucket_name +S3_ACCESS_KEY_ID=your_access_key_id +S3_SECRET_ACCESS_KEY=your_secret_access_key +S3_URL=your_s3_url # Application URLs HOSTNAME=localhost:5000 FRONTEND_URL=http://localhost:5000 -# For local development, set these to the frontend service +# For local development LOGIN_FAILURE_REDIRECT=http://localhost:5000 LOGIN_SUCCESS_REDIRECT=http://localhost:5000/auth/authenticate \ No newline at end of file diff --git a/.gitignore b/.gitignore index f3e608ef..d255be7f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,79 @@ -.idea -.vscode +# Dependencies +node_modules/ +.pnp/ +.pnp.js + +# Build outputs +dist/ +build/ +out/ +.next/ +.nuxt/ +.cache/ + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local +.env.development +.env.production +.env.* +!.env.example +!.env.template + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea/ +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +*.sublime-workspace + +# Testing +coverage/ +.nyc_output/ + +# Temp files +.temp/ +.tmp/ +temp/ +tmp/ + +# Debug +.debug/ + +# Misc +.DS_Store +Thumbs.db +.thumbs.db +ehthumbs.db +Desktop.ini +$RECYCLE.BIN/ + +# Yarn +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# TypeScript +*.tsbuildinfo diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2b7541f9..9b17f481 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ We have adopted a Code of Conduct to ensure a welcoming and inclusive environmen ## Coding Standards -- Follow the [Angular Style Guide](https://angular.io/guide/styleguide) for the frontend code. +- Follow the [React Documentation](https://react.dev/learn) for best practices in React development. - Use [TypeScript](https://www.typescriptlang.org/) for both frontend and backend code. - Follow the [Conventional Commits](https://www.conventionalcommits.org/) specification for commit messages. diff --git a/README.md b/README.md index ae535ea8..0288fe02 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,172 @@ +# Worklenz + +Worklenz is an open-source project management platform designed to help teams collaborate efficiently on tasks and projects. + +## Features + +- **Project Planning**: Create and organize projects, assign tasks to team members. +- **Task Management**: Break down projects into smaller tasks, set due dates, priorities, and track progress. +- **Collaboration**: Share files, leave comments, and communicate seamlessly with your team members. +- **Time Tracking**: Monitor time spent on tasks and projects for better resource allocation and billing. +- **Reporting**: Generate detailed reports on project status, team workload, and performance metrics. + +## Tech Stack + +This repository contains the frontend and backend code for Worklenz. + +- **Frontend**: Built using React with Ant Design as the UI library. +- **Backend**: Built using TypeScript, Express.js, with PostgreSQL as the database. + +## Requirements + +- Node.js version v16 or newer +- PostgreSQL version v15 or newer +- Docker and Docker Compose (for containerized setup) + +## Getting Started + +These instructions will help you set up and run the Worklenz project on your local machine for development and testing purposes. + +### Prerequisites + +- Node.js (version 16 or higher) +- PostgreSQL database +- An S3-compatible storage service (like MinIO) or Azure Blob Storage + +### Option 1: Manual Installation + +1. Clone the repository +```bash +git clone https://github.com/yourusername/worklenz.git +cd worklenz +``` + +2. Set up environment variables + - Copy the example environment files + ```bash + cp .env.example .env + cp worklenz-backend/.env.example worklenz-backend/.env + ``` + - Update the environment variables with your configuration + +3. Install dependencies +```bash +# Install backend dependencies +cd worklenz-backend +npm install + +# Install frontend dependencies +cd ../worklenz-frontend +npm install +``` + +4. Set up the database +```bash +# Create a PostgreSQL database named worklenz_db +cd worklenz-backend + +# Execute the SQL setup files in the correct order +psql -U your_username -d worklenz_db -f database/sql/0_extensions.sql +psql -U your_username -d worklenz_db -f database/sql/1_tables.sql +psql -U your_username -d worklenz_db -f database/sql/indexes.sql +psql -U your_username -d worklenz_db -f database/sql/4_functions.sql +psql -U your_username -d worklenz_db -f database/sql/triggers.sql +psql -U your_username -d worklenz_db -f database/sql/3_views.sql +psql -U your_username -d worklenz_db -f database/sql/2_dml.sql +psql -U your_username -d worklenz_db -f database/sql/5_database_user.sql +``` + +5. Start the development servers +```bash +# In one terminal, start the backend +cd worklenz-backend +npm run dev + +# In another terminal, start the frontend +cd worklenz-frontend +npm run dev +``` + +6. Access the application at http://localhost:5000 + +### Option 2: Docker Setup + +The project includes a fully configured Docker setup with: +- Frontend React application +- Backend server +- PostgreSQL database +- MinIO for S3-compatible storage + +1. Clone the repository: +```bash +git clone https://github.com/yourusername/worklenz.git +cd worklenz +``` + +2. Start the Docker containers (choose one option): + +**Using Docker Compose directly** +```bash +docker-compose up -d +``` + +3. The application will be available at: + - Frontend: http://localhost:5000 + - Backend API: http://localhost:3000 + - MinIO Console: http://localhost:9001 (login with minioadmin/minioadmin) + +4. To stop the services: +```bash +docker-compose down +``` + +## Configuration + +### Environment Variables + +Worklenz requires several environment variables to be configured for proper operation. These include: + +- Database credentials +- Session secrets +- Storage configuration (S3 or Azure) +- Authentication settings + +Please refer to the `.env.example` files for a full list of required variables. + +### MinIO Integration + +The project uses MinIO as an S3-compatible object storage service, which provides an open-source alternative to AWS S3 for development and production. + +- **MinIO Console**: http://localhost:9001 + - Username: minioadmin + - Password: minioadmin + +- **Default Bucket**: worklenz-bucket (created automatically when the containers start) + +### Security Considerations + +For production deployments: + +1. Use strong, unique passwords and keys for all services +2. Do not commit `.env` files to version control +3. Use a production-grade PostgreSQL setup with proper backup procedures +4. Enable HTTPS for all public endpoints +5. Review and update dependencies regularly + +## Contributing + +We welcome contributions from the community! If you'd like to contribute, please follow our [contributing guidelines](CONTRIBUTING.md). + +## Security + +If you believe you have found a security vulnerability in Worklenz, we encourage you to responsibly disclose this and not open a public issue. We will investigate all legitimate reports. + +Email [your-security-email@example.com](mailto:your-security-email@example.com) to disclose any security vulnerabilities. + +## License + +This project is licensed under the [MIT License](LICENSE). +

        Worklenz Logo @@ -27,33 +196,6 @@ Worklenz is a project management tool designed to help organizations improve their efficiency. It provides a comprehensive solution for managing projects, tasks, and collaboration within teams. -## Features - -- **Project Planning**: Create and organize projects, assign tasks to team members. -- **Task Management**: Break down projects into smaller tasks, set due dates, priorities, and track progress. -- **Collaboration**: Share files, leave comments, and communicate seamlessly with your team members. -- **Time Tracking**: Monitor time spent on tasks and projects for better resource allocation and billing. -- **Reporting**: Generate detailed reports on project status, team workload, and performance metrics. - -## Tech Stack - -This repository contains the frontend and backend code for Worklenz. - -- **Frontend**: Built using Angular, with [Ant Design of Angular](https://ng.ant.design/docs/introduce/en) as the UI - library.. -- **Backend**: Built using a custom TypeScript implementation of ExpressJS, with PostgreSQL as the database, providing a - robust, scalable, and type-safe backend. - -## Requirements - -- Node.js version v18 or newer -- Postgres version v15.6 -- Redis version v4.6.7 (not used yet. setup only.) - -## Getting started with Worklenz. -- Containerized Installation - Use docker to deploy Worklenz in production or development environments. -- Manual installation - To get started with Worklenz, please follow this guide [worklenz setup guidelines](SETUP_THE_PROJECT.md). - ## Screenshots

        @@ -107,13 +249,6 @@ This repository contains the frontend and backend code for Worklenz. We welcome contributions from the community! If you'd like to contribute, please follow our [contributing guidelines](CONTRIBUTING.md). -### Security - -If you believe you have found a security vulnerability in Worklenz, we encourage you to responsibly disclose this and -not open a public issue. We will investigate all legitimate reports. - -Email [info@worklenz.com](mailto:info@worklenz.com) to disclose any security vulnerabilities. - ### License Worklenz is open source and released under the [GNU Affero General Public License Version 3 (AGPLv3)](LICENSE). diff --git a/SETUP_THE_PROJECT.md b/SETUP_THE_PROJECT.md index 8e777d71..c8917ac1 100644 --- a/SETUP_THE_PROJECT.md +++ b/SETUP_THE_PROJECT.md @@ -4,21 +4,20 @@ Getting started with development is a breeze! Follow these steps and you'll be c ## Requirements -- Node.js version v18 or newer - [Node.js](https://nodejs.org/en/download/current) -- Postgres version v15.6 - [PostgreSQL](https://www.postgresql.org/download/) -- Redis version v4.6.7 (not used yet. setup only.) +- Node.js version v16 or newer - [Node.js](https://nodejs.org/en/download/) +- PostgreSQL version v15 or newer - [PostgreSQL](https://www.postgresql.org/download/) +- S3-compatible storage (like MinIO) for file storage ## Prerequisites -- `$ npm install -g ts-node` -- `$ npm install -g typescript` -- `$ npm install -g grunt grunt-cli` +- `$ npm install -g typescript` (optional, but recommended) ## Installation **Clone the repository:** ```bash git clone https://github.com/Worklenz/worklenz.git + cd worklenz ``` ### Frontend installation @@ -32,13 +31,14 @@ Getting started with development is a breeze! Follow these steps and you'll be c ```bash npm install + ``` 3. **Run the frontend:** ```bash npm start ``` -4. Navigate to [http://localhost:4200](http://localhost:4200) +4. Navigate to [http://localhost:5173](http://localhost:5173) ### Backend installation @@ -54,13 +54,34 @@ Getting started with development is a breeze! Follow these steps and you'll be c 3. **Configure Environment Variables:** - - Create a copy of the `.env.template` file and name it `.env`. - - Update the required fields in `.env` with the specific information. + - Create a copy of the `.env.example` file and name it `.env`. + - Update the required fields in `.env` with your specific configuration. -4. **Restore Database** +4. **Set up Database** - Create a new database named `worklenz_db` on your local PostgreSQL server. - - Update the `DATABASE_NAME` and `PASSWORD` in the `database/6_user_permission.sql` with your DB credentials. - - Open a query console and execute the queries from the .sql files in the `database` directories, following the provided order. + - Update the database connection details in your `.env` file. + - Execute the SQL setup files in the correct order: + + ```bash + # From your PostgreSQL client or command line + psql -U your_username -d worklenz_db -f database/sql/0_extensions.sql + psql -U your_username -d worklenz_db -f database/sql/1_tables.sql + psql -U your_username -d worklenz_db -f database/sql/indexes.sql + psql -U your_username -d worklenz_db -f database/sql/4_functions.sql + psql -U your_username -d worklenz_db -f database/sql/triggers.sql + psql -U your_username -d worklenz_db -f database/sql/3_views.sql + psql -U your_username -d worklenz_db -f database/sql/2_dml.sql + psql -U your_username -d worklenz_db -f database/sql/5_database_user.sql + ``` + + Alternatively, you can use the provided shell script: + + ```bash + # Make sure the script is executable + chmod +x database/00-init-db.sh + # Run the script (may need modifications for local execution) + ./database/00-init-db.sh + ``` 5. **Install Dependencies:** @@ -68,48 +89,49 @@ Getting started with development is a breeze! Follow these steps and you'll be c npm install ``` - This command installs all the necessary libraries required to run the project. - 6. **Run the Development Server:** - **a. Start the TypeScript compiler:** - - Open a new terminal window and run the following command: - - ```bash - grunt dev - ``` - - This starts the `grunt` task runner, which compiles TypeScript code into JavaScript. - - **b. Start the development server:** - - Open another separate terminal window and run the following command: - - ```bash - npm start - ``` + ```bash + npm run dev + ``` This starts the development server allowing you to work on the project. 7. **Run the Production Server:** - **a. Compile TypeScript to JavaScript:** + **a. Build the project:** - Open a new terminal window and run the following command: + ```bash + npm run build + ``` - ```bash - grunt build - ``` - - This starts the `grunt` task runner, which compiles TypeScript code into JavaScript for production use. + This will compile the TypeScript code into JavaScript for production use. **b. Start the production server:** - Once the compilation is complete, run the following command in the same terminal window: + ```bash + npm start + ``` - ```bash - npm start - ``` +## Docker Setup (Alternative) - This starts the production server for your application. +For an easier setup, you can use Docker and Docker Compose: + +1. Make sure you have Docker and Docker Compose installed on your system. + +2. From the root directory, run: + + ```bash + docker-compose up -d + ``` + +3. Access the application: + - Frontend: http://localhost:5000 + - Backend API: http://localhost:3000 + - MinIO Console: http://localhost:9001 (login with minioadmin/minioadmin) + +4. To stop the services: + + ```bash + docker-compose down + ``` diff --git a/docker-compose.yml b/docker-compose.yml index d177d2d5..02dab3ee 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: frontend: - image: docker.io/kithceydigital/worklenz_frontend:latest + image: docker.io/chamikajaycey/worklenz-frontend:latest container_name: worklenz_frontend ports: - "5000:5000" @@ -11,7 +11,7 @@ services: - worklenz backend: - image: docker.io/kithceydigital/worklenz_backend:react + image: docker.io/chamikajaycey/worklenz-backend:latest container_name: worklenz_backend ports: - "3000:3000" @@ -19,7 +19,7 @@ services: db: condition: service_healthy minio: - condition: service_healthy + condition: service_started environment: - AWS_REGION=${AWS_REGION:-us-east-1} - BACKEND_PUBLIC_DIR @@ -65,12 +65,6 @@ services: volumes: - worklenz_minio_data:/data command: server /data --console-address ":9001" - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] - interval: 10s - timeout: 5s - retries: 3 - start_period: 10s networks: - worklenz @@ -79,14 +73,26 @@ services: image: minio/mc container_name: worklenz_createbuckets depends_on: - minio: - condition: service_healthy + - minio entrypoint: > /bin/sh -c " - /usr/bin/mc config host add myminio http://minio:9000 minioadmin minioadmin; - /usr/bin/mc mb --ignore-existing myminio/worklenz-bucket; - /usr/bin/mc policy set public myminio/worklenz-bucket; - exit 0; + # Wait for MinIO to be available + echo 'Waiting for MinIO to start...' + sleep 15; + # Retry up to 5 times + for i in 1 2 3 4 5; do + echo \"Attempt $$i to connect to MinIO...\" + if /usr/bin/mc config host add myminio http://minio:9000 minioadmin minioadmin; then + echo \"Successfully connected to MinIO!\" + /usr/bin/mc mb --ignore-existing myminio/worklenz-bucket; + /usr/bin/mc policy set public myminio/worklenz-bucket; + exit 0; + fi + echo \"Connection failed, retrying in 5 seconds...\" + sleep 5; + done + echo \"Failed to connect to MinIO after 5 attempts\" + exit 1; " networks: - worklenz diff --git a/start.bat b/start.bat index 63dd1022..78f6837a 100644 --- a/start.bat +++ b/start.bat @@ -1,4 +1,6 @@ @echo off +echo Starting Worklenz setup... > worklenz_startup.log +echo %DATE% %TIME% >> worklenz_startup.log echo. echo " __ __ _ _" echo " \ \ / / | | | |" @@ -12,58 +14,174 @@ echo. echo Starting Worklenz Docker Environment... echo. +REM Check for Docker installation +echo Checking for Docker installation... +where docker >nul 2>>worklenz_startup.log +IF %ERRORLEVEL% NEQ 0 ( + echo [91mWarning: Docker is not installed or not in PATH[0m + echo Warning: Docker is not installed or not in PATH >> worklenz_startup.log + echo Please install Docker first: https://docs.docker.com/get-docker/ + echo [93mContinuing for debugging purposes...[0m +) ELSE ( + echo [92m^✓[0m Docker is installed + echo Docker is installed >> worklenz_startup.log +) + +REM Check for docker-compose installation +echo Checking for docker-compose... +where docker-compose >nul 2>>worklenz_startup.log +IF %ERRORLEVEL% NEQ 0 ( + echo [91mWarning: docker-compose is not installed or not in PATH[0m + echo Warning: docker-compose is not installed or not in PATH >> worklenz_startup.log + echo [93mContinuing for debugging purposes...[0m +) ELSE ( + echo [92m^✓[0m docker-compose is installed + echo docker-compose is installed >> worklenz_startup.log +) + +REM Run preflight checks +echo Running Docker daemon check... +docker info >nul 2>>worklenz_startup.log +IF %ERRORLEVEL% NEQ 0 ( + echo [91mWarning: Docker daemon is not running[0m + echo Warning: Docker daemon is not running >> worklenz_startup.log + echo Please start Docker and try again + echo [93mContinuing for debugging purposes...[0m +) ELSE ( + echo [92m^✓[0m Docker daemon is running + echo Docker daemon is running >> worklenz_startup.log +) + REM Check if .env file exists IF NOT EXIST .env ( echo Warning: .env file not found. Using default configuration. + echo Warning: .env file not found. Using default configuration. >> worklenz_startup.log IF EXIST .env.example ( copy .env.example .env echo Created .env file from .env.example + echo Created .env file from .env.example >> worklenz_startup.log ) ) REM Stop any running containers -docker-compose down +echo Stopping any running containers... +docker-compose down > nul 2>>worklenz_startup.log +IF %ERRORLEVEL% NEQ 0 ( + echo [91mWarning: Error stopping containers[0m + echo Warning: Error stopping containers >> worklenz_startup.log + echo [93mContinuing anyway...[0m +) REM Start the containers -docker-compose up -d +echo Starting containers... +echo Attempting to start containers... >> worklenz_startup.log + +REM Start with docker-compose +docker-compose up -d > docker_up_output.txt 2>&1 +type docker_up_output.txt >> worklenz_startup.log + +REM Check for errors in output +findstr /C:"Error" docker_up_output.txt > nul +IF %ERRORLEVEL% EQU 0 ( + echo [91mErrors detected during startup[0m + echo Errors detected during startup >> worklenz_startup.log + type docker_up_output.txt +) + +del docker_up_output.txt > nul 2>&1 REM Wait for services to be ready echo Waiting for services to start... -timeout /t 5 /nobreak > nul +timeout /t 10 /nobreak > nul +echo After timeout, checking services >> worklenz_startup.log -REM Check if services are running -docker ps | findstr "worklenz_frontend" > nul +REM Check service status using docker-compose +echo Checking service status... +echo Checking service status... >> worklenz_startup.log +docker-compose ps --services --filter "status=running" > running_services.txt 2>>worklenz_startup.log + +REM Log services output +type running_services.txt >> worklenz_startup.log + +echo. +echo Checking individual services: +echo Checking individual services: >> worklenz_startup.log + +REM Check frontend +findstr /C:"frontend" running_services.txt > nul IF %ERRORLEVEL% EQU 0 ( echo [92m^✓[0m Frontend is running echo Frontend URL: http://localhost:5000 + echo Frontend is running >> worklenz_startup.log ) ELSE ( echo [91m^✗[0m Frontend service failed to start + echo Frontend service failed to start >> worklenz_startup.log ) -docker ps | findstr "worklenz_backend" > nul +REM Check backend +findstr /C:"backend" running_services.txt > nul IF %ERRORLEVEL% EQU 0 ( echo [92m^✓[0m Backend is running echo Backend URL: http://localhost:3000 + echo Backend is running >> worklenz_startup.log ) ELSE ( echo [91m^✗[0m Backend service failed to start + echo Backend service failed to start >> worklenz_startup.log ) -docker ps | findstr "worklenz_minio" > nul +REM Check MinIO +findstr /C:"minio" running_services.txt > nul IF %ERRORLEVEL% EQU 0 ( echo [92m^✓[0m MinIO is running echo MinIO Console URL: http://localhost:9001 (login: minioadmin/minioadmin) + echo MinIO is running >> worklenz_startup.log ) ELSE ( echo [91m^✗[0m MinIO service failed to start + echo MinIO service failed to start >> worklenz_startup.log + + REM Check MinIO logs + echo Checking MinIO logs for errors: + docker-compose logs minio --tail=20 > minio_logs.txt + type minio_logs.txt + type minio_logs.txt >> worklenz_startup.log + del minio_logs.txt > nul 2>&1 ) -docker ps | findstr "worklenz_db" > nul +REM Check Database +findstr /C:"db" running_services.txt > nul IF %ERRORLEVEL% EQU 0 ( echo [92m^✓[0m Database is running + echo Database is running >> worklenz_startup.log ) ELSE ( echo [91m^✗[0m Database service failed to start + echo Database service failed to start >> worklenz_startup.log +) + +del running_services.txt > nul 2>&1 + +REM Check if all services are running +set allRunning=1 +docker-compose ps --services | findstr /V /C:"frontend" /C:"backend" /C:"minio" /C:"db" > remaining_services.txt +FOR /F "tokens=*" %%s IN (remaining_services.txt) DO ( + findstr /C:"%%s" running_services.txt > nul || set allRunning=0 +) +del remaining_services.txt > nul 2>&1 + +IF %allRunning% EQU 1 ( + echo. + echo [92mWorklenz setup completed![0m + echo Setup completed successfully >> worklenz_startup.log +) ELSE ( + echo. + echo [93mWarning: Some services may not be running correctly.[0m + echo Warning: Some services may not be running correctly >> worklenz_startup.log + echo Run 'docker-compose logs' to check for errors. ) -echo. -echo [92mWorklenz is now running![0m echo You can access the application at: http://localhost:5000 -echo To stop the services, run: stop.bat \ No newline at end of file +echo To stop the services, run: stop.bat +echo. +echo For any errors, check worklenz_startup.log file +echo. +echo Press any key to exit... +pause > nul \ No newline at end of file diff --git a/start.sh b/start.sh index 5720cf61..34b0f649 100644 --- a/start.sh +++ b/start.sh @@ -3,6 +3,7 @@ # Colors for terminal output GREEN='\033[0;32m' YELLOW='\033[1;33m' +RED='\033[0;31m' NC='\033[0m' # No Color # Print banner @@ -30,11 +31,20 @@ fi # Check if Docker is installed if ! command -v docker &> /dev/null; then - echo "Error: Docker is not installed or not in PATH" + echo -e "${RED}Error: Docker is not installed or not in PATH${NC}" echo "Please install Docker first: https://docs.docker.com/get-docker/" exit 1 fi +# Check if Docker daemon is running +echo "Running preflight checks..." +if ! docker info &> /dev/null; then + echo -e "${RED}Error: Docker daemon is not running${NC}" + echo "Please start Docker and try again" + exit 1 +fi +echo -e "${GREEN}✓${NC} Docker is running" + # Check if Docker Compose is installed if ! command -v docker compose &> /dev/null; then echo "Warning: Docker Compose V2 not found, trying docker-compose command..." diff --git a/worklenz-backend/.env.example b/worklenz-backend/.env.example index 9ed46239..281e8514 100644 --- a/worklenz-backend/.env.example +++ b/worklenz-backend/.env.example @@ -2,66 +2,75 @@ NODE_ENV=development PORT=3000 SESSION_NAME=worklenz.sid -SESSION_SECRET="your-session-secret" -COOKIE_SECRET="your-cookie-secret" +SESSION_SECRET="your_session_secret" +COOKIE_SECRET="your_cookie_secret" # CORS -SOCKET_IO_CORS=http://localhost:4200 +SOCKET_IO_CORS=http://localhost:5000 SERVER_CORS=* # Database DB_USER=postgres -DB_PASSWORD=password +DB_PASSWORD=your_db_password DB_NAME=worklenz_db DB_HOST=localhost DB_PORT=5432 DB_MAX_CLIENTS=50 # Google Login -GOOGLE_CLIENT_ID="client_id" -GOOGLE_CLIENT_SECRET="client_secret" +GOOGLE_CLIENT_ID="your_google_client_id" +GOOGLE_CLIENT_SECRET="your_google_client_secret" GOOGLE_CALLBACK_URL="http://localhost:3000/secure/google/verify" LOGIN_FAILURE_REDIRECT="/" -LOGIN_SUCCESS_REDIRECT="http://localhost:4200/auth/authenticate" +LOGIN_SUCCESS_REDIRECT="http://localhost:5000/auth/authenticate" -# SENDGRID -SENDGRID_API_KEY="your-sendgrid-api-key" -EMAIL_NOTIFICATIONS=your-email@example.com +# CLI +ANGULAR_DIST_DIR="path/to/frontend/dist" +ANGULAR_SRC_DIR="path/to/frontend" +BACKEND_PUBLIC_DIR="path/to/backend/public" +BACKEND_VIEWS_DIR="path/to/backend/views" +COMMIT_BUILD_IMMEDIATELY=false # HOST -HOSTNAME=localhost:4200 +HOSTNAME=localhost:5000 -SLACK_WEBHOOK=your-slack-webhook-url -USE_PG_NATIVE=true +# SLACK +SLACK_WEBHOOK=your_slack_webhook_url +USE_PG_NATIVE=false # JWT SECRET -JWT_SECRET=your-jwt-secret +JWT_SECRET=your_jwt_secret # FRONTEND_URL -FRONTEND_URL=https://example.com/ - -# AWS -AWS_REGION="us-west-2" -AWS_ACCESS_KEY_ID="YOUR_AWS_ACCESS_KEY_ID" -AWS_SECRET_ACCESS_KEY="YOUR_AWS_SECRET_ACCESS_KEY" -AWS_BUCKET="your-s3-bucket" -S3_URL="https://s3.your-region.amazonaws.com/your-bucket" +FRONTEND_URL=http://localhost:5000 # STORAGE -STORAGE_PROVIDER=s3 # s3 or azure -AZURE_STORAGE_ACCOUNT_NAME=yourstorageaccount -AZURE_STORAGE_CONTAINER=yourcontainer -AZURE_STORAGE_ACCOUNT_KEY=yourstorageaccountkey -AZURE_STORAGE_URL=https://yourstorageaccount.blob.core.windows.net +STORAGE_PROVIDER=s3 # values s3 or azure + +# AWS +AWS_REGION="your_aws_region" +AWS_ACCESS_KEY_ID="your_aws_access_key_id" +AWS_SECRET_ACCESS_KEY="your_aws_secret_access_key" +AWS_BUCKET="your_s3_bucket" +S3_URL="your_s3_url" + +# Azure Storage +AZURE_STORAGE_ACCOUNT_NAME="your_storage_account_name" +AZURE_STORAGE_CONTAINER="your_storage_container" +AZURE_STORAGE_ACCOUNT_KEY="your_storage_account_key" +AZURE_STORAGE_URL="your_storage_url" # DIRECTPAY DP_STAGE=DEV -DP_URL=https://dev.directpay.lk/v1/mpg/api/external/cardManagement -DP_MERCHANT_ID=YOUR_MERCHANT_ID -DP_SECRET_KEY=YOUR_SECRET_KEY -DP_API_KEY=YOUR_API_KEY +DP_URL=your_url +DP_MERCHANT_ID=your_merchant_id +DP_SECRET_KEY=your_secret_key +DP_API_KEY=your_api_key CONTACT_US_EMAIL=support@example.com -GOOGLE_CAPTCHA_SECRET_KEY=YOUR_SECRET_KEY -GOOGLE_CAPTCHA_PASS_SCORE=0.8 \ No newline at end of file +GOOGLE_CAPTCHA_SECRET_KEY=your_captcha_secret_key +GOOGLE_CAPTCHA_PASS_SCORE=0.8 + +# Email Cronjobs +ENABLE_EMAIL_CRONJOBS=true \ No newline at end of file diff --git a/worklenz-backend/.env.template b/worklenz-backend/.env.template index 18aa2d3f..281e8514 100644 --- a/worklenz-backend/.env.template +++ b/worklenz-backend/.env.template @@ -2,74 +2,75 @@ NODE_ENV=development PORT=3000 SESSION_NAME=worklenz.sid -SESSION_SECRET="YOUR_SESSION_SECRET_HERE" -COOKIE_SECRET="YOUR_COOKIE_SECRET_HERE" +SESSION_SECRET="your_session_secret" +COOKIE_SECRET="your_cookie_secret" # CORS -SOCKET_IO_CORS=http://localhost:4200 +SOCKET_IO_CORS=http://localhost:5000 SERVER_CORS=* # Database -DB_USER=DATABASE_USER_HERE # default : worklenz_backend (update "user-permission.sql" if needed) -DB_PASSWORD=DATABASE_PASSWORD_HERE -DB_NAME=DATABASE_NAME_HERE # default : worklenz_db -DB_HOST=DATABASE_HOST_HERE # default : localhost -DB_PORT=DATABASE_PORT_HERE # default : 5432 +DB_USER=postgres +DB_PASSWORD=your_db_password +DB_NAME=worklenz_db +DB_HOST=localhost +DB_PORT=5432 DB_MAX_CLIENTS=50 # Google Login -GOOGLE_CLIENT_ID="GOOGLE_CLIENT_ID_HERE" -GOOGLE_CLIENT_SECRET="GOOGLE_CLIENT_SECRET_HERE" +GOOGLE_CLIENT_ID="your_google_client_id" +GOOGLE_CLIENT_SECRET="your_google_client_secret" GOOGLE_CALLBACK_URL="http://localhost:3000/secure/google/verify" LOGIN_FAILURE_REDIRECT="/" -LOGIN_SUCCESS_REDIRECT="http://localhost:4200/auth/authenticate" +LOGIN_SUCCESS_REDIRECT="http://localhost:5000/auth/authenticate" # CLI -ANGULAR_DIST_DIR="/path/worklenz_frontend/dist/worklenz" -ANGULAR_SRC_DIR="/path/worklenz_frontend" -BACKEND_PUBLIC_DIR="/path/worklenz_backend/src/public" -BACKEND_VIEWS_DIR="/path/worklenz_backend/src/views/admin" -COMMIT_BUILD_IMMEDIATELY=true +ANGULAR_DIST_DIR="path/to/frontend/dist" +ANGULAR_SRC_DIR="path/to/frontend" +BACKEND_PUBLIC_DIR="path/to/backend/public" +BACKEND_VIEWS_DIR="path/to/backend/views" +COMMIT_BUILD_IMMEDIATELY=false # HOST -HOSTNAME=localhost:4200 +HOSTNAME=localhost:5000 # SLACK -SLACK_WEBHOOK=SLACK_WEBHOOK_HERE +SLACK_WEBHOOK=your_slack_webhook_url USE_PG_NATIVE=false # JWT SECRET -JWT_SECRET=JWT_SECRET_HERE +JWT_SECRET=your_jwt_secret # FRONTEND_URL -FRONTEND_URL=FRONTEND_URL_HERE +FRONTEND_URL=http://localhost:5000 # STORAGE -STORAGE_PROVIDER=STORAGE_PROVIDER_HERE # values s3 or azure, if s3 is selected, then the following AWS credentials are required. if azure is selected, then the following Azure credentials are required. +STORAGE_PROVIDER=s3 # values s3 or azure # AWS -AWS_REGION="AWS_REGION_HERE" -AWS_ACCESS_KEY_ID="AWS_ACCESS_KEY_ID_HERE" -AWS_SECRET_ACCESS_KEY="AWS_SECRET_ACCESS_KEY_HERE" -AWS_BUCKET="AWS_BUCKET_HERE" -S3_URL="S3_URL_HERE" +AWS_REGION="your_aws_region" +AWS_ACCESS_KEY_ID="your_aws_access_key_id" +AWS_SECRET_ACCESS_KEY="your_aws_secret_access_key" +AWS_BUCKET="your_s3_bucket" +S3_URL="your_s3_url" -# STORAGE -AZURE_STORAGE_ACCOUNT_NAME="AZURE_STORAGE_ACCOUNT_NAME_HERE" -AZURE_STORAGE_CONTAINER="AZURE_STORAGE_CONTAINER_HERE" -AZURE_STORAGE_ACCOUNT_KEY="AZURE_STORAGE_ACCOUNT_KEY_HERE" -AZURE_STORAGE_URL="AZURE_STORAGE_URL_HERE" +# Azure Storage +AZURE_STORAGE_ACCOUNT_NAME="your_storage_account_name" +AZURE_STORAGE_CONTAINER="your_storage_container" +AZURE_STORAGE_ACCOUNT_KEY="your_storage_account_key" +AZURE_STORAGE_URL="your_storage_url" # DIRECTPAY -DP_STAGE=DP_STAGE_HERE #DEV or -DP_URL=DP_URL_HERE -DP_MERCHANT_ID=DP_MERCHANT_ID_HERE -DP_SECRET_KEY=DP_SECRET_KEY_HERE -DP_API_KEY=DP_API_KEY_HERE +DP_STAGE=DEV +DP_URL=your_url +DP_MERCHANT_ID=your_merchant_id +DP_SECRET_KEY=your_secret_key +DP_API_KEY=your_api_key -CONTACT_US_EMAIL=CONTACT_US_EMAIL_HERE +CONTACT_US_EMAIL=support@example.com -GOOGLE_CAPTCHA_SECRET_KEY=GOOGLE_CAPTCHA_SECRET_KEY_HERE +GOOGLE_CAPTCHA_SECRET_KEY=your_captcha_secret_key +GOOGLE_CAPTCHA_PASS_SCORE=0.8 # Email Cronjobs ENABLE_EMAIL_CRONJOBS=true \ No newline at end of file diff --git a/worklenz-backend/Dockerfile b/worklenz-backend/Dockerfile index f0815533..f381e37e 100644 --- a/worklenz-backend/Dockerfile +++ b/worklenz-backend/Dockerfile @@ -1,5 +1,5 @@ -# Use the official Node.js 18 image as a base -FROM node:18 +# Use the official Node.js 20 image as a base +FROM node:20 # Create and set the working directory WORKDIR /usr/src/app diff --git a/worklenz-backend/README.md b/worklenz-backend/README.md index 25590300..96c6c5d5 100644 --- a/worklenz-backend/README.md +++ b/worklenz-backend/README.md @@ -1,81 +1,96 @@ # Worklenz Backend -1. **Open your IDE:** +This is the Express.js backend for the Worklenz project management application. - Open the project directory in your preferred code editor or IDE like Visual Studio Code. +## Getting Started -2. **Configure Environment Variables:** +Follow these steps to set up the backend for development: - - Create a copy of the `.env.template` file and name it `.env`. - - Update the required fields in `.env` with the specific information. +1. **Configure Environment Variables:** -3. **Restore Database** - - Create a new database named `worklenz_db` on your local PostgreSQL server. - - Update the `DATABASE_NAME` and `PASSWORD` in the `database/6_user_permission.sql` with your DB credentials. - - Open a query console and execute the queries from the .sql files in the `database` directories, following the provided order. + - Create a copy of the `.env.example` file and name it `.env`. + - Update the required fields in `.env` with your specific configuration. -4. **Install Dependencies:** +2. **Set up Database:** + - Create a new database named `worklenz_db` on your local PostgreSQL server. + - Update the database connection details in your `.env` file. + - Execute the SQL setup files in the correct order: + + ```bash + # From your PostgreSQL client or command line + psql -U your_username -d worklenz_db -f database/sql/0_extensions.sql + psql -U your_username -d worklenz_db -f database/sql/1_tables.sql + psql -U your_username -d worklenz_db -f database/sql/indexes.sql + psql -U your_username -d worklenz_db -f database/sql/4_functions.sql + psql -U your_username -d worklenz_db -f database/sql/triggers.sql + psql -U your_username -d worklenz_db -f database/sql/3_views.sql + psql -U your_username -d worklenz_db -f database/sql/2_dml.sql + psql -U your_username -d worklenz_db -f database/sql/5_database_user.sql + ``` + + Alternatively, you can use the provided shell script: + + ```bash + # Make sure the script is executable + chmod +x database/00-init-db.sh + # Run the script (may need modifications for local execution) + ./database/00-init-db.sh + ``` + +3. **Install Dependencies:** ```bash npm install ``` - This command installs all the necessary libraries required to run the project. +4. **Run the Development Server:** -5. **Run the Development Server:** + ```bash + npm run dev + ``` - **a. Start the TypeScript compiler:** + This starts the development server with hot reloading enabled. - Open a new terminal window and run the following command: +5. **Build for Production:** - ```bash - grunt dev - ``` + ```bash + npm run build + ``` - This starts the `grunt` task runner, which compiles TypeScript code into JavaScript. + This will compile the TypeScript code into JavaScript for production use. - **b. Start the development server:** +6. **Start Production Server:** - Open another separate terminal window and run the following command: + ```bash + npm start + ``` - ```bash - npm start - ``` +## API Documentation - This starts the development server allowing you to work on the project. +The API endpoints are organized into logical controllers and follow RESTful design principles. The main API routes are prefixed with `/api/v1`. -6. **Run the Production Server:** +### Authentication - **a. Compile TypeScript to JavaScript:** +Authentication is handled via JWT tokens. Protected routes require a valid token in the Authorization header. - Open a new terminal window and run the following command: +### File Storage - ```bash - grunt build - ``` +The application supports both S3-compatible storage and Azure Blob Storage for file uploads. Configure your preferred storage option in the `.env` file. - This starts the `grunt` task runner, which compiles TypeScript code into JavaScript for production use. +## Development Guidelines - **b. Start the production server:** +- Code should be written in TypeScript +- Follow the established patterns for controllers, services, and middlewares +- Add proper error handling for all API endpoints +- Write unit tests for critical functionality +- Document API endpoints with clear descriptions and examples - Once the compilation is complete, run the following command in the same terminal window: +## Running Tests - ```bash - npm start - ``` +```bash +npm test +``` - This starts the production server for your application. +## Docker Support -### CLI - -- Create controller: `$ node new controller Test` -- Create angular release: `$ node new release` - -### Developement Rules - -- Controllers should only generate/create using the CLI (`node new controller Projects`) -- Validations should only be done using a middleware placed under src/validators/ and used inside the routers (E.g., api-router.ts) -- Validators should only generate/create using the CLI (`node new vaidator projects-params`) - -## Pull submodules -- git submodule update --init --recursive +The backend can be run in a Docker container. See the main project README for Docker setup instructions. diff --git a/worklenz-backend/database/00-init-db.sh b/worklenz-backend/database/00-init-db.sh new file mode 100644 index 00000000..9743d435 --- /dev/null +++ b/worklenz-backend/database/00-init-db.sh @@ -0,0 +1,55 @@ +#!/bin/bash +set -e + +# This script controls the order of SQL file execution during database initialization +echo "Starting database initialization..." + +# Check if we have SQL files in expected locations +if [ -f "/docker-entrypoint-initdb.d/sql/0_extensions.sql" ]; then + SQL_DIR="/docker-entrypoint-initdb.d/sql" + echo "Using SQL files from sql/ subdirectory" +elif [ -f "/docker-entrypoint-initdb.d/0_extensions.sql" ]; then + # First time setup - move files to subdirectory + echo "Moving SQL files to sql/ subdirectory..." + mkdir -p /docker-entrypoint-initdb.d/sql + + # Move all SQL files (except this script) to the subdirectory + for f in /docker-entrypoint-initdb.d/*.sql; do + if [ -f "$f" ]; then + cp "$f" /docker-entrypoint-initdb.d/sql/ + echo "Copied $f to sql/ subdirectory" + fi + done + + SQL_DIR="/docker-entrypoint-initdb.d/sql" +else + echo "SQL files not found in expected locations!" + exit 1 +fi + +# Execute SQL files in the correct order +echo "Executing 0_extensions.sql..." +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -f "$SQL_DIR/0_extensions.sql" + +echo "Executing 1_tables.sql..." +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -f "$SQL_DIR/1_tables.sql" + +echo "Executing indexes.sql..." +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -f "$SQL_DIR/indexes.sql" + +echo "Executing 4_functions.sql..." +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -f "$SQL_DIR/4_functions.sql" + +echo "Executing triggers.sql..." +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -f "$SQL_DIR/triggers.sql" + +echo "Executing 3_views.sql..." +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -f "$SQL_DIR/3_views.sql" + +echo "Executing 2_dml.sql..." +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -f "$SQL_DIR/2_dml.sql" + +echo "Executing 5_database_user.sql..." +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -f "$SQL_DIR/5_database_user.sql" + +echo "Database initialization completed successfully" \ No newline at end of file diff --git a/worklenz-backend/database/README.md b/worklenz-backend/database/README.md index f616d601..26200757 100644 --- a/worklenz-backend/database/README.md +++ b/worklenz-backend/database/README.md @@ -1 +1,36 @@ All database DDLs, DMLs and migrations relates to the application should be stored here as well. + +# Worklenz Database + +## Directory Structure + +- `sql/` - Contains all SQL files needed for database initialization +- `migrations/` - Contains database migration scripts +- `00-init-db.sh` - Initialization script that executes SQL files in the correct order + +## SQL File Execution Order + +The database initialization files should be executed in the following order: + +1. `sql/0_extensions.sql` - PostgreSQL extensions +2. `sql/1_tables.sql` - Table definitions and constraints +3. `sql/indexes.sql` - All database indexes +4. `sql/4_functions.sql` - Database functions +5. `sql/triggers.sql` - Database triggers +6. `sql/3_views.sql` - Database views +7. `sql/2_dml.sql` - Data Manipulation Language statements (inserts, updates) +8. `sql/5_database_user.sql` - Database user setup + +## Docker-based Setup + +In the Docker environment, we use a shell script called `00-init-db.sh` to control the SQL file execution order: + +1. The shell script creates a `sql/` subdirectory if it doesn't exist +2. It copies all .sql files into this subdirectory +3. It executes the SQL files from the subdirectory in the correct order + +This approach prevents the SQL files from being executed twice by Docker's automatic initialization mechanism, which would cause errors for objects that already exist. + +## Manual Setup + +If you're setting up the database manually, please follow the execution order listed above. Ensure your SQL files are in the `sql/` subdirectory before executing the script. diff --git a/worklenz-backend/database/sql/0_extensions.sql b/worklenz-backend/database/sql/0_extensions.sql new file mode 100644 index 00000000..b959a894 --- /dev/null +++ b/worklenz-backend/database/sql/0_extensions.sql @@ -0,0 +1,3 @@ +-- Extensions +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE EXTENSION IF NOT EXISTS "unaccent"; \ No newline at end of file diff --git a/worklenz-backend/database/1_tables.sql b/worklenz-backend/database/sql/1_tables.sql similarity index 97% rename from worklenz-backend/database/1_tables.sql rename to worklenz-backend/database/sql/1_tables.sql index 07ac36d6..ad6ec1f4 100644 --- a/worklenz-backend/database/1_tables.sql +++ b/worklenz-backend/database/sql/1_tables.sql @@ -1,7 +1,3 @@ --- Extensions -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -CREATE EXTENSION IF NOT EXISTS "unaccent"; - -- Domains CREATE DOMAIN WL_HEX_COLOR AS TEXT CHECK (value ~* '^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$'); CREATE DOMAIN WL_EMAIL AS TEXT CHECK (value ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'); @@ -18,7 +14,27 @@ CREATE TYPE SCHEDULE_TYPE AS ENUM ('daily', 'weekly', 'yearly', 'monthly', 'ever CREATE TYPE LANGUAGE_TYPE AS ENUM ('en', 'es', 'pt'); +-- START: Users +CREATE SEQUENCE IF NOT EXISTS users_user_no_seq START 1; + -- Utility and referenced tables +-- Create sessions table for connect-pg-simple session store +CREATE TABLE IF NOT EXISTS pg_sessions ( + sid VARCHAR NOT NULL PRIMARY KEY, + sess JSON NOT NULL, + expire TIMESTAMP(6) NOT NULL +); + +CREATE TABLE IF NOT EXISTS project_access_levels ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + name TEXT NOT NULL, + key TEXT NOT NULL +); + +ALTER TABLE project_access_levels + ADD CONSTRAINT project_access_levels_pk + PRIMARY KEY (id); + CREATE TABLE IF NOT EXISTS countries ( id UUID DEFAULT uuid_generate_v4() NOT NULL, code CHAR(2) NOT NULL, @@ -40,7 +56,6 @@ ALTER TABLE permissions ADD CONSTRAINT permissions_pk PRIMARY KEY (id); --- Tables that reference utility tables CREATE TABLE IF NOT EXISTS archived_projects ( user_id UUID NOT NULL, project_id UUID NOT NULL @@ -77,7 +92,6 @@ ALTER TABLE clients ADD CONSTRAINT clients_name_check CHECK (CHAR_LENGTH(name) <= 60); --- Remaining tables CREATE TABLE IF NOT EXISTS cpt_phases ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, @@ -232,11 +246,6 @@ ALTER TABLE email_invitations ADD CONSTRAINT email_invitations_pk PRIMARY KEY (id); -CREATE TRIGGER email_invitations_email_lower - BEFORE INSERT OR UPDATE - ON email_invitations -EXECUTE PROCEDURE lower_email(); - CREATE TABLE IF NOT EXISTS favorite_projects ( user_id UUID NOT NULL, project_id UUID NOT NULL @@ -260,6 +269,35 @@ ALTER TABLE job_titles ADD CONSTRAINT job_titles_name_check CHECK (CHAR_LENGTH(name) <= 55); +CREATE TABLE IF NOT EXISTS licensing_admin_users ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + name TEXT NOT NULL, + username TEXT NOT NULL, + phone_no TEXT NOT NULL, + otp TEXT, + otp_expiry TIMESTAMP WITH TIME ZONE, + active BOOLEAN DEFAULT TRUE NOT NULL +); + +ALTER TABLE licensing_admin_users + ADD CONSTRAINT licensing_admin_users_id_pk + PRIMARY KEY (id); + +CREATE TABLE IF NOT EXISTS licensing_app_sumo_batches ( + id UUID DEFAULT uuid_generate_v4() NOT NULL, + name TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + created_by UUID NOT NULL +); + +ALTER TABLE licensing_app_sumo_batches + ADD CONSTRAINT licensing_app_sumo_batches_pk + PRIMARY KEY (id); + +ALTER TABLE licensing_app_sumo_batches + ADD CONSTRAINT licensing_app_sumo_batches_created_by_fk + FOREIGN KEY (created_by) REFERENCES licensing_admin_users; + CREATE TABLE IF NOT EXISTS licensing_coupon_codes ( id UUID DEFAULT uuid_generate_v4() NOT NULL, coupon_code TEXT NOT NULL, @@ -283,11 +321,6 @@ ALTER TABLE licensing_coupon_codes ADD CONSTRAINT licensing_coupon_codes_pk PRIMARY KEY (id); -ALTER TABLE licensing_coupon_codes - ADD CONSTRAINT licensing_coupon_codes_app_sumo_batches__fk - FOREIGN KEY (batch_id) REFERENCES licensing_app_sumo_batches - ON DELETE CASCADE; - ALTER TABLE licensing_coupon_codes ADD CONSTRAINT licensing_coupon_codes_created_by_fk FOREIGN KEY (created_by) REFERENCES licensing_admin_users; @@ -1466,33 +1499,6 @@ ALTER TABLE tasks ADD CONSTRAINT tasks_total_minutes_check CHECK ((total_minutes >= (0)::NUMERIC) AND (total_minutes <= (999999)::NUMERIC)); -CREATE TRIGGER projects_tasks_counter_trigger - BEFORE INSERT - ON tasks - FOR EACH ROW -EXECUTE PROCEDURE update_project_tasks_counter_trigger_fn(); - -CREATE TRIGGER set_task_updated_at - BEFORE UPDATE - ON tasks - FOR EACH ROW -EXECUTE PROCEDURE set_task_updated_at_trigger_fn(); - -CREATE TRIGGER tasks_status_id_change - AFTER UPDATE - OF status_id - ON tasks - FOR EACH ROW -EXECUTE PROCEDURE task_status_change_trigger_fn(); - -CREATE TRIGGER tasks_task_subscriber_notify_done - BEFORE UPDATE - OF status_id - ON tasks - FOR EACH ROW - WHEN (old.status_id IS DISTINCT FROM new.status_id) -EXECUTE PROCEDURE tasks_task_subscriber_notify_done_trigger(); - CREATE TABLE IF NOT EXISTS tasks_assignees ( task_id UUID NOT NULL, project_member_id UUID NOT NULL, @@ -1579,18 +1585,6 @@ ALTER TABLE team_members ADD CONSTRAINT team_members_role_id_fk FOREIGN KEY (role_id) REFERENCES roles; -CREATE TRIGGER insert_notification_settings - AFTER INSERT - ON team_members - FOR EACH ROW -EXECUTE PROCEDURE notification_settings_insert_trigger_fn(); - -CREATE TRIGGER remove_notification_settings - BEFORE DELETE - ON team_members - FOR EACH ROW -EXECUTE PROCEDURE notification_settings_delete_trigger_fn(); - CREATE TABLE IF NOT EXISTS users ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, @@ -1640,18 +1634,10 @@ ALTER TABLE licensing_payment_details ADD CONSTRAINT licensing_payment_details_users_id_fk FOREIGN KEY (user_id) REFERENCES users; -ALTER TABLE licensing_user_payment_methods - ADD CONSTRAINT licensing_user_payment_methods_users_id_fk - FOREIGN KEY (user_id) REFERENCES users; - ALTER TABLE licensing_user_subscriptions ADD CONSTRAINT licensing_user_subscriptions_users_id_fk FOREIGN KEY (user_id) REFERENCES users; -ALTER TABLE licensing_user_subscriptions_log - ADD CONSTRAINT licensing_user_subscriptions_log_users_id_fk - FOREIGN KEY (user_id) REFERENCES users; - ALTER TABLE notification_settings ADD CONSTRAINT notification_settings_user_id_fk FOREIGN KEY (user_id) REFERENCES users @@ -1751,11 +1737,6 @@ ALTER TABLE users ADD CONSTRAINT users_name_check CHECK (CHAR_LENGTH(name) <= 55); -CREATE TRIGGER users_email_lower - BEFORE INSERT OR UPDATE - ON users -EXECUTE PROCEDURE lower_email(); - CREATE TABLE IF NOT EXISTS teams ( id UUID DEFAULT uuid_generate_v4() NOT NULL, name TEXT NOT NULL, diff --git a/worklenz-backend/database/2_dml.sql b/worklenz-backend/database/sql/2_dml.sql similarity index 99% rename from worklenz-backend/database/2_dml.sql rename to worklenz-backend/database/sql/2_dml.sql index c2d51b73..f2fa4d23 100644 --- a/worklenz-backend/database/2_dml.sql +++ b/worklenz-backend/database/sql/2_dml.sql @@ -388,14 +388,14 @@ SELECT sys_insert_project_access_levels(); SELECT sys_insert_task_status_categories(); SELECT sys_insert_project_statuses(); SELECT sys_insert_project_healths(); -SELECT sys_insert_project_templates(); +-- SELECT sys_insert_project_templates(); DROP FUNCTION sys_insert_task_priorities(); DROP FUNCTION sys_insert_project_access_levels(); DROP FUNCTION sys_insert_task_status_categories(); DROP FUNCTION sys_insert_project_statuses(); DROP FUNCTION sys_insert_project_healths(); -DROP FUNCTION sys_insert_project_templates(); +-- DROP FUNCTION sys_insert_project_templates(); INSERT INTO timezones (name, abbrev, utc_offset) SELECT name, abbrev, utc_offset diff --git a/worklenz-backend/database/3_views.sql b/worklenz-backend/database/sql/3_views.sql similarity index 100% rename from worklenz-backend/database/3_views.sql rename to worklenz-backend/database/sql/3_views.sql diff --git a/worklenz-backend/database/4_functions.sql b/worklenz-backend/database/sql/4_functions.sql similarity index 100% rename from worklenz-backend/database/4_functions.sql rename to worklenz-backend/database/sql/4_functions.sql diff --git a/worklenz-backend/database/5_database_user.sql b/worklenz-backend/database/sql/5_database_user.sql similarity index 100% rename from worklenz-backend/database/5_database_user.sql rename to worklenz-backend/database/sql/5_database_user.sql diff --git a/worklenz-backend/database/indexes.sql b/worklenz-backend/database/sql/indexes.sql similarity index 85% rename from worklenz-backend/database/indexes.sql rename to worklenz-backend/database/sql/indexes.sql index 3c44dd41..b9df6ab1 100644 --- a/worklenz-backend/database/indexes.sql +++ b/worklenz-backend/database/sql/indexes.sql @@ -26,12 +26,25 @@ CREATE UNIQUE INDEX IF NOT EXISTS cpt_task_statuses_template_id_name_uindex CREATE UNIQUE INDEX IF NOT EXISTS custom_project_templates_name_team_id_uindex ON custom_project_templates (name, team_id); +-- Create index on expire field +CREATE INDEX IF NOT EXISTS idx_pg_sessions_expire + ON pg_sessions (expire); + CREATE UNIQUE INDEX IF NOT EXISTS job_titles_name_team_id_uindex ON job_titles (name, team_id); CREATE INDEX IF NOT EXISTS job_titles_team_id_index ON job_titles (team_id); +CREATE UNIQUE INDEX IF NOT EXISTS licensing_admin_users_name_uindex + ON licensing_admin_users (name); + +CREATE UNIQUE INDEX IF NOT EXISTS licensing_admin_users_phone_no_uindex + ON licensing_admin_users (phone_no); + +CREATE UNIQUE INDEX IF NOT EXISTS licensing_admin_users_username_uindex + ON licensing_admin_users (username); + CREATE UNIQUE INDEX IF NOT EXISTS licensing_coupon_codes_coupon_code_uindex ON licensing_coupon_codes (coupon_code); @@ -53,6 +66,12 @@ CREATE INDEX IF NOT EXISTS notification_settings_team_user_id_index CREATE UNIQUE INDEX IF NOT EXISTS personal_todo_list_index_uindex ON personal_todo_list (user_id, index); +CREATE UNIQUE INDEX IF NOT EXISTS project_access_levels_key_uindex + ON project_access_levels (key); + +CREATE UNIQUE INDEX IF NOT EXISTS project_access_levels_name_uindex + ON project_access_levels (name); + CREATE UNIQUE INDEX IF NOT EXISTS project_categories_name_team_id_uindex ON project_categories (name, team_id); diff --git a/worklenz-backend/database/text_length_checks.sql b/worklenz-backend/database/sql/text_length_checks.sql similarity index 100% rename from worklenz-backend/database/text_length_checks.sql rename to worklenz-backend/database/sql/text_length_checks.sql diff --git a/worklenz-backend/database/triggers.sql b/worklenz-backend/database/sql/triggers.sql similarity index 100% rename from worklenz-backend/database/triggers.sql rename to worklenz-backend/database/sql/triggers.sql diff --git a/worklenz-backend/database/truncate.sql b/worklenz-backend/database/sql/truncate.sql similarity index 100% rename from worklenz-backend/database/truncate.sql rename to worklenz-backend/database/sql/truncate.sql diff --git a/worklenz-backend/package-lock.json b/worklenz-backend/package-lock.json index bab8daf0..2953defa 100644 --- a/worklenz-backend/package-lock.json +++ b/worklenz-backend/package-lock.json @@ -113,6 +113,7 @@ "grunt-contrib-watch": "^1.1.0", "grunt-shell": "^4.0.0", "grunt-sync": "^0.8.2", + "highcharts": "^11.1.0", "jest": "^28.1.3", "jest-sonar-reporter": "^2.0.0", "ncp": "^2.0.0", @@ -10138,6 +10139,13 @@ "node": ">=14.0.0" } }, + "node_modules/highcharts": { + "version": "11.4.8", + "resolved": "https://registry.npmjs.org/highcharts/-/highcharts-11.4.8.tgz", + "integrity": "sha512-5Tke9LuzZszC4osaFisxLIcw7xgNGz4Sy3Jc9pRMV+ydm6sYqsPYdU8ELOgpzGNrbrRNDRBtveoR5xS3SzneEA==", + "dev": true, + "license": "https://www.highcharts.com/license" + }, "node_modules/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", diff --git a/worklenz-backend/package.json b/worklenz-backend/package.json index 27b21c88..f3faaaec 100644 --- a/worklenz-backend/package.json +++ b/worklenz-backend/package.json @@ -134,6 +134,7 @@ "grunt-contrib-watch": "^1.1.0", "grunt-shell": "^4.0.0", "grunt-sync": "^0.8.2", + "highcharts": "^11.1.0", "jest": "^28.1.3", "jest-sonar-reporter": "^2.0.0", "ncp": "^2.0.0", diff --git a/worklenz-backend/src/app.ts b/worklenz-backend/src/app.ts index a45fd12b..4fead2d9 100644 --- a/worklenz-backend/src/app.ts +++ b/worklenz-backend/src/app.ts @@ -54,15 +54,16 @@ app.use((_req: Request, res: Response, next: NextFunction) => { const allowedOrigins = [ isProduction() ? [ - `https://react.worklenz.com`, - `https://v2.worklenz.com`, - `https://dev.worklenz.com` + `http://localhost:5000`, + `http://127.0.0.1:5000` ] : [ "http://localhost:3000", "http://localhost:5173", "http://127.0.0.1:5173", - "http://127.0.0.1:3000" + "http://127.0.0.1:3000", + "http://127.0.0.1:5000", + `http://localhost:5000` ] ].flat(); @@ -71,7 +72,7 @@ app.use(cors({ if (!origin || allowedOrigins.includes(origin)) { callback(null, true); } else { - console.log("Blocked origin:", origin); + console.log("Blocked origin:", origin, process.env.NODE_ENV); callback(new Error("Not allowed by CORS")); } }, diff --git a/worklenz-backend/src/shared/constants.ts b/worklenz-backend/src/shared/constants.ts index 0383ab89..28fc60fc 100644 --- a/worklenz-backend/src/shared/constants.ts +++ b/worklenz-backend/src/shared/constants.ts @@ -118,10 +118,10 @@ export const DEFAULT_PAGE_SIZE = 20; // S3 Credentials export const REGION = process.env.AWS_REGION || "us-east-1"; -export const BUCKET = process.env.AWS_BUCKET || "worklenz-bucket"; -export const S3_URL = process.env.S3_URL || "http://minio:9000/worklenz-bucket"; -export const S3_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID || "minioadmin"; -export const S3_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY || "minioadmin"; +export const BUCKET = process.env.AWS_BUCKET || "your-bucket-name"; +export const S3_URL = process.env.S3_URL || "https://your-s3-url"; +export const S3_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID || ""; +export const S3_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY || ""; // Azure Blob Storage Credentials export const STORAGE_PROVIDER = process.env.STORAGE_PROVIDER || "s3"; @@ -150,9 +150,9 @@ export const TEAM_MEMBER_TREE_MAP_COLOR_ALPHA = "40"; // LICENSING SERVER URLS export const LOCAL_URL = "http://localhost:3001"; -export const UAT_SERVER_URL = "https://uat.admin.worklenz.com"; -export const DEV_SERVER_URL = "https://dev.admin.worklenz.com"; -export const PRODUCTION_SERVER_URL = "https://admin.worklenz.com"; +export const UAT_SERVER_URL = process.env.UAT_SERVER_URL || "https://your-uat-server-url"; +export const DEV_SERVER_URL = process.env.DEV_SERVER_URL || "https://your-dev-server-url"; +export const PRODUCTION_SERVER_URL = process.env.PRODUCTION_SERVER_URL || "https://your-production-server-url"; // *Sync with the client export const PASSWORD_POLICY = "Minimum of 8 characters, with upper and lowercase and a number and a symbol."; diff --git a/worklenz-frontend/.env.development b/worklenz-frontend/.env.development index b9b2f923..21605cdf 100644 --- a/worklenz-frontend/.env.development +++ b/worklenz-frontend/.env.development @@ -5,13 +5,14 @@ VITE_APP_TITLE=Worklenz VITE_APP_ENV=development # Mixpanel -VITE_MIXPANEL_TOKEN=bb330b6bd25db4a6c988da89046f4b80 +VITE_MIXPANEL_TOKEN=mixpanel-token # Recaptcha -VITE_RECAPTCHA_SITE_KEY=6LeUWjYqAAAAAFhi9Z8KPeiix3RRjxoZtJhLJZXb +VITE_ENABLE_RECAPTCHA=false +VITE_RECAPTCHA_SITE_KEY=recaptcha-site-key # Session ID -VITE_WORKLENZ_SESSION_ID=worklenz.sid +VITE_WORKLENZ_SESSION_ID=worklenz-session-id # Google Login -VITE_ENABLE_GOOGLE_LOGIN=true \ No newline at end of file +VITE_ENABLE_GOOGLE_LOGIN=false \ No newline at end of file diff --git a/worklenz-frontend/Dockerfile b/worklenz-frontend/Dockerfile index b78f32cd..ac1f820f 100644 --- a/worklenz-frontend/Dockerfile +++ b/worklenz-frontend/Dockerfile @@ -1,4 +1,4 @@ -FROM node:18-alpine AS build +FROM node:22-alpine AS build WORKDIR /app @@ -9,7 +9,7 @@ RUN npm ci COPY . . RUN npm run build -FROM node:18-alpine AS production +FROM node:22-alpine AS production WORKDIR /app diff --git a/worklenz-frontend/README.md b/worklenz-frontend/README.md index 125581a7..02c573fc 100644 --- a/worklenz-frontend/README.md +++ b/worklenz-frontend/README.md @@ -1,6 +1,6 @@ -# Worklenz - React Application +# Worklenz - React Frontend -Worklenz is a task management application built with React and bundled using [Vite](https://vitejs.dev/). +Worklenz is a project management application built with React, TypeScript, and Ant Design. The project is bundled using [Vite](https://vitejs.dev/). ## Table of Contents - [Getting Started](#getting-started) @@ -15,11 +15,11 @@ To get started with the project, follow these steps: 1. **Clone the repository**: ```bash - git clone https://github.com/Worklenz/worklenz-v2.git + git clone https://github.com/Worklenz/worklenz.git ``` 2. **Navigate to the project directory**: ```bash - cd worklenz-v2 + cd worklenz/worklenz-frontend ``` 3. **Install dependencies**: ```bash @@ -29,7 +29,7 @@ To get started with the project, follow these steps: ```bash npm run dev ``` -5. Open [http://localhost:3000](http://localhost:3000) in your browser to view the application. +5. Open [http://localhost:5000](http://localhost:5000) in your browser to view the application. ## Available Scripts @@ -38,7 +38,7 @@ In the project directory, you can run: ### `npm run dev` Runs the app in the development mode.\ -Open [http://localhost:5173](http://localhost:5173) to view it in the browser. +Open [http://localhost:5000](http://localhost:5000) to view it in the browser. The page will reload if you make edits.\ You will also see any lint errors in the console. @@ -58,7 +58,22 @@ Open [http://localhost:4173](http://localhost:4173) to preview the build. ## Project Structure -The project structure is organized as follows: +The project is organized around a feature-based structure: + +``` +src/ +├── components/ # Reusable UI components +├── hooks/ # Custom React hooks +├── lib/ # Feature-specific logic +├── pages/ # Route components +├── services/ # API services +├── shared/ # Shared utilities, constants, and types +├── store/ # Global state management +├── types/ # TypeScript type definitions +├── utils/ # Utility functions +├── App.tsx # Main application component +└── main.tsx # Application entry point +``` ## Contributing @@ -72,6 +87,9 @@ Contributions are welcome! If you'd like to contribute, please follow these step ## Learn More -To learn more about Vite, check out the [Vite documentation](https://vitejs.dev/guide/). +To learn more about the technologies used in this project: -To learn React, check out the [React documentation](https://reactjs.org/). +- [React Documentation](https://react.dev/) +- [TypeScript Documentation](https://www.typescriptlang.org/docs/) +- [Ant Design Documentation](https://ant.design/docs/react/introduce) +- [Vite Documentation](https://vitejs.dev/guide/) diff --git a/worklenz-frontend/package.json b/worklenz-frontend/package.json index 527b3a82..31b7f8bf 100644 --- a/worklenz-frontend/package.json +++ b/worklenz-frontend/package.json @@ -5,7 +5,7 @@ "scripts": { "start": "vite", "prebuild": "node scripts/copy-tinymce.js", - "build": "node --max-old-space-size=4096 node_modules/.bin/vite build", + "build": "vite build", "dev-build": "vite build", "serve": "vite preview", "format": "prettier --write ." diff --git a/worklenz-frontend/src/components/EmptyListPlaceholder.tsx b/worklenz-frontend/src/components/EmptyListPlaceholder.tsx index 1d10002c..4953f202 100644 --- a/worklenz-frontend/src/components/EmptyListPlaceholder.tsx +++ b/worklenz-frontend/src/components/EmptyListPlaceholder.tsx @@ -8,7 +8,7 @@ type EmptyListPlaceholderProps = { }; const EmptyListPlaceholder = ({ - imageSrc = 'https://app.worklenz.com/assets/images/empty-box.webp', + imageSrc = '/assets/images/empty-box.webp', imageHeight = 60, text, }: EmptyListPlaceholderProps) => { diff --git a/worklenz-frontend/src/pages/auth/signup-page.tsx b/worklenz-frontend/src/pages/auth/signup-page.tsx index 86b663b9..4e546951 100644 --- a/worklenz-frontend/src/pages/auth/signup-page.tsx +++ b/worklenz-frontend/src/pages/auth/signup-page.tsx @@ -24,6 +24,16 @@ import logger from '@/utils/errorLogger'; import alertService from '@/services/alerts/alertService'; import { WORKLENZ_REDIRECT_PROJ_KEY } from '@/shared/constants'; +// Define the global grecaptcha type +declare global { + interface Window { + grecaptcha?: { + ready: (callback: () => void) => void; + execute: (siteKey: string, options: { action: string }) => Promise; + }; + } +} + const SignupPage = () => { const [form] = Form.useForm(); const navigate = useNavigate(); @@ -58,6 +68,7 @@ const SignupPage = () => { }; const enableGoogleLogin = import.meta.env.VITE_ENABLE_GOOGLE_LOGIN === 'true' || false; + const enableRecaptcha = import.meta.env.VITE_ENABLE_RECAPTCHA === 'true' && import.meta.env.VITE_RECAPTCHA_SITE_KEY && import.meta.env.VITE_RECAPTCHA_SITE_KEY !== 'recaptcha-site-key'; useEffect(() => { trackMixpanelEvent(evt_signup_page_visit); @@ -79,26 +90,35 @@ const SignupPage = () => { }, [trackMixpanelEvent]); useEffect(() => { - const script = document.createElement('script'); - script.src = `https://www.google.com/recaptcha/api.js?render=${import.meta.env.VITE_RECAPTCHA_SITE_KEY}`; - script.async = true; - script.defer = true; - document.body.appendChild(script); - - return () => { - if (script && script.parentNode) { - script.parentNode.removeChild(script); + // Only load recaptcha script if recaptcha is enabled and site key is valid + if (enableRecaptcha && import.meta.env.VITE_RECAPTCHA_SITE_KEY) { + // Check if site key is not the placeholder value + if (import.meta.env.VITE_RECAPTCHA_SITE_KEY === 'recaptcha-site-key') { + console.warn('Using placeholder reCAPTCHA site key. Please set a valid key in your environment variables.'); + return; } - const recaptchaElements = document.getElementsByClassName('grecaptcha-badge'); - while (recaptchaElements.length > 0) { - const element = recaptchaElements[0]; - if (element.parentNode) { - element.parentNode.removeChild(element); + const script = document.createElement('script'); + script.src = `https://www.google.com/recaptcha/api.js?render=${import.meta.env.VITE_RECAPTCHA_SITE_KEY}`; + script.async = true; + script.defer = true; + document.body.appendChild(script); + + return () => { + if (script && script.parentNode) { + script.parentNode.removeChild(script); } - } - }; - }, []); + + const recaptchaElements = document.getElementsByClassName('grecaptcha-badge'); + while (recaptchaElements.length > 0) { + const element = recaptchaElements[0]; + if (element.parentNode) { + element.parentNode.removeChild(element); + } + } + }; + } + }, [enableRecaptcha]); const getInvitationQueryParams = () => { const params = [`team=${urlParams.teamId}`, `teamMember=${urlParams.teamMemberId}`]; @@ -109,33 +129,72 @@ const SignupPage = () => { }; const getRecaptchaToken = async () => { - return new Promise(resolve => { - window.grecaptcha?.ready(() => { - window.grecaptcha - ?.execute(import.meta.env.VITE_RECAPTCHA_SITE_KEY, { action: 'signup' }) - .then((token: string) => { - resolve(token); - }); + if (!enableRecaptcha) return ''; + + // Check if site key is valid + if (!import.meta.env.VITE_RECAPTCHA_SITE_KEY || import.meta.env.VITE_RECAPTCHA_SITE_KEY === 'recaptcha-site-key') { + console.warn('Invalid reCAPTCHA site key. Skipping reCAPTCHA verification.'); + return 'skip-verification'; + } + + try { + return new Promise((resolve, reject) => { + if (!window.grecaptcha) { + reject('reCAPTCHA not loaded'); + return; + } + + window.grecaptcha.ready(() => { + window.grecaptcha! + .execute(import.meta.env.VITE_RECAPTCHA_SITE_KEY, { action: 'signup' }) + .then((token: string) => { + resolve(token); + }) + .catch((error: any) => { + console.error('reCAPTCHA execution error:', error); + reject(error); + }); + }); }); - }); + } catch (error) { + console.error('Error getting reCAPTCHA token:', error); + return ''; + } }; const onFinish = async (values: IUserSignUpRequest) => { try { setValidating(true); - const token = await getRecaptchaToken(); + + if (enableRecaptcha) { + try { + const token = await getRecaptchaToken(); - if (!token) { - logger.error('Failed to get reCAPTCHA token'); - alertService.error(t('reCAPTCHAVerificationError'), t('reCAPTCHAVerificationErrorMessage')); - return; - } + if (!token) { + logger.error('Failed to get reCAPTCHA token'); + alertService.error(t('reCAPTCHAVerificationError'), t('reCAPTCHAVerificationErrorMessage')); + return; + } + + // Skip verification if we're using the special token due to invalid site key + if (token !== 'skip-verification') { + const verifyToken = await authApiService.verifyRecaptchaToken(token); - const veriftToken = await authApiService.verifyRecaptchaToken(token); - - if (!veriftToken.done) { - logger.error('Failed to verify reCAPTCHA token'); - return; + if (!verifyToken.done) { + logger.error('Failed to verify reCAPTCHA token'); + return; + } + } + } catch (error) { + logger.error('reCAPTCHA error:', error); + // Continue with sign up even if reCAPTCHA fails in development + if (import.meta.env.DEV) { + console.warn('Continuing signup despite reCAPTCHA error in development mode'); + } else { + alertService.error(t('reCAPTCHAVerificationError'), t('reCAPTCHAVerificationErrorMessage')); + return; + } + } } const body = { From 6efaeb3ff6ca8f764199d6177e191672327f08f2 Mon Sep 17 00:00:00 2001 From: chamiakJ Date: Mon, 21 Apr 2025 00:12:10 +0530 Subject: [PATCH 3/5] Enhance database schema and functions - Added 'PHASE' value to the WL_TASK_LIST_COL_KEY enum type. - Introduced sys_insert_license_types function to populate license types in the database. - Updated sys_insert_project_templates function to include a new project template and adjusted associated task statuses. - Modified organization insertion logic to set a longer trial period and change subscription status to 'active' for self-hosted licenses. --- worklenz-backend/database/sql/1_tables.sql | 2 +- worklenz-backend/database/sql/2_dml.sql | 302 ++---------------- worklenz-backend/database/sql/4_functions.sql | 9 +- 3 files changed, 28 insertions(+), 285 deletions(-) diff --git a/worklenz-backend/database/sql/1_tables.sql b/worklenz-backend/database/sql/1_tables.sql index ad6ec1f4..af6cdc0e 100644 --- a/worklenz-backend/database/sql/1_tables.sql +++ b/worklenz-backend/database/sql/1_tables.sql @@ -4,7 +4,7 @@ CREATE DOMAIN WL_EMAIL AS TEXT CHECK (value ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-] -- Enumerated Types -- Add new values using "ALTER TYPE WL_TASK_LIST_COL_KEY ADD VALUE 'NEW_VALUE_NAME' AFTER 'REPORTER';" -CREATE TYPE WL_TASK_LIST_COL_KEY AS ENUM ('ASSIGNEES', 'COMPLETED_DATE', 'CREATED_DATE', 'DESCRIPTION', 'DUE_DATE', 'ESTIMATION', 'KEY', 'LABELS', 'LAST_UPDATED', 'NAME', 'PRIORITY', 'PROGRESS', 'START_DATE', 'STATUS', 'TIME_TRACKING', 'REPORTER'); +CREATE TYPE WL_TASK_LIST_COL_KEY AS ENUM ('ASSIGNEES', 'COMPLETED_DATE', 'CREATED_DATE', 'DESCRIPTION', 'DUE_DATE', 'ESTIMATION', 'KEY', 'LABELS', 'LAST_UPDATED', 'NAME', 'PRIORITY', 'PROGRESS', 'START_DATE', 'STATUS', 'TIME_TRACKING', 'REPORTER', 'PHASE'); CREATE TYPE REACTION_TYPES AS ENUM ('like'); diff --git a/worklenz-backend/database/sql/2_dml.sql b/worklenz-backend/database/sql/2_dml.sql index f2fa4d23..8ad29af2 100644 --- a/worklenz-backend/database/sql/2_dml.sql +++ b/worklenz-backend/database/sql/2_dml.sql @@ -66,6 +66,20 @@ BEGIN END; $$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION sys_insert_license_types() RETURNS VOID AS +$$ +BEGIN + INSERT INTO public.sys_license_types (name, key) + VALUES ('Custom Subscription', 'CUSTOM'), + ('Free Trial', 'TRIAL'), + ('Paddle Subscription', 'PADDLE'), + ('Credit Subscription', 'CREDIT'), + ('Free Plan', 'FREE'), + ('Life Time Deal', 'LIFE_TIME_DEAL'), + ('Self Hosted', 'SELF_HOSTED'); +END; +$$ LANGUAGE plpgsql; + CREATE OR REPLACE FUNCTION sys_insert_project_templates() RETURNS VOID AS $$ DECLARE @@ -81,68 +95,12 @@ BEGIN SELECT id INTO done_category_id FROM public.sys_task_status_categories WHERE name = 'Done' LIMIT 1; INSERT INTO public.pt_project_templates (id, name, key, description, phase_label, image_url, color_code) - VALUES ('0a769952-e9b8-4e48-b562-f4ebb0f5914e', 'Software Development', 'SD', 'The "Software Development" project template is a specialized and comprehensive template tailored to the unique needs of software development teams and companies. It offers a structured framework for planning, tracking, and executing software development projects, making it an essential resource for software development firms, tech startups, IT departments, and any business involved in software product development.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/software-developement.gif', '#3b7ad4'), - ('c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', 'Design & Creative', 'DC', 'The "Design & Creative" project template is a versatile solution meticulously crafted to support and enhance the creative and design processes of businesses across various industries. It offers a structured and efficient approach to managing design and creative projects.\r\n', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/creative-and-designing.gif', '#3b7ad4'), - ('f25549d0-d5de-46aa-bbd5-eaab7a970799', 'HR & Recruiting', 'HR', 'The "HR & Recruiting" project template is a specialized tool designed to streamline and enhance human resources and talent acquisition processes. This template is essential for businesses and organizations of all sizes and industries that are involved in HR management and recruitment activities. It provides a structured approach to managing HR projects, making it suitable for a diverse range of businesses.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/hr.gif', '#3b7ad4'), - ('22805d87-ba25-4b2a-8384-7a4eeedc3b63', 'Information Technology', 'IT', 'The "Information Technology" project template is a comprehensive framework tailored to meet the unique needs of businesses and organizations operating in the field of technology and IT services. This versatile template is designed to facilitate the management of IT-related projects, making it applicable to a wide range of businesses.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/information-technology.gif', '#3b7ad4'), - ('a014fae0-1b70-4c05-96b4-1fa8c54591b0', 'Finance', 'FIN', 'The "Finance" project template is a strategic tool meticulously designed to guide businesses and financial professionals in managing critical financial processes, making it applicable to a wide range of industries and businesses. This template provides a structured approach to financial planning, decision-making, and review, making it valuable for various types of organizations.\r\n', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/finance.gif', '#3b7ad4'), - ('e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', 'Personal use', 'PU', 'The "Personal Use" project template is a versatile and user-centric template designed to help individuals manage their personal projects, tasks, and goals with efficiency and organization. This template is adaptable for a wide range of personal activities and can be utilized by anyone seeking a structured approach to planning, tracking, and completing their personal endeavors.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/personal-use.gif', '#3b7ad4'), - ('0aa3f6fc-678f-4d17-8df2-9674ca81fad2', 'Legal', 'LEG', 'The "Legal" project template is a specialized framework meticulously designed to facilitate organized and efficient management of legal projects and matters. This versatile template is essential for businesses, legal departments, and law firms involved in legal activities, making it applicable to a wide range of industries and organizations', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/legal.gif', '#3b7ad4'), - ('5d1ffd21-a7b7-4a5a-9d19-f7fd3ee253e7', 'Nonprofit', 'NON', 'The "Nonprofit" project template is a purpose-built solution tailored to the unique needs and challenges faced by organizations dedicated to serving a greater social or community cause. It provides a structured framework for planning, executing, and monitoring nonprofit initiatives, making it an ideal template for nonprofit organizations, charities, foundations, and community groups.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/non-profit.gif', '#3b7ad4'), - ('39db59be-1dba-448b-87f4-3b955ea699d2', 'Bug Tracking', 'BT', 'The "Bug Tracking" project template is a versatile solution meticulously designed to streamline and enhance the bug management processes of businesses across diverse industries. This template is especially valuable for organizations that rely on software development, IT services, or digital product management. It provides a structured and efficient approach to tracking, resolving, and improving software issues.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/bug-tracking.gif', '#3b7ad4'), - ('d1baf96f-07f4-4468-bb4f-44cdedfad965', 'Manufacturing', 'MAN', 'The "Manufacturing" project template is a specialized and comprehensive template designed to facilitate the management of manufacturing processes and product development from conception to production. This template provides a structured framework for planning, prototyping, and manufacturing, making it essential for manufacturing companies, product design firms, startups, and organizations involved in the development and production of physical products.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/maufacturing.gif', '#3b7ad4'), - ('d90f9194-200a-4afa-a896-ee5afda2cc8a', 'Construction', 'CON', 'The "Construction" project template is a comprehensive solution designed to facilitate efficient project execution for businesses operating in the construction industry. This template offers a structured approach to managing various aspects of construction projects.\r\n', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/construction.gif', '#3b7ad4'), - ('bdf32f36-e85b-434f-aeda-dde2e8ade8c7', 'Sales & CRM', 'SC', 'The "Sales & CRM" project template is a comprehensive solution designed to optimize sales processes and enhance customer relationship management for businesses across various industries. It provides a structured framework for tracking leads, opportunities, and customer interactions, making it an invaluable template for sales-driven organizations and businesses aiming to foster strong customer relationships.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/sales.gif', '#3b7ad4'), - ('c86ed436-f9af-4332-8a2d-2afe9a6d66d4', 'Education', 'EDU', 'The "Education" project template is a dynamic tool designed to facilitate organized and effective management of educational initiatives and projects. This versatile template can be utilized by a variety of educational institutions and businesses involved in the field of education\r\n', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/education.gif', '#3b7ad4'), - ('c94c7306-73ab-4e17-bad4-0e7ff4a9da09', 'Services & Consulting', 'SCE', 'The "Services & Consulting" project template is a robust and versatile template designed to facilitate the efficient delivery of services, consulting engagements, and project-based work across various industries. This template offers a structured framework for managing projects from initiation to closure, making it an invaluable resource for service providers, consulting firms, and businesses engaged in project-driven activities.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/services.gif', '#3b7ad4'), - ('c6917d57-f84a-40fc-aff0-70fd0aab951f', 'Marketing', 'MAR', 'The "Marketing" project template is a versatile and essential template designed to streamline and manage marketing initiatives and campaigns across various industries and business types. This template offers a structured framework for planning, executing, and evaluating marketing projects, making it invaluable for marketing agencies, in-house marketing teams, e-commerce businesses, startups, and organizations seeking to enhance their marketing efforts.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/marketing.gif', '#3b7ad4'); + VALUES ('39db59be-1dba-448b-87f4-3b955ea699d2', 'Bug Tracking', 'BT', 'The "Bug Tracking" project template is a versatile solution meticulously designed to streamline and enhance the bug management processes of businesses across diverse industries. This template is especially valuable for organizations that rely on software development, IT services, or digital product management. It provides a structured and efficient approach to tracking, resolving, and improving software issues.', 'Phase', 'https://worklenz.s3.amazonaws.com/project-template-gifs/bug-tracking.gif', '#3b7ad4'); INSERT INTO public.pt_statuses (id, name, template_id, category_id) VALUES ('c3242606-5a24-48aa-8320-cc90a05c2589', 'To Do', '39db59be-1dba-448b-87f4-3b955ea699d2', todo_category_id), ('05ed8d04-92b1-4c44-bd06-abee29641f31', 'Doing', '39db59be-1dba-448b-87f4-3b955ea699d2', doing_category_id), - ('66e80bc8-6b29-4e72-a484-1593eb1fb44b', 'Done', '39db59be-1dba-448b-87f4-3b955ea699d2', done_category_id), - ('d8a7ac27-cdfb-48fe-b8cd-c87128269a93', 'To Do', 'd90f9194-200a-4afa-a896-ee5afda2cc8a', todo_category_id), - ('a83565f9-2c22-486a-8c10-59182bfe19c6', 'Doing', 'd90f9194-200a-4afa-a896-ee5afda2cc8a', doing_category_id), - ('78803d54-ee61-4389-a5c2-c676f1007d75', 'Done', 'd90f9194-200a-4afa-a896-ee5afda2cc8a', done_category_id), - ('70cbcea9-2b3c-453a-af0d-fb8a7f6270fd', 'To Do', 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', todo_category_id), - ('bf37bf58-c0d1-498f-b208-c9e80509a593', 'Doing', 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', doing_category_id), - ('c7384096-7008-4eb4-b95f-c0706b9647d6', 'Done', 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', done_category_id), - ('efdb40e7-78ae-4a5a-83ec-d372a5816ae5', 'To Do', 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', todo_category_id), - ('ca81d57d-b7d2-45fc-adcd-1c926fd93e6c', 'Doing', 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', doing_category_id), - ('e6dffb96-6879-4064-ab8f-73ca784b1964', 'Done', 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', done_category_id), - ('0010ae09-0dd6-4c08-b351-30124e0dc436', 'To Do', 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', todo_category_id), - ('bde5228a-23b9-4f9d-87fe-1784311d68f6', 'Doing', 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', doing_category_id), - ('5b01b5bd-f29e-477d-94a6-dc5b8101db37', 'Done', 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', done_category_id), - ('35430ef9-040f-465f-b5e3-1f33b26124dd', 'To Do', 'f25549d0-d5de-46aa-bbd5-eaab7a970799', todo_category_id), - ('bac101f9-cec3-43aa-8bd8-354de42fdff2', 'Doing', 'f25549d0-d5de-46aa-bbd5-eaab7a970799', doing_category_id), - ('400b58bd-ad09-4cd1-b5bd-a9f7cb46cb47', 'Done', 'f25549d0-d5de-46aa-bbd5-eaab7a970799', done_category_id), - ('8e9cc873-d9a5-404f-9f38-8ca4fba91a0d', 'To Do', '22805d87-ba25-4b2a-8384-7a4eeedc3b63', todo_category_id), - ('856cd7b1-76b1-4182-b2c8-60c278839913', 'Doing', '22805d87-ba25-4b2a-8384-7a4eeedc3b63', doing_category_id), - ('cbf86a20-8b6d-43d0-99d7-523fb600cdac', 'Done', '22805d87-ba25-4b2a-8384-7a4eeedc3b63', done_category_id), - ('6f0f7261-7f1a-40f3-a771-04fbc5cd0059', 'To Do', '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', todo_category_id), - ('54452070-d355-4fb2-b416-e57390d1b668', 'Doing', '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', doing_category_id), - ('8aac89fc-cf0c-4a4e-889b-97ccd8e7af94', 'Done', '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', done_category_id), - ('0c3c5259-70d2-407b-8a45-c9b81ac84a3a', 'To Do', 'd1baf96f-07f4-4468-bb4f-44cdedfad965', todo_category_id), - ('61cf7147-4416-4efb-bda7-4db1059f76a9', 'Doing', 'd1baf96f-07f4-4468-bb4f-44cdedfad965', doing_category_id), - ('65f4cbd7-bcbb-4a10-8014-fd3b9a3c9ed9', 'Done', 'd1baf96f-07f4-4468-bb4f-44cdedfad965', done_category_id), - ('1073754d-c498-4e6d-bcee-68bba526e572', 'To Do', 'c6917d57-f84a-40fc-aff0-70fd0aab951f', todo_category_id), - ('44d54c08-bce6-44e6-9cfc-23d1b4a9e011', 'Doing', 'c6917d57-f84a-40fc-aff0-70fd0aab951f', doing_category_id), - ('1ae23b11-5edf-485d-9217-f905dc33ddda', 'Done', 'c6917d57-f84a-40fc-aff0-70fd0aab951f', done_category_id), - ('da60606a-ced5-46bf-bf2f-4ee9d5627a9a', 'To Do', '5d1ffd21-a7b7-4a5a-9d19-f7fd3ee253e7', todo_category_id), - ('d2bff598-5135-4cac-92aa-641b69ae1dab', 'Doing', '5d1ffd21-a7b7-4a5a-9d19-f7fd3ee253e7', doing_category_id), - ('9ec60343-27fe-4af5-a35b-d356ebd61837', 'Done', '5d1ffd21-a7b7-4a5a-9d19-f7fd3ee253e7', done_category_id), - ('2e14a9e9-4c1a-4596-a0f2-574d2e7b2fa4', 'To Do', 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', todo_category_id), - ('1a403c75-5d8c-4752-bf96-e486b8042df6', 'Doing', 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', doing_category_id), - ('70fd7f98-b278-440a-8a33-d3453bfd4b70', 'Done', 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', done_category_id), - ('c20816ac-fe57-4471-91b0-61fa060663ca', 'To Do', 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', todo_category_id), - ('291095da-b3d9-40aa-bcd2-5cd6fb51b89f', 'Doing', 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', doing_category_id), - ('b450e432-63f4-46a9-bd4a-26b0bdd37507', 'Done', 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', done_category_id), - ('4da7cb15-9767-4522-9950-6a4b26e660a6', 'To Do', 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', todo_category_id), - ('b4a409f7-4009-4633-b147-f4f99c73f30b', 'Doing', 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', doing_category_id), - ('171e791d-6601-45e7-8c5e-80591a610ed8', 'Done', 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', done_category_id), - ('757e7655-22c1-4a40-8f3d-2754fbd564dc', 'To Do', '0a769952-e9b8-4e48-b562-f4ebb0f5914e', todo_category_id), - ('e22f483a-4c87-417c-8c3d-2673c44b1079', 'Doing', '0a769952-e9b8-4e48-b562-f4ebb0f5914e', doing_category_id), - ('7fae7fde-26a3-44aa-b694-b6d9228e9d6a', 'Done', '0a769952-e9b8-4e48-b562-f4ebb0f5914e', done_category_id); + ('66e80bc8-6b29-4e72-a484-1593eb1fb44b', 'Done', '39db59be-1dba-448b-87f4-3b955ea699d2', done_category_id); INSERT INTO public.pt_tasks (id, name, description, total_minutes, sort_order, priority_id, template_id, parent_task_id, status_id) VALUES ('a75993d9-3fb3-4d0b-a5d4-cab53b60462c', 'Testing and Verification', NULL, 0, 0, medium_priority_id, '39db59be-1dba-448b-87f4-3b955ea699d2', NULL, 'c3242606-5a24-48aa-8320-cc90a05c2589'), @@ -151,116 +109,7 @@ BEGIN ('1e493de8-38cf-4e6e-8f0b-5e1f6f3b07f4', 'Bug Assignment', NULL, 0, 5, medium_priority_id, '39db59be-1dba-448b-87f4-3b955ea699d2', NULL, '05ed8d04-92b1-4c44-bd06-abee29641f31'), ('67b2ab3c-53e5-428c-bbad-8bdc19dc88de', 'Bug Closure', NULL, 0, 4, medium_priority_id, '39db59be-1dba-448b-87f4-3b955ea699d2', NULL, '66e80bc8-6b29-4e72-a484-1593eb1fb44b'), ('9311ff84-1052-4989-8192-0fea20204fbe', 'Documentation', NULL, 0, 3, medium_priority_id, '39db59be-1dba-448b-87f4-3b955ea699d2', NULL, '66e80bc8-6b29-4e72-a484-1593eb1fb44b'), - ('7d0697cd-868c-4b41-9f4f-f9a8c1131b24', 'Reporting', NULL, 0, 1, medium_priority_id, '39db59be-1dba-448b-87f4-3b955ea699d2', NULL, '66e80bc8-6b29-4e72-a484-1593eb1fb44b'), - ('f994718d-3f1c-4880-99da-0b38ce90e0b4', 'Insulation and HVAC', NULL, 0, 4, medium_priority_id, 'd90f9194-200a-4afa-a896-ee5afda2cc8a', NULL, 'a83565f9-2c22-486a-8c10-59182bfe19c6'), - ('5c5fac9e-5016-4e36-b812-1a9c164adca6', 'Site Preparation', NULL, 0, 8, medium_priority_id, 'd90f9194-200a-4afa-a896-ee5afda2cc8a', NULL, 'a83565f9-2c22-486a-8c10-59182bfe19c6'), - ('9127f591-e532-4f88-b61e-2a02fd6a654a', 'Electrical and Plumbing', NULL, 0, 5, medium_priority_id, 'd90f9194-200a-4afa-a896-ee5afda2cc8a', NULL, 'a83565f9-2c22-486a-8c10-59182bfe19c6'), - ('41a6a358-e603-4bf9-9db5-6595f8f00e00', 'Project Planning', NULL, 0, 0, medium_priority_id, 'd90f9194-200a-4afa-a896-ee5afda2cc8a', NULL, 'd8a7ac27-cdfb-48fe-b8cd-c87128269a93'), - ('c37db3ea-0be7-453b-8588-3672fff523fe', 'Exterior Work', NULL, 0, 6, medium_priority_id, 'd90f9194-200a-4afa-a896-ee5afda2cc8a', NULL, '78803d54-ee61-4389-a5c2-c676f1007d75'), - ('6ecc497e-ce9a-4a5d-a2a6-a951f26223cf', 'Structural Framing', NULL, 0, 1, medium_priority_id, 'd90f9194-200a-4afa-a896-ee5afda2cc8a', NULL, 'd8a7ac27-cdfb-48fe-b8cd-c87128269a93'), - ('d53a405b-0bcf-4372-a7ea-d4bca9c13740', 'Foundation Work', NULL, 0, 9, medium_priority_id, 'd90f9194-200a-4afa-a896-ee5afda2cc8a', NULL, '78803d54-ee61-4389-a5c2-c676f1007d75'), - ('b73154b9-d335-4a3e-8593-84da85801720', 'Finishing and Finishing Work', NULL, 0, 7, medium_priority_id, 'd90f9194-200a-4afa-a896-ee5afda2cc8a', NULL, '78803d54-ee61-4389-a5c2-c676f1007d75'), - ('5caac736-3ccf-4b0d-ada4-e1cf4f6ef631', 'Quality Assurance and Inspections', NULL, 0, 2, medium_priority_id, 'd90f9194-200a-4afa-a896-ee5afda2cc8a', NULL, 'd8a7ac27-cdfb-48fe-b8cd-c87128269a93'), - ('44346d7f-fbbd-468a-8c37-e439c268c3e1', 'Utilities and Systems Integration', NULL, 0, 3, medium_priority_id, 'd90f9194-200a-4afa-a896-ee5afda2cc8a', NULL, 'd8a7ac27-cdfb-48fe-b8cd-c87128269a93'), - ('870daf76-fedb-4198-b13a-9ac453a73dee', 'Brainstorm creative ideas and concepts, and sketch initial designs.', NULL, 0, 0, medium_priority_id, 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', NULL, '70cbcea9-2b3c-453a-af0d-fb8a7f6270fd'), - ('0ebb388d-c7e5-49ce-bf25-e2ce7b9511a1', 'Create storyboards or wireframes to outline the structure and flow of the design.', NULL, 0, 1, medium_priority_id, 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', NULL, '70cbcea9-2b3c-453a-af0d-fb8a7f6270fd'), - ('5d09ae52-7e88-41d6-af82-4efdc55b4af0', 'Create user-friendly interfaces for digital products, ensuring a seamless user experience.', NULL, 0, 2, medium_priority_id, 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', NULL, '70cbcea9-2b3c-453a-af0d-fb8a7f6270fd'), - ('675a8efc-17f2-4c9b-bd06-227020ffa688', 'Develop high-quality visual design assets based on approved concepts.', NULL, 0, 3, medium_priority_id, 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', NULL, '70cbcea9-2b3c-453a-af0d-fb8a7f6270fd'), - ('5403584c-57c4-40f6-8ac5-7c0b89d10370', 'Prepare design files for final production, including optimizing for different platforms.', NULL, 0, 4, medium_priority_id, 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', NULL, '70cbcea9-2b3c-453a-af0d-fb8a7f6270fd'), - ('14de7fa7-e8ac-4d1d-8249-250c733f57c0', 'Collaborate with production teams to produce and deliver the final design assets.', NULL, 0, 5, medium_priority_id, 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', NULL, '70cbcea9-2b3c-453a-af0d-fb8a7f6270fd'), - ('188fcdab-ea71-4be3-96f1-cbe224b53310', 'Research and analyze the target market, including demographics and trends.', NULL, 0, 6, medium_priority_id, 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', NULL, '70cbcea9-2b3c-453a-af0d-fb8a7f6270fd'), - ('a67b4800-b35d-4448-90ba-e6aa1053ea3f', 'Study competitors'' design strategies and identify opportunities for differentiation.', NULL, 0, 7, medium_priority_id, 'c73b8eaf-9a07-4d89-a7e7-84cd2fd4e9f0', NULL, '70cbcea9-2b3c-453a-af0d-fb8a7f6270fd'), - ('1b5be3a3-ae6d-43f7-8a5a-fa48c96fd54a', 'Class Scheduling', NULL, 0, 8, medium_priority_id, 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', NULL, 'e6dffb96-6879-4064-ab8f-73ca784b1964'), - ('fbe32f14-a8e9-401b-8a39-ad53ebd520fa', 'Curriculum Development', NULL, 0, 5, medium_priority_id, 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', NULL, 'ca81d57d-b7d2-45fc-adcd-1c926fd93e6c'), - ('1a9bedd1-dbfb-4d25-9b16-ec1eeb773bac', 'Assignments and Homework', NULL, 0, 7, medium_priority_id, 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', NULL, 'ca81d57d-b7d2-45fc-adcd-1c926fd93e6c'), - ('3e7db894-b421-46dc-954e-5c38d6270483', 'Grading and Assessment', NULL, 0, 6, medium_priority_id, 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', NULL, 'e6dffb96-6879-4064-ab8f-73ca784b1964'), - ('744e8dff-76b0-4450-8ad3-1d38d137c4df', 'Student Progress Tracking', NULL, 0, 4, medium_priority_id, 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', NULL, 'ca81d57d-b7d2-45fc-adcd-1c926fd93e6c'), - ('f9b8c333-b8d2-45b8-aff1-d2686aecc23d', 'Resource Management', NULL, 0, 0, medium_priority_id, 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', NULL, 'efdb40e7-78ae-4a5a-83ec-d372a5816ae5'), - ('97619dc4-8879-45cc-b17b-bc34db6a658f', 'Research Projects', NULL, 0, 1, medium_priority_id, 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', NULL, 'efdb40e7-78ae-4a5a-83ec-d372a5816ae5'), - ('2e277e0f-b389-41de-8caf-c577245be524', 'Event Planning', NULL, 0, 2, medium_priority_id, 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', NULL, 'efdb40e7-78ae-4a5a-83ec-d372a5816ae5'), - ('c3bb796c-cfe2-47a8-935e-a9176652279d', 'Budget Management', NULL, 0, 3, medium_priority_id, 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', NULL, 'efdb40e7-78ae-4a5a-83ec-d372a5816ae5'), - ('c981c84d-bbe7-48a1-891b-8485b44ba6dc', 'Online Learning Management', NULL, 0, 9, medium_priority_id, 'c86ed436-f9af-4332-8a2d-2afe9a6d66d4', NULL, 'e6dffb96-6879-4064-ab8f-73ca784b1964'), - ('adbe95e7-a5df-4741-bf1d-79be33654d43', 'Budget Planning', NULL, 0, 0, medium_priority_id, 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', NULL, '0010ae09-0dd6-4c08-b351-30124e0dc436'), - ('140792fb-d588-4928-8d8a-40641d8982f0', 'Financial Reporting', NULL, 0, 3, medium_priority_id, 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', NULL, '0010ae09-0dd6-4c08-b351-30124e0dc436'), - ('ae43bf0c-32b9-42e7-8c2d-e13921734669', 'Vendor and Supplier Management', NULL, 0, 4, medium_priority_id, 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', NULL, '0010ae09-0dd6-4c08-b351-30124e0dc436'), - ('c76b2ba9-a2f3-4acc-8b23-cbf89f9e858e', 'Invoice Management', NULL, 0, 5, medium_priority_id, 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', NULL, '0010ae09-0dd6-4c08-b351-30124e0dc436'), - ('0079334e-6bbd-45df-8591-444d53df8195', 'Expense Approval Workflow', NULL, 0, 6, medium_priority_id, 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', NULL, '0010ae09-0dd6-4c08-b351-30124e0dc436'), - ('a656409f-48a2-473c-b53f-9431701e9641', 'Tax Planning and Compliance', NULL, 0, 7, medium_priority_id, 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', NULL, '0010ae09-0dd6-4c08-b351-30124e0dc436'), - ('5312ae9f-378d-4aff-ac79-55b401750f82', 'Budget Monitoring', NULL, 0, 1, medium_priority_id, 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', NULL, '0010ae09-0dd6-4c08-b351-30124e0dc436'), - ('d884b9e0-189a-4a2f-9467-92c132295e40', 'Expense Tracking', NULL, 0, 2, medium_priority_id, 'a014fae0-1b70-4c05-96b4-1fa8c54591b0', NULL, '0010ae09-0dd6-4c08-b351-30124e0dc436'), - ('6e33e82f-ac08-4162-9b84-0f98f8153ec4', 'Identify and reach out to potential candidates through various channels', NULL, 0, 2, medium_priority_id, 'f25549d0-d5de-46aa-bbd5-eaab7a970799', NULL, '35430ef9-040f-465f-b5e3-1f33b26124dd'), - ('8f02befa-3671-4a18-bd01-817e2fb8f9c7', 'Define the job position, responsibilities, and requirements.', NULL, 0, 4, medium_priority_id, 'f25549d0-d5de-46aa-bbd5-eaab7a970799', NULL, '35430ef9-040f-465f-b5e3-1f33b26124dd'), - ('f96e15cf-4a99-4f62-a19d-649d890dc364', 'Schedule and conduct interviews with shortlisted candidates.', NULL, 0, 0, medium_priority_id, 'f25549d0-d5de-46aa-bbd5-eaab7a970799', NULL, 'bac101f9-cec3-43aa-8bd8-354de42fdff2'), - ('e1a78723-a98d-4fa9-ba11-3d5b1429796c', 'Contact provided references to validate candidates'' background and skills.', NULL, 0, 1, medium_priority_id, 'f25549d0-d5de-46aa-bbd5-eaab7a970799', NULL, 'bac101f9-cec3-43aa-8bd8-354de42fdff2'), - ('e7ca181d-d348-4745-a7d9-8e8f1abcda77', 'Collect feedback from interviewers and team members', NULL, 0, 5, medium_priority_id, 'f25549d0-d5de-46aa-bbd5-eaab7a970799', NULL, '400b58bd-ad09-4cd1-b5bd-a9f7cb46cb47'), - ('55a7db50-f37d-46b5-be2b-b9938b892ad7', 'Document feedback for future reference and improvement.', NULL, 0, 3, medium_priority_id, 'f25549d0-d5de-46aa-bbd5-eaab7a970799', NULL, '400b58bd-ad09-4cd1-b5bd-a9f7cb46cb47'), - ('52199ccf-bbea-465d-9e0a-192abd5b4ddf', 'Designing, coding, testing, and maintaining software applications and systems.', NULL, 0, 0, medium_priority_id, '22805d87-ba25-4b2a-8384-7a4eeedc3b63', NULL, '8e9cc873-d9a5-404f-9f38-8ca4fba91a0d'), - ('87005eaf-d51c-4876-92ea-5071973de1f6', 'Managing servers and computer systems, including installation, configuration, updates, and maintenance.', NULL, 0, 3, medium_priority_id, '22805d87-ba25-4b2a-8384-7a4eeedc3b63', NULL, '856cd7b1-76b1-4182-b2c8-60c278839913'), - ('64f5c488-7600-4d94-a85b-6fff17ffc746', 'Conducting security audits, vulnerability assessments, and risk management.', NULL, 0, 2, medium_priority_id, '22805d87-ba25-4b2a-8384-7a4eeedc3b63', NULL, 'cbf86a20-8b6d-43d0-99d7-523fb600cdac'), - ('ad7c0cf8-af9c-46e0-b717-492b29ce4411', 'Managing data integrity, security, and backups.', NULL, 0, 1, medium_priority_id, '22805d87-ba25-4b2a-8384-7a4eeedc3b63', NULL, '8e9cc873-d9a5-404f-9f38-8ca4fba91a0d'), - ('71f423c7-0de0-445b-b84f-a507c2a56398', 'Performance Optimization', NULL, 0, 4, medium_priority_id, '22805d87-ba25-4b2a-8384-7a4eeedc3b63', NULL, '8e9cc873-d9a5-404f-9f38-8ca4fba91a0d'), - ('e6a32779-9a8c-4e6c-bf25-0cc33de94f19', 'Privacy and Data Protection', NULL, 0, 6, medium_priority_id, '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', NULL, '54452070-d355-4fb2-b416-e57390d1b668'), - ('43bec431-1290-420e-8dc6-906204a6d25f', 'Regulatory Filings', NULL, 0, 7, medium_priority_id, '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', NULL, '8aac89fc-cf0c-4a4e-889b-97ccd8e7af94'), - ('59c54e42-163b-45ab-a3dc-ce0816f5ed9f', 'Litigation and Dispute Resolution', NULL, 0, 5, medium_priority_id, '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', NULL, '8aac89fc-cf0c-4a4e-889b-97ccd8e7af94'), - ('d82d92a3-54a5-41ac-b77b-c72535bffeb6', 'Contract Management', NULL, 0, 0, medium_priority_id, '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', NULL, '6f0f7261-7f1a-40f3-a771-04fbc5cd0059'), - ('20697a2d-6586-4ffb-901c-529f154c8b43', 'Contract Renewals and Expirations', NULL, 0, 1, medium_priority_id, '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', NULL, '6f0f7261-7f1a-40f3-a771-04fbc5cd0059'), - ('1f8826ea-60a9-4d93-a7f8-96fbe72aef6c', 'Compliance Checks', NULL, 0, 4, medium_priority_id, '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', NULL, '54452070-d355-4fb2-b416-e57390d1b668'), - ('ae28eba7-c9d8-480c-ab42-20e348f0355d', 'Legal Research', NULL, 0, 2, medium_priority_id, '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', NULL, '6f0f7261-7f1a-40f3-a771-04fbc5cd0059'), - ('049681c1-5a04-4cf3-aaff-fdea575daeb6', 'Intellectual Property Management', NULL, 0, 3, medium_priority_id, '0aa3f6fc-678f-4d17-8df2-9674ca81fad2', NULL, '54452070-d355-4fb2-b416-e57390d1b668'), - ('bc5d795c-4d2b-4fe2-938a-5f7758e8c90f', 'Develop detailed product design specifications, including materials, dimensions, and functionality.', NULL, 0, 0, medium_priority_id, 'd1baf96f-07f4-4468-bb4f-44cdedfad965', NULL, '0c3c5259-70d2-407b-8a45-c9b81ac84a3a'), - ('2771231b-9b2d-46c0-ac2d-d34d71f16c09', 'Collaborate with the design and engineering teams to brainstorm innovative ideas for product features and improvements.', NULL, 0, 1, medium_priority_id, 'd1baf96f-07f4-4468-bb4f-44cdedfad965', NULL, '0c3c5259-70d2-407b-8a45-c9b81ac84a3a'), - ('bab7972e-5668-4fdd-9de2-11b6a6788703', 'Conduct market research to assess the viability of proposed product enhancements and innovations.', NULL, 0, 6, medium_priority_id, 'd1baf96f-07f4-4468-bb4f-44cdedfad965', NULL, '0c3c5259-70d2-407b-8a45-c9b81ac84a3a'), - ('75720af5-67cc-493d-88cf-4e373bc579e0', 'Develop detailed manufacturing processes, including workflow, quality control measures, and production schedules.', NULL, 0, 3, medium_priority_id, 'd1baf96f-07f4-4468-bb4f-44cdedfad965', NULL, '0c3c5259-70d2-407b-8a45-c9b81ac84a3a'), - ('3a57acd0-4c0a-4cc8-bf99-0c9f959eaed6', 'Plan and optimize the supply chain, including sourcing raw materials, logistics, and inventory management.', NULL, 0, 2, medium_priority_id, 'd1baf96f-07f4-4468-bb4f-44cdedfad965', NULL, '0c3c5259-70d2-407b-8a45-c9b81ac84a3a'), - ('fc721d7c-6a69-4241-8294-45122584b238', 'Create prototypes of the product to validate the design and manufacturing processes', NULL, 0, 4, medium_priority_id, 'd1baf96f-07f4-4468-bb4f-44cdedfad965', NULL, '0c3c5259-70d2-407b-8a45-c9b81ac84a3a'), - ('0264e8c3-d63f-40ba-906c-e3c090ce02ab', 'Create designs for the molds, tooling, and equipment needed in the manufacturing process.', NULL, 0, 7, medium_priority_id, 'd1baf96f-07f4-4468-bb4f-44cdedfad965', NULL, '0c3c5259-70d2-407b-8a45-c9b81ac84a3a'), - ('b17877ae-0ab0-4ffc-a4fb-49a8c80c0374', 'Perform rigorous testing and validation on prototypes to ensure they meet quality and performance standards.', NULL, 0, 5, medium_priority_id, 'd1baf96f-07f4-4468-bb4f-44cdedfad965', NULL, '0c3c5259-70d2-407b-8a45-c9b81ac84a3a'), - ('c2d154ca-4e56-4d88-9982-8e5587463b29', 'Delivering value to your customers and leads', NULL, 0, 6, medium_priority_id, 'c6917d57-f84a-40fc-aff0-70fd0aab951f', NULL, '1073754d-c498-4e6d-bcee-68bba526e572'), - ('5ccadffe-a860-49b2-be7c-73af5a6f81e9', 'Introducing new products or services', NULL, 0, 3, medium_priority_id, 'c6917d57-f84a-40fc-aff0-70fd0aab951f', NULL, '44d54c08-bce6-44e6-9cfc-23d1b4a9e011'), - ('6e563048-0891-418e-a5f5-28f228f0339b', 'Collecting feedback from customers', NULL, 0, 1, medium_priority_id, 'c6917d57-f84a-40fc-aff0-70fd0aab951f', NULL, '1ae23b11-5edf-485d-9217-f905dc33ddda'), - ('e83926a9-b618-4398-a4ec-9022314df3bd', 'Building marketing strategies and campaigns', NULL, 0, 2, medium_priority_id, 'c6917d57-f84a-40fc-aff0-70fd0aab951f', NULL, '1ae23b11-5edf-485d-9217-f905dc33ddda'), - ('08496b12-c259-49ad-9ceb-6669171c38c3', 'Tracking and monitoring marketing campaigns', NULL, 0, 4, medium_priority_id, 'c6917d57-f84a-40fc-aff0-70fd0aab951f', NULL, '1ae23b11-5edf-485d-9217-f905dc33ddda'), - ('eae2707f-4804-4691-80ba-4f86f71e7b32', 'Creating a strong and dependable brand', NULL, 0, 5, medium_priority_id, 'c6917d57-f84a-40fc-aff0-70fd0aab951f', NULL, '44d54c08-bce6-44e6-9cfc-23d1b4a9e011'), - ('709e726f-02d8-4549-b77a-c38d14054cf3', 'Boosting company sales', NULL, 0, 7, medium_priority_id, 'c6917d57-f84a-40fc-aff0-70fd0aab951f', NULL, '1073754d-c498-4e6d-bcee-68bba526e572'), - ('4f2eacd8-daaf-45f6-9986-de5a9c8e459d', 'Resource Management', NULL, 0, 5, medium_priority_id, '5d1ffd21-a7b7-4a5a-9d19-f7fd3ee253e7', NULL, 'd2bff598-5135-4cac-92aa-641b69ae1dab'), - ('483d6ba0-ce02-4f0f-8f27-d417289939a5', 'Budgeting and Fundraising', NULL, 0, 2, medium_priority_id, '5d1ffd21-a7b7-4a5a-9d19-f7fd3ee253e7', NULL, 'd2bff598-5135-4cac-92aa-641b69ae1dab'), - ('e70c332b-d0f6-403b-b3c0-cc7467487cae', 'Project Planning', NULL, 0, 3, medium_priority_id, '5d1ffd21-a7b7-4a5a-9d19-f7fd3ee253e7', NULL, 'da60606a-ced5-46bf-bf2f-4ee9d5627a9a'), - ('6686a09b-f767-466a-a355-fe8d9a16c28b', 'Task Management', NULL, 0, 4, medium_priority_id, '5d1ffd21-a7b7-4a5a-9d19-f7fd3ee253e7', NULL, '9ec60343-27fe-4af5-a35b-d356ebd61837'), - ('f819d4fc-d3d9-48a8-b06e-92e982686d25', 'Project Initiation', NULL, 0, 0, medium_priority_id, '5d1ffd21-a7b7-4a5a-9d19-f7fd3ee253e7', NULL, 'da60606a-ced5-46bf-bf2f-4ee9d5627a9a'), - ('a224c0dc-d86d-4fdb-9f69-bd59d26f9760', 'Communication and Collaboration', NULL, 0, 1, medium_priority_id, '5d1ffd21-a7b7-4a5a-9d19-f7fd3ee253e7', NULL, '9ec60343-27fe-4af5-a35b-d356ebd61837'), - ('b8766b9d-5ea3-4704-ad6e-88d5f94eb816', 'Home Renovation Project', NULL, 0, 0, medium_priority_id, 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', NULL, '2e14a9e9-4c1a-4596-a0f2-574d2e7b2fa4'), - ('f4a7de5a-7ffd-4ae3-8266-7fe12a75256f', 'Education and Self-Improvement', NULL, 0, 7, medium_priority_id, 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', NULL, '2e14a9e9-4c1a-4596-a0f2-574d2e7b2fa4'), - ('651c7ffe-58d4-4807-aef5-720e58d254d8', 'Event Planning', NULL, 0, 1, medium_priority_id, 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', NULL, '2e14a9e9-4c1a-4596-a0f2-574d2e7b2fa4'), - ('d3c78cb9-72b8-4277-ae12-094ce4be1bbc', 'Fitness and Health Goals', NULL, 0, 2, medium_priority_id, 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', NULL, '1a403c75-5d8c-4752-bf96-e486b8042df6'), - ('619f02b0-7639-42aa-ae39-2808289c713f', 'Vacation Planning', NULL, 0, 3, medium_priority_id, 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', NULL, '1a403c75-5d8c-4752-bf96-e486b8042df6'), - ('edc39164-215e-4a4c-9e91-a4ef1cc17709', 'Home Organization', NULL, 0, 4, medium_priority_id, 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', NULL, '70fd7f98-b278-440a-8a33-d3453bfd4b70'), - ('d7a2898a-887c-4832-af44-4c2bfa8c482f', 'Personal Blog', NULL, 0, 5, medium_priority_id, 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', NULL, '70fd7f98-b278-440a-8a33-d3453bfd4b70'), - ('1510b125-197d-4389-afa9-70d2ee2c610e', 'Financial Goals', NULL, 0, 6, medium_priority_id, 'e71b3b9b-31a8-47ae-84d8-6dc2ae62e06a', NULL, '70fd7f98-b278-440a-8a33-d3453bfd4b70'), - ('a2c8a7ca-8f5f-4fe0-ac47-a186dc8cea25', 'Integration with Existing Systems', NULL, 0, 5, medium_priority_id, 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', NULL, 'b450e432-63f4-46a9-bd4a-26b0bdd37507'), - ('fff9365e-e86f-46d4-8ac3-e90ac6c8f391', 'Initial Contact and Relationship Building', NULL, 0, 4, medium_priority_id, 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', NULL, '291095da-b3d9-40aa-bcd2-5cd6fb51b89f'), - ('8d3df58a-3b57-4269-9f23-a02d8630b001', 'Lead Qualification and Research', NULL, 0, 0, medium_priority_id, 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', NULL, 'c20816ac-fe57-4471-91b0-61fa060663ca'), - ('c1a6dc5c-e952-43e0-bd02-5505e4db72f0', 'Initial Outreach and Engagement', NULL, 0, 1, medium_priority_id, 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', NULL, 'c20816ac-fe57-4471-91b0-61fa060663ca'), - ('7b1ec3b2-5e72-4b4d-87f6-1b158e4b98a4', 'Lost Opportunity Analysis', NULL, 0, 2, medium_priority_id, 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', NULL, 'c20816ac-fe57-4471-91b0-61fa060663ca'), - ('2fd89257-98c7-4bae-9186-961259edd4fd', 'Competitor Analysis and Benchmarking', NULL, 0, 3, medium_priority_id, 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', NULL, '291095da-b3d9-40aa-bcd2-5cd6fb51b89f'), - ('d43541ae-bc9c-49ad-aa5f-097563ca3e2d', 'Post-Sale Follow-Up and Relationship Building', NULL, 0, 6, medium_priority_id, 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', NULL, 'b450e432-63f4-46a9-bd4a-26b0bdd37507'), - ('24ae6a1a-019d-4040-bacc-115be99f7982', 'Testing and Quality Assurance', NULL, 0, 7, medium_priority_id, 'bdf32f36-e85b-434f-aeda-dde2e8ade8c7', NULL, 'b450e432-63f4-46a9-bd4a-26b0bdd37507'), - ('1c78e9bd-e869-4486-a891-5dae020f35da', 'Client Onboarding', NULL, 0, 0, medium_priority_id, 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', NULL, '4da7cb15-9767-4522-9950-6a4b26e660a6'), - ('642b2621-16bb-4615-9b4c-4ea6cd32f7e9', 'Project Documentation', NULL, 0, 1, medium_priority_id, 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', NULL, '4da7cb15-9767-4522-9950-6a4b26e660a6'), - ('df901da9-9a6e-4d54-9fbd-e14065dadc40', 'Task Management', NULL, 0, 6, medium_priority_id, 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', NULL, '171e791d-6601-45e7-8c5e-80591a610ed8'), - ('643deb27-9521-4672-9ebc-9349e39607a4', 'Resource Allocation', NULL, 0, 8, medium_priority_id, 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', NULL, '4da7cb15-9767-4522-9950-6a4b26e660a6'), - ('77278afe-54fa-4174-b56f-bf2217aaa8d6', 'Risk Assessment and Management', NULL, 0, 9, medium_priority_id, 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', NULL, 'b4a409f7-4009-4633-b147-f4f99c73f30b'), - ('baa06fdf-495a-42ee-ba1c-026953301487', 'Time and Expense Tracking', NULL, 0, 2, medium_priority_id, 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', NULL, '4da7cb15-9767-4522-9950-6a4b26e660a6'), - ('f80ecf2c-4e52-4957-bb3a-3bd5f98b1017', 'Continuous Improvement', NULL, 0, 3, medium_priority_id, 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', NULL, '4da7cb15-9767-4522-9950-6a4b26e660a6'), - ('f3565c7f-39cd-46b4-bc44-2682d7b48cbc', 'Client Satisfaction and Feedback', NULL, 0, 5, medium_priority_id, 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', NULL, 'b4a409f7-4009-4633-b147-f4f99c73f30b'), - ('2080b4ac-4e00-4e24-9f93-05b54d72f2e9', 'Client Communication', NULL, 0, 4, medium_priority_id, 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', NULL, 'b4a409f7-4009-4633-b147-f4f99c73f30b'), - ('276bf24a-da04-4276-bbb8-206e9b276968', 'Project Planning', NULL, 0, 7, medium_priority_id, 'c94c7306-73ab-4e17-bad4-0e7ff4a9da09', NULL, '171e791d-6601-45e7-8c5e-80591a610ed8'), - ('5fd32a4a-4ab0-44ad-873d-459b9062ae6e', 'User Acceptance Testing', NULL, 0, 12, medium_priority_id, '0a769952-e9b8-4e48-b562-f4ebb0f5914e', NULL, '757e7655-22c1-4a40-8f3d-2754fbd564dc'), - ('8f54a3a2-38cc-48d6-85e1-d8fe47eea2f0', 'Requirement Gathering', NULL, 0, 11, medium_priority_id, '0a769952-e9b8-4e48-b562-f4ebb0f5914e', NULL, '757e7655-22c1-4a40-8f3d-2754fbd564dc'), - ('091b0fea-c089-499d-a9e4-8a0eba3bbe47', 'Coding', NULL, 0, 4, medium_priority_id, '0a769952-e9b8-4e48-b562-f4ebb0f5914e', NULL, '757e7655-22c1-4a40-8f3d-2754fbd564dc'), - ('a4b2ead5-9d73-4064-b261-35cfcd0e6f89', 'Unit Testing', NULL, 0, 6, medium_priority_id, '0a769952-e9b8-4e48-b562-f4ebb0f5914e', NULL, '7fae7fde-26a3-44aa-b694-b6d9228e9d6a'), - ('81cab85b-0272-40b2-9163-92973bc0fdfd', 'Continuous Integration/Continuous Deployment (CI/CD)', NULL, 0, 7, medium_priority_id, '0a769952-e9b8-4e48-b562-f4ebb0f5914e', NULL, 'e22f483a-4c87-417c-8c3d-2673c44b1079'), - ('c7b7ac95-5c5a-4dcd-8a59-c8060011f753', 'Code Review', NULL, 0, 10, medium_priority_id, '0a769952-e9b8-4e48-b562-f4ebb0f5914e', NULL, 'e22f483a-4c87-417c-8c3d-2673c44b1079'), - ('914b10d9-622a-4269-b6bc-07c1377fb512', 'Collaboration and Communication', NULL, 0, 9, medium_priority_id, '0a769952-e9b8-4e48-b562-f4ebb0f5914e', NULL, '7fae7fde-26a3-44aa-b694-b6d9228e9d6a'); + ('7d0697cd-868c-4b41-9f4f-f9a8c1131b24', 'Reporting', NULL, 0, 1, medium_priority_id, '39db59be-1dba-448b-87f4-3b955ea699d2', NULL, '66e80bc8-6b29-4e72-a484-1593eb1fb44b'); INSERT INTO public.pt_task_phases (task_id, phase_id) VALUES ('a75993d9-3fb3-4d0b-a5d4-cab53b60462c', '4b4a8fe0-4f35-464a-a337-848e5b432ab5'), @@ -269,116 +118,7 @@ BEGIN ('1e493de8-38cf-4e6e-8f0b-5e1f6f3b07f4', 'e3128891-4873-4795-ad8a-880474280045'), ('67b2ab3c-53e5-428c-bbad-8bdc19dc88de', '77204bf3-fcb3-4e39-a843-14458b2f659d'), ('9311ff84-1052-4989-8192-0fea20204fbe', '62097027-979f-4b00-afb8-f70fba533f80'), - ('7d0697cd-868c-4b41-9f4f-f9a8c1131b24', '62097027-979f-4b00-afb8-f70fba533f80'), - ('f994718d-3f1c-4880-99da-0b38ce90e0b4', '76356365-e420-46b7-b7ff-1747589cb7c9'), - ('5c5fac9e-5016-4e36-b812-1a9c164adca6', 'a0cb92e9-1e44-4fd6-9522-a1ec5d50fd5c'), - ('9127f591-e532-4f88-b61e-2a02fd6a654a', '76356365-e420-46b7-b7ff-1747589cb7c9'), - ('41a6a358-e603-4bf9-9db5-6595f8f00e00', 'a0cb92e9-1e44-4fd6-9522-a1ec5d50fd5c'), - ('c37db3ea-0be7-453b-8588-3672fff523fe', '6872d742-8e8e-43fe-8d3b-031ff23ba32f'), - ('6ecc497e-ce9a-4a5d-a2a6-a951f26223cf', '1bea49a2-ba94-4c3a-acd3-973edfc182c8'), - ('d53a405b-0bcf-4372-a7ea-d4bca9c13740', 'a0cb92e9-1e44-4fd6-9522-a1ec5d50fd5c'), - ('b73154b9-d335-4a3e-8593-84da85801720', '6872d742-8e8e-43fe-8d3b-031ff23ba32f'), - ('5caac736-3ccf-4b0d-ada4-e1cf4f6ef631', '702c5891-85bf-4b11-ab70-4f84557b65be'), - ('44346d7f-fbbd-468a-8c37-e439c268c3e1', '702c5891-85bf-4b11-ab70-4f84557b65be'), - ('870daf76-fedb-4198-b13a-9ac453a73dee', 'a6db279b-61e9-4e3b-9521-dfae7bb1c0bb'), - ('0ebb388d-c7e5-49ce-bf25-e2ce7b9511a1', 'a6db279b-61e9-4e3b-9521-dfae7bb1c0bb'), - ('5d09ae52-7e88-41d6-af82-4efdc55b4af0', 'a6db279b-61e9-4e3b-9521-dfae7bb1c0bb'), - ('675a8efc-17f2-4c9b-bd06-227020ffa688', '8c3d49a2-6057-42fe-a458-f4596dc31eb4'), - ('5403584c-57c4-40f6-8ac5-7c0b89d10370', '42df6764-069f-45e6-8b74-4f1e82e8c1e6'), - ('14de7fa7-e8ac-4d1d-8249-250c733f57c0', '42df6764-069f-45e6-8b74-4f1e82e8c1e6'), - ('188fcdab-ea71-4be3-96f1-cbe224b53310', 'b871bc4f-aa95-4bec-b943-0a518d29019a'), - ('a67b4800-b35d-4448-90ba-e6aa1053ea3f', 'b871bc4f-aa95-4bec-b943-0a518d29019a'), - ('1b5be3a3-ae6d-43f7-8a5a-fa48c96fd54a', '05148a01-26f4-40ef-966d-f7ec601f08e4'), - ('fbe32f14-a8e9-401b-8a39-ad53ebd520fa', '97248076-3eda-434e-8e9d-4b15a9faefca'), - ('1a9bedd1-dbfb-4d25-9b16-ec1eeb773bac', '05148a01-26f4-40ef-966d-f7ec601f08e4'), - ('3e7db894-b421-46dc-954e-5c38d6270483', '97248076-3eda-434e-8e9d-4b15a9faefca'), - ('744e8dff-76b0-4450-8ad3-1d38d137c4df', '37eb54c4-d10c-428b-a2e8-1842de245b56'), - ('f9b8c333-b8d2-45b8-aff1-d2686aecc23d', '37eb54c4-d10c-428b-a2e8-1842de245b56'), - ('97619dc4-8879-45cc-b17b-bc34db6a658f', '8cfe36b7-3d19-45f4-bf2b-ec5ea16efb23'), - ('2e277e0f-b389-41de-8caf-c577245be524', '8cfe36b7-3d19-45f4-bf2b-ec5ea16efb23'), - ('c3bb796c-cfe2-47a8-935e-a9176652279d', 'd642aacb-85ee-4f23-a0af-c1e4ea9f3128'), - ('c981c84d-bbe7-48a1-891b-8485b44ba6dc', 'd642aacb-85ee-4f23-a0af-c1e4ea9f3128'), - ('adbe95e7-a5df-4741-bf1d-79be33654d43', '0d5e5593-76f6-4fd7-b979-40b0ae306ff3'), - ('140792fb-d588-4928-8d8a-40641d8982f0', '290fb77c-b727-43ba-a31d-06cd73ead1cc'), - ('ae43bf0c-32b9-42e7-8c2d-e13921734669', '641997aa-f850-41d9-bbe6-1901cb439c42'), - ('c76b2ba9-a2f3-4acc-8b23-cbf89f9e858e', '641997aa-f850-41d9-bbe6-1901cb439c42'), - ('0079334e-6bbd-45df-8591-444d53df8195', '726d1785-3686-4ff9-bd95-f2f409d915ff'), - ('a656409f-48a2-473c-b53f-9431701e9641', '726d1785-3686-4ff9-bd95-f2f409d915ff'), - ('5312ae9f-378d-4aff-ac79-55b401750f82', '290fb77c-b727-43ba-a31d-06cd73ead1cc'), - ('d884b9e0-189a-4a2f-9467-92c132295e40', '290fb77c-b727-43ba-a31d-06cd73ead1cc'), - ('6e33e82f-ac08-4162-9b84-0f98f8153ec4', '56af064d-df42-49c5-b5e6-438316ee25d7'), - ('8f02befa-3671-4a18-bd01-817e2fb8f9c7', 'aa1888cc-5bc3-4f71-b409-4b5edf4a1627'), - ('f96e15cf-4a99-4f62-a19d-649d890dc364', 'b052092d-d5e2-4ac2-979e-6add568a6781'), - ('e1a78723-a98d-4fa9-ba11-3d5b1429796c', '4b1619ef-d86d-4c18-b163-b959c732830f'), - ('e7ca181d-d348-4745-a7d9-8e8f1abcda77', 'b052092d-d5e2-4ac2-979e-6add568a6781'), - ('55a7db50-f37d-46b5-be2b-b9938b892ad7', '23c1c050-af67-45ef-8a1e-7d7da60a23b9'), - ('52199ccf-bbea-465d-9e0a-192abd5b4ddf', 'f543d067-beff-4c1f-a2b8-203ffe70d4b6'), - ('87005eaf-d51c-4876-92ea-5071973de1f6', '01062648-d3db-4f52-a0bf-970f663f8349'), - ('64f5c488-7600-4d94-a85b-6fff17ffc746', '90670cac-e0c4-4928-a8a3-bda0a55dd8e1'), - ('ad7c0cf8-af9c-46e0-b717-492b29ce4411', '28c1e81c-5169-4f1f-b1ce-f7da05a446a0'), - ('71f423c7-0de0-445b-b84f-a507c2a56398', '01062648-d3db-4f52-a0bf-970f663f8349'), - ('e6a32779-9a8c-4e6c-bf25-0cc33de94f19', 'a2a92231-fb9e-4285-aa8a-abd1f20eaad0'), - ('43bec431-1290-420e-8dc6-906204a6d25f', 'e8275563-8dba-4172-99b9-7c9ab5187e39'), - ('59c54e42-163b-45ab-a3dc-ce0816f5ed9f', '0a49c05e-c642-4235-8a36-03c6f29b7be4'), - ('d82d92a3-54a5-41ac-b77b-c72535bffeb6', 'a2a92231-fb9e-4285-aa8a-abd1f20eaad0'), - ('20697a2d-6586-4ffb-901c-529f154c8b43', 'c51e1c30-a4db-4419-8f65-9fa94496a773'), - ('1f8826ea-60a9-4d93-a7f8-96fbe72aef6c', '0a49c05e-c642-4235-8a36-03c6f29b7be4'), - ('ae28eba7-c9d8-480c-ab42-20e348f0355d', 'c51e1c30-a4db-4419-8f65-9fa94496a773'), - ('049681c1-5a04-4cf3-aaff-fdea575daeb6', '0a49c05e-c642-4235-8a36-03c6f29b7be4'), - ('bc5d795c-4d2b-4fe2-938a-5f7758e8c90f', '590f0b13-461e-43ca-bc10-136f1eeb340c'), - ('2771231b-9b2d-46c0-ac2d-d34d71f16c09', '6ad92c84-c953-4077-b652-39474a8e83c6'), - ('bab7972e-5668-4fdd-9de2-11b6a6788703', '590f0b13-461e-43ca-bc10-136f1eeb340c'), - ('75720af5-67cc-493d-88cf-4e373bc579e0', '912e172c-3a21-4b16-a0da-bf86a1e01719'), - ('3a57acd0-4c0a-4cc8-bf99-0c9f959eaed6', '912e172c-3a21-4b16-a0da-bf86a1e01719'), - ('fc721d7c-6a69-4241-8294-45122584b238', 'a1124508-f464-46e9-85c6-33590d37c913'), - ('0264e8c3-d63f-40ba-906c-e3c090ce02ab', '590f0b13-461e-43ca-bc10-136f1eeb340c'), - ('b17877ae-0ab0-4ffc-a4fb-49a8c80c0374', 'a1124508-f464-46e9-85c6-33590d37c913'), - ('c2d154ca-4e56-4d88-9982-8e5587463b29', 'e36f2219-46be-44ab-8699-0d4c70d5d6d0'), - ('5ccadffe-a860-49b2-be7c-73af5a6f81e9', '9766b4ca-5599-48cb-9b12-83210b49e152'), - ('6e563048-0891-418e-a5f5-28f228f0339b', '3bdc66df-dae8-472b-8820-94326307b6ec'), - ('e83926a9-b618-4398-a4ec-9022314df3bd', '3bdc66df-dae8-472b-8820-94326307b6ec'), - ('08496b12-c259-49ad-9ceb-6669171c38c3', '9766b4ca-5599-48cb-9b12-83210b49e152'), - ('eae2707f-4804-4691-80ba-4f86f71e7b32', '01394483-9074-4ae3-86bc-61fb9e1a0886'), - ('709e726f-02d8-4549-b77a-c38d14054cf3', 'e36f2219-46be-44ab-8699-0d4c70d5d6d0'), - ('4f2eacd8-daaf-45f6-9986-de5a9c8e459d', 'e4c0558b-f275-464d-a531-e56f6728fd4d'), - ('483d6ba0-ce02-4f0f-8f27-d417289939a5', '5110d5e4-f4e8-4f4b-86e4-b93b921cc76b'), - ('e70c332b-d0f6-403b-b3c0-cc7467487cae', '5110d5e4-f4e8-4f4b-86e4-b93b921cc76b'), - ('6686a09b-f767-466a-a355-fe8d9a16c28b', '82361471-13cb-4d55-a48e-ae72d4269dec'), - ('f819d4fc-d3d9-48a8-b06e-92e982686d25', 'eca3fd1f-2c80-4983-92eb-a4969e16c5ec'), - ('a224c0dc-d86d-4fdb-9f69-bd59d26f9760', '5110d5e4-f4e8-4f4b-86e4-b93b921cc76b'), - ('b8766b9d-5ea3-4704-ad6e-88d5f94eb816', '217d01b8-dc31-4ba9-a6f2-ce32369f7797'), - ('f4a7de5a-7ffd-4ae3-8266-7fe12a75256f', '217d01b8-dc31-4ba9-a6f2-ce32369f7797'), - ('651c7ffe-58d4-4807-aef5-720e58d254d8', 'aceff9cb-e679-46b0-b148-a4d15154ddfe'), - ('d3c78cb9-72b8-4277-ae12-094ce4be1bbc', '114a8e9d-a7a6-4445-854c-469208dd9c10'), - ('619f02b0-7639-42aa-ae39-2808289c713f', '114a8e9d-a7a6-4445-854c-469208dd9c10'), - ('edc39164-215e-4a4c-9e91-a4ef1cc17709', 'fa8437aa-2131-4bb8-9361-25d7bab6bde6'), - ('d7a2898a-887c-4832-af44-4c2bfa8c482f', 'aceff9cb-e679-46b0-b148-a4d15154ddfe'), - ('1510b125-197d-4389-afa9-70d2ee2c610e', 'fa8437aa-2131-4bb8-9361-25d7bab6bde6'), - ('a2c8a7ca-8f5f-4fe0-ac47-a186dc8cea25', '7cdafda5-30f1-42c0-8fe2-edcf7522724b'), - ('fff9365e-e86f-46d4-8ac3-e90ac6c8f391', '7cdafda5-30f1-42c0-8fe2-edcf7522724b'), - ('8d3df58a-3b57-4269-9f23-a02d8630b001', '6810f956-cb7e-4689-b717-917246f2ae25'), - ('c1a6dc5c-e952-43e0-bd02-5505e4db72f0', '36b7080d-2875-4dcb-ae8e-eb06468cf2b0'), - ('7b1ec3b2-5e72-4b4d-87f6-1b158e4b98a4', '36b7080d-2875-4dcb-ae8e-eb06468cf2b0'), - ('2fd89257-98c7-4bae-9186-961259edd4fd', '36b7080d-2875-4dcb-ae8e-eb06468cf2b0'), - ('d43541ae-bc9c-49ad-aa5f-097563ca3e2d', '9bf13d98-1393-4e56-87bd-72ff466e6521'), - ('24ae6a1a-019d-4040-bacc-115be99f7982', '9bf13d98-1393-4e56-87bd-72ff466e6521'), - ('1c78e9bd-e869-4486-a891-5dae020f35da', '6dc18765-0b64-4e8d-8ba7-b99c64a3bbc3'), - ('642b2621-16bb-4615-9b4c-4ea6cd32f7e9', '9b0ba3d3-e244-429e-b309-27d24fe5f484'), - ('df901da9-9a6e-4d54-9fbd-e14065dadc40', '9b0ba3d3-e244-429e-b309-27d24fe5f484'), - ('643deb27-9521-4672-9ebc-9349e39607a4', '6dc18765-0b64-4e8d-8ba7-b99c64a3bbc3'), - ('77278afe-54fa-4174-b56f-bf2217aaa8d6', '6dc18765-0b64-4e8d-8ba7-b99c64a3bbc3'), - ('baa06fdf-495a-42ee-ba1c-026953301487', 'bce4184c-1317-454d-badc-da2549493f01'), - ('f80ecf2c-4e52-4957-bb3a-3bd5f98b1017', '1966e857-54d2-4e1b-a22e-e120c96724e6'), - ('f3565c7f-39cd-46b4-bc44-2682d7b48cbc', '1966e857-54d2-4e1b-a22e-e120c96724e6'), - ('2080b4ac-4e00-4e24-9f93-05b54d72f2e9', 'bce4184c-1317-454d-badc-da2549493f01'), - ('276bf24a-da04-4276-bbb8-206e9b276968', 'd33b2210-3ef5-4e3b-9f0e-bf2e83e22fff'), - ('5fd32a4a-4ab0-44ad-873d-459b9062ae6e', '4a7831cc-5b26-470c-8966-4093f3fd9a8a'), - ('8f54a3a2-38cc-48d6-85e1-d8fe47eea2f0', '4a7831cc-5b26-470c-8966-4093f3fd9a8a'), - ('091b0fea-c089-499d-a9e4-8a0eba3bbe47', '039d221e-b71a-4f55-b4a0-ac80e7a382da'), - ('a4b2ead5-9d73-4064-b261-35cfcd0e6f89', '5fe3ea1e-c1c5-4739-8d4f-5595fe47554a'), - ('81cab85b-0272-40b2-9163-92973bc0fdfd', '039d221e-b71a-4f55-b4a0-ac80e7a382da'), - ('c7b7ac95-5c5a-4dcd-8a59-c8060011f753', '74bae823-b725-4523-94d2-bfbfba901586'), - ('914b10d9-622a-4269-b6bc-07c1377fb512', '74bae823-b725-4523-94d2-bfbfba901586'); + ('7d0697cd-868c-4b41-9f4f-f9a8c1131b24', '62097027-979f-4b00-afb8-f70fba533f80'); END; $$ LANGUAGE plpgsql; @@ -388,14 +128,16 @@ SELECT sys_insert_project_access_levels(); SELECT sys_insert_task_status_categories(); SELECT sys_insert_project_statuses(); SELECT sys_insert_project_healths(); --- SELECT sys_insert_project_templates(); +SELECT sys_insert_license_types(); +SELECT sys_insert_project_templates(); DROP FUNCTION sys_insert_task_priorities(); DROP FUNCTION sys_insert_project_access_levels(); DROP FUNCTION sys_insert_task_status_categories(); DROP FUNCTION sys_insert_project_statuses(); DROP FUNCTION sys_insert_project_healths(); --- DROP FUNCTION sys_insert_project_templates(); +DROP FUNCTION sys_insert_license_types(); +DROP FUNCTION sys_insert_project_templates(); INSERT INTO timezones (name, abbrev, utc_offset) SELECT name, abbrev, utc_offset diff --git a/worklenz-backend/database/sql/4_functions.sql b/worklenz-backend/database/sql/4_functions.sql index b3af256b..189f8ac7 100644 --- a/worklenz-backend/database/sql/4_functions.sql +++ b/worklenz-backend/database/sql/4_functions.sql @@ -4876,8 +4876,8 @@ BEGIN --insert organization data INSERT INTO organizations (user_id, organization_name, contact_number, contact_number_secondary, trial_in_progress, trial_expire_date, subscription_status, license_type_id) - VALUES (_user_id, TRIM((_body ->> 'displayName')::TEXT), NULL, NULL, TRUE, CURRENT_DATE + INTERVAL '14 days', - 'trialing', (SELECT id FROM sys_license_types WHERE key = 'TRIAL')) + VALUES (_user_id, TRIM((_body ->> 'team_name')::TEXT), NULL, NULL, TRUE, CURRENT_DATE + INTERVAL '9999 days', + 'active', (SELECT id FROM sys_license_types WHERE key = 'SELF_HOSTED')) RETURNING id INTO _organization_id; INSERT INTO teams (name, user_id, organization_id) @@ -4958,10 +4958,11 @@ BEGIN --insert organization data INSERT INTO organizations (user_id, organization_name, contact_number, contact_number_secondary, trial_in_progress, trial_expire_date, subscription_status, license_type_id) - VALUES (_user_id, TRIM((_body ->> 'team_name')::TEXT), NULL, NULL, TRUE, CURRENT_DATE + INTERVAL '14 days', - 'trialing', (SELECT id FROM sys_license_types WHERE key = 'TRIAL')) + VALUES (_user_id, TRIM((_body ->> 'team_name')::TEXT), NULL, NULL, TRUE, CURRENT_DATE + INTERVAL '9999 days', + 'active', (SELECT id FROM sys_license_types WHERE key = 'SELF_HOSTED')) RETURNING id INTO _organization_id; + -- insert team INSERT INTO teams (name, user_id, organization_id) VALUES (_trimmed_team_name, _user_id, _organization_id) From 89e39520bacf855fbb1071be2fcafc14f010621b Mon Sep 17 00:00:00 2001 From: chamiakJ Date: Mon, 21 Apr 2025 07:43:17 +0530 Subject: [PATCH 4/5] Update README.md and .env.template for improved clarity and configuration - Enhanced README.md with a new header, centered links, and an updated project description. - Modified .env.template to change Google callback and redirect URLs for consistency in authentication flow. --- README.md | 58 ++++++++++++++++------------------ worklenz-backend/.env.template | 6 ++-- 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 0288fe02..7e2350e3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,31 @@ -# Worklenz +

        + + Worklenz Logo + +
        + Worklenz +

        -Worklenz is an open-source project management platform designed to help teams collaborate efficiently on tasks and projects. +

        + Task Management | + Time Tracking | + Analytics | + Resource Management | + Project Templates +

        + +

        + + Worklenz + +

        + +Worklenz is a project management tool designed to help organizations improve their efficiency. It provides a +comprehensive solution for managing projects, tasks, and collaboration within teams. ## Features @@ -167,35 +192,6 @@ Email [your-security-email@example.com](mailto:your-security-email@example.com) This project is licensed under the [MIT License](LICENSE). -

        - - Worklenz Logo - -
        - Worklenz -

        - -

        - Task Management | - Time Tracking | - Analytics | - Resource Management | - Project Templates -

        - -

        - - Worklenz - -

        - -Worklenz is a project management tool designed to help organizations improve their efficiency. It provides a -comprehensive solution for managing projects, tasks, and collaboration within teams. - ## Screenshots

        diff --git a/worklenz-backend/.env.template b/worklenz-backend/.env.template index 281e8514..145f2411 100644 --- a/worklenz-backend/.env.template +++ b/worklenz-backend/.env.template @@ -20,9 +20,9 @@ DB_MAX_CLIENTS=50 # Google Login GOOGLE_CLIENT_ID="your_google_client_id" GOOGLE_CLIENT_SECRET="your_google_client_secret" -GOOGLE_CALLBACK_URL="http://localhost:3000/secure/google/verify" -LOGIN_FAILURE_REDIRECT="/" -LOGIN_SUCCESS_REDIRECT="http://localhost:5000/auth/authenticate" +GOOGLE_CALLBACK_URL="http://localhost:5000/secure/google/verify" +LOGIN_FAILURE_REDIRECT="http://localhost:5000/auth/authenticating" +LOGIN_SUCCESS_REDIRECT="http://localhost:5000/auth/authenticating" # CLI ANGULAR_DIST_DIR="path/to/frontend/dist" From ad7c2b20a251f7bfe3b1882b416fcdef122b9ecd Mon Sep 17 00:00:00 2001 From: chamiakJ Date: Mon, 21 Apr 2025 07:43:35 +0530 Subject: [PATCH 5/5] Update README.md and .env.example for Node.js version and redirect URLs - Updated Node.js version requirement in README.md to v18 or newer. - Modified .env.example to change Google callback and redirect URLs for consistency in authentication flow. --- README.md | 4 ++-- worklenz-backend/.env.example | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7e2350e3..449ec741 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ This repository contains the frontend and backend code for Worklenz. ## Requirements -- Node.js version v16 or newer +- Node.js version v18 or newer - PostgreSQL version v15 or newer - Docker and Docker Compose (for containerized setup) @@ -54,7 +54,7 @@ These instructions will help you set up and run the Worklenz project on your loc ### Prerequisites -- Node.js (version 16 or higher) +- Node.js (version 18 or higher) - PostgreSQL database - An S3-compatible storage service (like MinIO) or Azure Blob Storage diff --git a/worklenz-backend/.env.example b/worklenz-backend/.env.example index 281e8514..145f2411 100644 --- a/worklenz-backend/.env.example +++ b/worklenz-backend/.env.example @@ -20,9 +20,9 @@ DB_MAX_CLIENTS=50 # Google Login GOOGLE_CLIENT_ID="your_google_client_id" GOOGLE_CLIENT_SECRET="your_google_client_secret" -GOOGLE_CALLBACK_URL="http://localhost:3000/secure/google/verify" -LOGIN_FAILURE_REDIRECT="/" -LOGIN_SUCCESS_REDIRECT="http://localhost:5000/auth/authenticate" +GOOGLE_CALLBACK_URL="http://localhost:5000/secure/google/verify" +LOGIN_FAILURE_REDIRECT="http://localhost:5000/auth/authenticating" +LOGIN_SUCCESS_REDIRECT="http://localhost:5000/auth/authenticating" # CLI ANGULAR_DIST_DIR="path/to/frontend/dist"